Core
Fetch the module first: go get github.com/yshengliao/gortexa@v0.27.0. Every snippet below is compile-verified against v0.27.0.
kernel
Section titled “kernel”The composition root: App owns the single h2c listener (multiplexing gRPC, HTTP/JSON and MCP), the gRPC server, the health registry, an in-process loopback, and graceful shutdown. Build it with kernel.New(opts...); options include WithConfig, WithLogger, WithInterceptors (takes an interceptor.Set by value, not a pointer), WithStatsHandler, WithHTTPWrap, WithGateway, WithMCPHandler, WithShutdownHook, WithServerOptions (append gRPC ServerOptions to add your own interceptors/stats, chained after the framework’s), WithAdminListener (opt-in separate ops port serving only /healthz + /readyz), WithExtraListener (serve any handler on a caller-owned listener). The main h2c port stays single-port-multiplexed; app.AdminAddr() reports the bound admin address.
import ( "context" "github.com/yshengliao/gortexa/config" "github.com/yshengliao/gortexa/health" "github.com/yshengliao/gortexa/kernel")
cfg := config.MustBuild()app, err := kernel.New(kernel.WithConfig(cfg))if err != nil { return err}// register your gRPC services on app.GRPCServer() hereapp.Health().Register("self", func(context.Context) health.State { return health.Healthy })return app.Run(ctx) // /healthz and /readyz are built inApp methods: GRPCServer(), Health(), Loopback(), Logger(), SetGateway(), SetMCPHandler(), Run(ctx), Shutdown(ctx).
config
Section titled “config”Fixed precedence: built-in defaults < YAML file < .env file < environment (GORTEXA_ prefix, __ for nesting). The entry points are MustBuild (fail-loud: it panics when jwt_secret is empty, the dev placeholder, or < 32 bytes, and when issuer is blank) and BuildUnvalidated. There is no Load.
import "github.com/yshengliao/gortexa/config"
cfg := config.MustBuild(config.WithConfigFile("etc/config.yaml"))dsn := cfg.DB.DSN.Reveal() // a Secret is revealed only at the point of useThe config.Secret type masks itself to **** in String(), logs and JSON; call Reveal() for the real value. Config’s sub-structs: Server, Auth, DB, Cache, MQ, Log, Observ (all fields documented in Configuration).
interceptor
Section titled “interceptor”NewSet(Config) builds the eight-stage fixed-order chain (recover → request-id → logger → load-shed → rate-limit → circuit-breaker → auth → validation), which you pass to kernel.WithInterceptors.
import ( "time" "github.com/yshengliao/gortexa/auth" "github.com/yshengliao/gortexa/interceptor")
set, err := interceptor.NewSet(interceptor.Config{ Verifier: auth.MustNewVerifier([]byte(secret), "gortexa"), RateLimit: interceptor.RateLimitConfig{RPS: 200, Burst: 100, TTL: 10 * time.Minute},})// pass set to kernel.WithInterceptors(set)Config also carries Logger, AuthSkip, CircuitBreaker, LoadShedding, and Metrics. Set exposes UnaryChain() / StreamChain().
The auth stage is pluggable: Verifier uses the built-in HS256 JWT; a non-JWT scheme sets Authenticator instead (an auth.Authenticator, Authenticate(ctx) (ctx, error)) — static bearer, mTLS, API key. Authenticator wins when both are set. JWT is also available explicitly via auth.NewJWTAuthenticator(v).
HS256 JWT verification/signing plus context helpers. The secret must be ≥ 32 bytes.
import "github.com/yshengliao/gortexa/auth"
v := auth.MustNewVerifier([]byte(secret), "gortexa")tok, err := v.Sign("user-123", []string{"admin"}, time.Hour)// later, inside a handler, retrieve the verified claims:if claims, ok := auth.ClaimsFrom(ctx); ok { _ = claims.Subject // "user-123"}Verify(token) returns *Claims (with Roles plus embedded jwt.RegisteredClaims); BearerToken(header) extracts a token from the Authorization header.
apperr
Section titled “apperr”A single mapping table drives the gRPC status, HTTP status and MCP error envelope. *apperr.Error implements GRPCStatus(), so a handler just returns it.
import "github.com/yshengliao/gortexa/apperr"
func (s *svc) GetThing(ctx context.Context, req *pb.GetThingRequest) (*pb.Thing, error) { t, err := s.repo.Find(ctx, req.GetId()) if err != nil { return nil, apperr.Wrap(apperr.CatNotFound, "thing not found", err) } return t, nil}Only CatInvalidArgument and CatUnauthenticated forward the developer message to the caller; every other category exposes only the registry SafeMessage, so internal causes never leak. The category constants cover the full gRPC set (CatNotFound, CatPermissionDenied, CatFailedPrecondition, CatInternal, and so on).