Brick

App setup

Brick[TCtx], Config, middleware, handler, and docs endpoints.

Source: brick.go.

Brick[TCtx]

Brick[TCtx] owns three things: a bunrouter router, a huma.API (OpenAPI 3.1), and a db.DB wrapper. TCtx is your app-defined context type — use Config[any] when you don't need one.

type Config[TCtx any] struct {
    Title   string
    Version string
    DB      *bun.DB

    API       huma.API              // optional: inject your own (tests)
    Resources []ResourceConfig
    Routes    func(api huma.API, db *db.DB)

    CtxFromRequest func(req *http.Request, base *AppCtx) *TCtx
}
  • DB == nil is an error, never a panic.
  • When API is nil, Brick builds its own router + adapter and owns the http.Handler. When API is supplied (typically humatest in tests), Brick uses it as-is and Handler() / Router() return nil — the caller owns the router.
  • Routes is the only custom-route mechanism; app code cannot override a resource's CRUD routes.

Lifecycle

b, err := brick.New(cfg)   // registers Resources + Routes
b.Huma()                   // underlying huma.API
b.DB()                     // *db.DB wrapper
b.Router()                 // *bunrouter.Router (nil when API injected)
b.Handler()                // middleware + router (nil when API injected)
b.ListenAndServe(":8080")  // errors when Brick doesn't own the router

Middleware and identity

b.Middleware() stores your typed app context on every request. Brick mounts no auth of its own — it calls CtxFromRequest(r, nil) with a nil base, and stores the result only if it implements AppCtxer. Nil result means anonymous; guards treat it as unauthenticated. Header parsing and crypto live in your middleware, Brick owns the plumbing. See Context.

Handler() wraps the router with requestIDMiddleware + Middleware(): X-Request-ID is passed through or minted, echoed as a response header, and put in the request context for error responses and logs.

OpenAPI and Scalar

Huma serves the spec itself: GET /openapi.json (same bytes as b.OpenAPISpec()), plus Stoplight docs at /docs — no wiring needed. For the Scalar viewer, mount /reference with an absolute spec URL built per request: scalar-go rejects relative URLs, and the public host is only known at request time (localhost vs your Vercel domain):

b.Router().GET("/reference", func(w http.ResponseWriter, req bunrouter.Request) error {
    scheme := req.Header.Get("X-Forwarded-Proto") // edge scheme behind proxies
    if scheme == "" {
        scheme = "http"
        if req.TLS != nil {
            scheme = "https"
        }
    }
    html, err := brick.ScalarDocsHTML(scheme + "://" + req.Host + "/openapi.json")
    if err != nil {
        http.Error(w, "reference unavailable", http.StatusInternalServerError)
        return nil
    }
    w.Header().Set("Content-Type", "text/html; charset=utf-8")
    _, _ = w.Write([]byte(html))
    return nil
})

See Deploy to Vercel for the full serverless pattern.

On this page