Skip to content

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+.

The dev toolchain (buf, protoc plugins, sqlc, govulncheck — all pinned):

Terminal window
GORTEXA_VERSION="v0.28.3" # update only after testing a newer release
export 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:

Terminal window
export PATH="$(go env GOPATH)/bin:$PATH" # add to your shell profile

Run 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.

Terminal window
gortexa create myapp --module github.com/me/myapp \
--repo https://github.com/yshengliao/gortexa.git --ref v0.28.3
cd 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.yaml

create 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.

Terminal window
gortexa gen billing/v1 Invoice

This 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).

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.

Terminal window
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.

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:

Terminal window
test ! -e cmd/devtoken
mkdir -p cmd/devtoken

Save 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:

Terminal window
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" = 404
printf 'authenticated HTTP status=%s\n' "$AUTH_STATUS"
rm cmd/devtoken/main.go
rmdir cmd/devtoken
cleanup
trap - EXIT INT TERM

Do 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.

Every framework package is importable directly with go get — no scaffolded project required:

Terminal window
go get github.com/yshengliao/gortexa@latest

A 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: config for layered config, interceptor for the governed chain, httpcompat for the gateway mux, apperr for the three-transport error model, plus mq / cache / storage / client.
  • AI services: annotate your protos with gortexa/ai/v1/annotations.proto (its Go bindings ship in the companion module github.com/yshengliao/gortexa/api, import path github.com/yshengliao/gortexa/api/gen/gortexa/ai/v1, resolved along with the framework by go get) and wire mcp.NewBridge to 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.

When developing the framework itself, use make:

Terminal window
make bootstrap # install the pinned toolchain
make gen # buf lint → breaking → generate
make test # go test -race
make run # start the sample server (injects a local dev JWT secret)
make dev # docker compose with Jaeger, Grafana, Prometheus
  • 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.