Quickstart
This quickstart installs the pinned toolchain, scaffolds a project, generates a CRUD API from proto, and verifies all three protocols — about ten minutes on a machine that already has Go 1.21+.
Install
Section titled “Install”The dev toolchain (buf, protoc plugins, sqlc, govulncheck — all pinned):
GORTEXA_VERSION="v0.28.3" # update only after testing a newer releaseexport GORTEXA_REF="$GORTEXA_VERSION"export GOTOOLCHAIN=auto/bin/bash -c "$(curl -fsSL "https://raw.githubusercontent.com/yshengliao/gortexa/$GORTEXA_VERSION/install.sh")"go install github.com/yshengliao/gortexa/cmd/gortexa@"$GORTEXA_VERSION"Run outside a checkout, the script clones the repo into ./gortexa (override with GORTEXA_DIR, delete afterwards if unwanted); it only writes persistent go env settings when it detects the broken dev-container values — a normal machine’s Go config is left untouched.
Note: the go.mod of v0.28.0 and v0.28.1 carries a replace directive, and Go refuses go install …@ either of them (“contains one or more replace directives”). Pin the CLI to v0.28.2 or later; v0.27.5 installs too, but its scaffolds lack the namespace and api-module behaviour this page describes.
go install puts the binary in $(go env GOPATH)/bin, which most shells do not have on PATH by default — without it, the next step fails with command not found: gortexa:
export PATH="$(go env GOPATH)/bin:$PATH" # add to your shell profileRun gortexa doctor afterwards to check the Go toolchain and proto tools.
The full command set: create (new project), gen (generate an API), regen (regenerate after proto edits), run (start the dev server), export --format mcp|openai|gemini [--out file] (export AI tool schemas), tools install (install the pinned toolchain), tools sync (re-pin one tool), skills install / skills list (AI-assistant skills), doctor (environment check), and version.
Create a project
Section titled “Create a project”gortexa create myapp --module github.com/me/myapp \ --repo https://github.com/yshengliao/gortexa.git --ref v0.28.3cd myapp# create writes no usable secret — it leaves the placeholder that refuses# startup. Confirm it is there.grep -Fq 'jwt_secret: "dev-only-insecure-secret-change-me-please"' etc/config.yamlcreate copies the framework layout, rewrites the module path and drops the git history. The result ships a sample resource, the full interceptor chain, config, health and the MCP bridge. The sample lives in the project’s own proto namespace: package myapp.resource.v1 under proto/myapp/resource/v1/. The namespace is derived from the last element of the module path and recorded in .gortexa/project.json, which gortexa doctor prints.
Generate your first API
Section titled “Generate your first API”gortexa gen billing/v1 InvoiceThis produces the proto contract (with validation and AI annotations), an in-memory logic stub and the server wiring, then runs buf lint → breaking → generate. The service lands under the project’s namespace: package myapp.billing.v1 in proto/myapp/billing/v1/invoice.proto, and gRPC and MCP see it as myapp.billing.v1.InvoiceService. After later proto edits, run gortexa regen. Overwriting an existing API needs --force — note it also implies --allow-breaking, skipping buf’s breaking-change gate.
Everything under gen/ is generated code: never hand-edited, produced only by make gen (committed, following the framework convention — the copied CI has a drift guard).
Run and verify
Section titled “Run and verify”The following single-shell smoke test uses a short-lived runtime secret, starts
the same server entrypoint used by gortexa run, waits for readiness, and registers
cleanup for its binary and log. The server remains for the next token flow; do not
change shells between these commands.
export GORTEXA_AUTH__JWT_SECRET="$(openssl rand -hex 32)"go build -o ./.gortexa-prototype-server ./cmd/server./.gortexa-prototype-server >./gortexa-prototype.log 2>&1 &SERVER_PID=$!cleanup() { kill -TERM "$SERVER_PID" 2>/dev/null || true; wait "$SERVER_PID" 2>/dev/null || true; rm -f ./.gortexa-prototype-server ./gortexa-prototype.log; }trap cleanup EXIT INT TERM( set -euo pipefail for _ in $(seq 1 60); do if curl -fsS http://127.0.0.1:8080/healthz >/dev/null 2>&1; then break; fi sleep 0.5 done curl -fsS http://127.0.0.1:8080/healthz test "$(curl -sS -o /dev/null -w '%{http_code}' http://127.0.0.1:8080/v1/resources/x)" = 401 curl -fsS -X POST http://127.0.0.1:8080/mcp \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}')The final HTTP-gateway request returns 401 by design; it shares authentication with gRPC and MCP. This smoke test does not replace a live gRPC E2E or authorization test. If you will not continue to the token flow, run cleanup; trap - EXIT INT TERM now to stop the server and remove temporary files. For normal interactive development, run gortexa run in the same shell with the exported secret, then stop it with Ctrl-C.
Getting a dev token
Section titled “Getting a dev token”The server remains running in the same shell from the previous block. First ensure the temporary command does not already exist, then create its directory:
test ! -e cmd/devtokenmkdir -p cmd/devtokenSave the following secret-free program as cmd/devtoken/main.go. Replace the import if your module is not github.com/me/myapp:
package main
import ( "fmt" "os" "time"
"github.com/me/myapp/auth")
func main() { secret := os.Getenv("GORTEXA_AUTH__JWT_SECRET") v, err := auth.NewVerifier([]byte(secret), "gortexa") if err != nil { panic(err) } tok, err := v.Sign("dev", []string{"admin"}, time.Hour) if err != nil { panic(err) } fmt.Println(tok)}Run it, verify an authenticated request, then remove the temporary command and stop the server:
TOKEN="$(go run ./cmd/devtoken)"AUTH_STATUS="$(curl -sS -o /dev/null -w '%{http_code}' http://127.0.0.1:8080/v1/resources/x -H "Authorization: Bearer $TOKEN")"test "$AUTH_STATUS" = 200 || test "$AUTH_STATUS" = 404printf 'authenticated HTTP status=%s\n' "$AUTH_STATUS"rm cmd/devtoken/main.gormdir cmd/devtokencleanuptrap - EXIT INT TERMDo not copy, commit, or bake a real secret from etc/config.yaml into an image. In production, inject and rotate it through a secret manager, mounted file, or runtime environment.
Use as a module (no scaffold)
Section titled “Use as a module (no scaffold)”Every framework package is importable directly with go get — no scaffolded project required:
go get github.com/yshengliao/gortexa@latestA minimal app — one h2c port with gRPC health, /healthz, /readyz — needs no code generation at all. kernel.New requires an explicit decision about the governance chain: pass WithInterceptors to install the eight stages, or WithoutInterceptors() to serve without them — omitting both fails at startup, because the framework will not let you make that choice by forgetting. The example below takes the second option, which is only safe because it serves nothing but health; switch to WithInterceptors before registering a real service:
package main
import ( "context" "log" "os/signal" "syscall"
"github.com/yshengliao/gortexa/health" "github.com/yshengliao/gortexa/kernel")
func main() { ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop()
// defaults: h2c on :8080, graceful shutdown. WithoutInterceptors is a // deliberate opt-out: no recover, auth, rate limit or validation. app, err := kernel.New(kernel.WithoutInterceptors()) if err != nil { log.Fatal(err) } app.Health().Register("self", func(context.Context) health.State { return health.Healthy })
if err := app.Run(ctx); err != nil { log.Fatal(err) }}From there, both the AI and the non-AI path are first-class:
- Plain services:
configfor layered config,interceptorfor the governed chain,httpcompatfor the gateway mux,apperrfor the three-transport error model, plusmq/cache/storage/client. - AI services: annotate your protos with
gortexa/ai/v1/annotations.proto(its Go bindings ship in the companion modulegithub.com/yshengliao/gortexa/api, import pathgithub.com/yshengliao/gortexa/api/gen/gortexa/ai/v1, resolved along with the framework bygo get) and wiremcp.NewBridgeto expose the annotated RPCs as MCP tools behind the same interceptor chain.
For each public component’s purpose, import path and a minimal example, see Components.
Note: since v0.28.0 a project created by gortexa create may also go get the framework. The gortexa.ai.v1 descriptor is registered once, by the github.com/yshengliao/gortexa/api module both depend on, and the sample sits in the project’s own namespace, so nothing collides. The project still carries its own copy of the framework source under its own module path, so importing github.com/yshengliao/gortexa/... as well links two independent copies of that code — reach for it only when you need something your copy does not have. Scaffolds from v0.27 and earlier have no such guarantee: both copies register gortexa/ai/v1/annotations.proto and the binary panics at init.
Working inside the framework checkout
Section titled “Working inside the framework checkout”When developing the framework itself, use make:
make bootstrap # install the pinned toolchainmake gen # buf lint → breaking → generatemake test # go test -racemake run # start the sample server (injects a local dev JWT secret)make dev # docker compose with Jaeger, Grafana, PrometheusNext steps
Section titled “Next steps”- Core concepts: how single-port multiplexing, the interceptor chain and the error model work.
- Configuration: every config.yaml field and the environment-variable overrides.
- Examples: 11 runnable verification examples.