FAQ
Common questions organized by topic. Jump to a section or scan the table below for the shortest path to the answer.
| I want to know… | Jump to |
|---|---|
| Whether Plumego fits my project | Fit check |
| How to build a REST API | REST API |
| How to add authentication | JWT Auth |
| How to connect a database | Database |
| How Plumego compares to Gin/Echo/Chi | Comparison |
| Whether I can use it in production now | Production readiness |
| Router performance overhead | Benchmarks |
| Stable root vs x/* decision | Module decision |
| Custom error format | Custom errors |
| Explicit DI rationale | DI rationale |
Getting Started
Section titled “Getting Started”Is Plumego the right fit for my team?
Section titled “Is Plumego the right fit for my team?”Read When Not to Use Plumego before committing to technical evaluation. That page lists the trade-offs Plumego makes deliberately — and the conditions under which a different tool is the better choice. The honest answer matters more than the feature list.
How do I build a REST API with Plumego?
Section titled “How do I build a REST API with Plumego?”Start with the four-step bootstrap from Getting Started, then register your routes using app.Get, app.Post, app.Put, and app.Delete. Use contract.WriteResponse for success responses and contract.NewErrorBuilder for error responses. The Handle Errors guide and Reference App show complete handler examples.
How do I add authentication to my routes?
Section titled “How do I add authentication to my routes?”Use security/jwt to issue and verify tokens, then protect routes with middleware/auth. The Add JWT Auth guide walks through the complete wiring in three steps.
How do I connect a database?
Section titled “How do I connect a database?”Import github.com/spcent/plumego/store/db, open a *sql.DB with db.Open, inject it through your application’s dependency struct, and pass it to handlers explicitly. The Connect Database guide covers connection pooling and context-based helpers.
Framework Comparison
Section titled “Framework Comparison”How do Plumego and Gin / Echo / Chi compare?
Section titled “How do Plumego and Gin / Echo / Chi compare?”All four are Go HTTP toolkits built on net/http. The key difference is philosophy:
- Gin and Echo give you a framework-shaped handler (
*gin.Context,echo.Context) that hides the standardhttp.ResponseWriter/*http.Requestpair. - Chi keeps the standard
http.Handlershape but is a router, not a full toolkit. - Plumego keeps the standard handler shape, provides a structured error and response model (
contract), a middleware catalog, and security primitives — without replacing thenet/httptypes your handlers already use.
If you have existing Go HTTP handlers, they work in Plumego without modification.
Can I use Plumego for GraphQL?
Section titled “Can I use Plumego for GraphQL?”Yes. Plumego has no opinion about the HTTP body format — it transports JSON by default but any handler can write any content type. Mount a GraphQL handler (e.g. from graph-gophers/graphql-go or 99designs/gqlgen) as a standard http.Handler on any route.
Auth, Security, and Production
Section titled “Auth, Security, and Production”What is the recommended production configuration?
Section titled “What is the recommended production configuration?”Set ReadHeaderTimeout to 5s (or less) to block slowloris attacks, tune WriteTimeout to match your longest handler path plus a margin, and set IdleTimeout below your load balancer’s idle timeout. See the Configuration Model for the full field reference.
How do I update routes without restarting?
Section titled “How do I update routes without restarting?”Plumego freezes the route table at app.Prepare() time. Dynamic routing (runtime route add/remove) is not a goal of the stable routing surface. For feature-flag routing, implement the branching logic inside a single handler rather than registering multiple routes.
What is Plumego trying to optimize for?
Section titled “What is Plumego trying to optimize for?”Plumego optimizes for explicit HTTP services built on the standard-library model. It prefers visible route wiring, visible bootstrap code, narrow stable roots, and a repository structure that agents can classify reliably.
Is Plumego a full-stack web framework?
Section titled “Is Plumego a full-stack web framework?”No. Plumego is a Go HTTP toolkit with strong opinions about explicit service structure. It does not try to hide application wiring behind framework magic.
The distinction matters in practice: Plumego keeps the standard func(http.ResponseWriter, *http.Request) handler signature everywhere. Your handlers do not know they are inside Plumego. You can take any existing net/http handler, mount it with app.Get or app.Handle, and it works without modification.
What Plumego adds on top of the stdlib is: a trie-based router, an explicit middleware chain, a structured response and error contract, security helpers, and a lifecycle (Prepare → Server → Shutdown). It does not generate code, hide initialization in init(), or own global state. The wiring you write in main.go and internal/app is the full picture — there is no framework-owned bootstrap running in the background.
Why keep saying “stdlib-first”?
Section titled “Why keep saying “stdlib-first”?”Because the request and handler shape should stay close to net/http. Plumego wants services to remain legible to people who already understand the Go standard library.
Module Stability
Section titled “Module Stability”Can I use Plumego in production right now?
Section titled “Can I use Plumego in production right now?”Yes — with one caveat per tier:
- Stable roots (
core,router,contract,middleware,security,store,health,log,metrics): production-ready today. Their APIs are frozen and will carry a formal compatibility promise at v1 release. - Beta extensions (
x/rest,x/websocket,x/gateway,x/observability,x/tenant,x/frontend,x/messaging): safe to adopt in production. APIs are frozen between minor release refs; check release notes before upgrading. Selected subpackages ofx/ai(provider,session,streaming,tool) andx/data(file,idempotency) also carry beta-surface stability at v1.1.0. - Experimental extensions (all other
x/*): APIs may change without a deprecation period. Wrap them behind an app-local interface before using in stable production paths.
See Stability for the four-tier overview and Releases for the full module-by-module support matrix.
What is the router’s performance overhead vs stdlib?
Section titled “What is the router’s performance overhead vs stdlib?”Measured against Go 1.22+ http.ServeMux with PathValue() parameter extraction (the closest stdlib equivalent):
| Route type | stdlib | plumego | Ratio |
|---|---|---|---|
| Static route | 110 ns | 367 ns | 3.3× |
Single :param + read | 186 ns | 807 ns | 4.3× |
Four :param deep path | 722 ns | 1,036 ns | 1.4× |
| 70-route mixed table | 247 ns | 660 ns | 2.7× |
The overhead covers per-request context injection (480 B, 5 allocs), parameter map construction, and middleware chain wiring. For typical handlers spending 1 ms–100 ms on I/O, router cost is 0.001%–0.1% of total request time. See the full benchmark table on Releases for all nine scenarios and methodology.
Where should I start?
Section titled “Where should I start?”Start from Docs Home if you still need help choosing the right first implementation page. From there, take one of these narrower paths:
- Complete the first-day path on Docs Home when you need the shortest command-and-verification route.
- Getting Started when the next question is how to run the smallest canonical path in detail.
- Reference App when the next question is what the canonical service shape looks like.
- Architecture when the next question is package ownership, stable roots, or
x/*boundaries.
That sequence is safer than scanning packages or concept pages at random.
How do I decide between a stable root and x/*?
Section titled “How do I decide between a stable root and x/*?”Use stable roots for long-lived transport and kernel primitives. Use x/* when the work is capability-specific, faster-moving, tenant-aware, protocol-specific, or likely to expand beyond the default learning path.
Does every package already carry a v1 stability promise?
Section titled “Does every package already carry a v1 stability promise?”No. Read Release Posture and the public Releases page before assuming long-term compatibility.
Why is there a reference app?
Section titled “Why is there a reference app?”reference/standard-service provides one canonical application path. It shows how Plumego expects bootstrap, wiring, routing, and handlers to stay visible in code, but it does not replace the repository-boundary explanation on Architecture.
Is the repository control plane part of the product?
Section titled “Is the repository control plane part of the product?”It is part of how Plumego is meant to be used and maintained. Docs, specs, and task cards help contributors and agents classify work consistently, which is especially important for a repository with stable roots and multiple extension families.
Can I use x/* experimental packages in production?
Section titled “Can I use x/* experimental packages in production?”You can, but you accept the risk of API changes without a deprecation period. The x/ai stability tier table in x/ai Primer is the current model for how experimental subpackages are classified. Before depending on an experimental package in a stable service path, check module.yaml for the package’s declared tier and consider wrapping the dependency behind an interface in your application so that upgrades remain local.
Can stable roots import from x/*?
Section titled “Can stable roots import from x/*?”No. This is a hard boundary. Stable roots (core, router, contract, middleware, security, store, health, log, metrics) must never depend on extension packages. Extension packages may depend on stable roots. Violating this boundary is a stop condition. Verify with the dependency boundary check: go run ./internal/checks/dependency-rules.
Advanced Patterns
Section titled “Advanced Patterns”How do I customize the error response format?
Section titled “How do I customize the error response format?”Use contract.NewErrorBuilder() to construct errors with the canonical type, message, and optional details. If your service needs a different wire format for errors, implement a custom contract.WriteError-equivalent in your application and keep it consistent across all handlers. Do not inline custom error formatting in individual handlers.
How do I do simple multi-tenancy without x/tenant?
Section titled “How do I do simple multi-tenancy without x/tenant?”If you only need to extract a tenant identifier from a header and pass it to storage queries, do it explicitly in your handler or a thin application middleware using r.Header.Get("X-Tenant-ID") and pass it through the call chain. Reach for x/tenant when you need enforced policy evaluation, per-tenant quota, JWT-backed session state, or tenant-aware store adapters at scale.
What is the difference between middleware and x/resilience?
Section titled “What is the difference between middleware and x/resilience?”middleware handles transport-layer concerns for inbound requests: access logging, request IDs, CORS, timeout enforcement, rate limiting of incoming traffic, and recovery. x/resilience handles outbound call protection: circuit breakers and rate limiters that wrap calls made from your service to external dependencies. If you are protecting an inbound route, start from middleware. If you are wrapping an outbound call to a database or upstream API, start from x/resilience.
Why does Plumego use explicit DI instead of a service locator?
Section titled “Why does Plumego use explicit DI instead of a service locator?”Explicit constructor injection keeps dependencies visible at the call site and in grep. Hidden service locators (global registries, context.Value as a service bag) make it impossible to know at a glance what a handler depends on. Plumego treats this as a first-class constraint: no hidden globals and no context-based service location.
How do I read path parameters in a handler?
Section titled “How do I read path parameters in a handler?”Use router.Param(r, "name") to read named path parameters set by the router:
import "github.com/spcent/plumego/router"
// Route registered as: app.Get("/users/:id", ...)func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) { id := router.Param(r, "id") if id == "" { _ = contract.WriteError(w, r, contract.NewErrorBuilder(). Type(contract.TypeRequired). Detail("param", "id"). Build()) return } // use id...}For wildcard routes registered with *name, the same router.Param(r, "name") call returns everything after the matched prefix.
How do I return paginated results?
Section titled “How do I return paginated results?”Pass pagination metadata in the meta argument of contract.WriteResponse:
func (h *Handler) ListItems(w http.ResponseWriter, r *http.Request) { page, _ := strconv.Atoi(r.URL.Query().Get("page")) if page < 1 { page = 1 } items, total := h.Repo.List(r.Context(), page, 20)
_ = contract.WriteResponse(w, r, http.StatusOK, items, map[string]any{ "total": total, "page": page, "limit": 20, })}The response envelope becomes:
{ "data": [...], "meta": { "total": 142, "page": 2, "limit": 20 }, "request_id": "req-abc-123"}How do I group routes under a common prefix?
Section titled “How do I group routes under a common prefix?”Use app.Group(prefix) to create a route group. All routes registered on the group inherit the prefix:
v1 := app.Group("/api/v1")v1.Get("/users", http.HandlerFunc(handler.ListUsers))v1.Post("/users", http.HandlerFunc(handler.CreateUser))v1.Get("/users/:id", http.HandlerFunc(handler.GetUser))v1.Delete("/users/:id", http.HandlerFunc(handler.DeleteUser))Groups only affect URL prefix — middleware must still be registered via app.Use.
How do I serve static files?
Section titled “How do I serve static files?”Use app.Router().Static(prefix, dir) to serve files from a directory:
app.Router().Static("/assets", "./public")// GET /assets/logo.png → ./public/logo.pngOr embed files with go:embed and use StaticFS:
//go:embed publicvar publicFS embed.FS
app.Router().StaticFS("/assets", http.FS(publicFS))How do I handle file uploads?
Section titled “How do I handle file uploads?”Use x/fileapi for file upload and download endpoints. For basic cases where you only need to read a multipart body, use the standard library directly:
func (h *Handler) Upload(w http.ResponseWriter, r *http.Request) { if err := r.ParseMultipartForm(10 << 20); err != nil { // 10 MB _ = contract.WriteError(w, r, contract.NewErrorBuilder(). Type(contract.TypeInvalidFormat). Message("invalid multipart form"). Build()) return } file, _, err := r.FormFile("file") if err != nil { _ = contract.WriteError(w, r, contract.NewErrorBuilder(). Type(contract.TypeRequired). Detail("field", "file"). Build()) return } defer file.Close() // process file...}Reach for x/fileapi when you need signed URL generation, chunked uploads, download resumption, or storage abstraction across backends.
How do I set response headers?
Section titled “How do I set response headers?”Set headers on the http.ResponseWriter before calling WriteResponse or WriteError:
w.Header().Set("X-Custom-Header", "value")w.Header().Set("Cache-Control", "max-age=300, public")_ = contract.WriteResponse(w, r, http.StatusOK, data, nil)For security headers (Content-Security-Policy, X-Frame-Options, HSTS), use middleware/security so the policy applies consistently across all routes.
Reference
Section titled “Reference”| Question | Page |
|---|---|
Exact function signatures for core, contract, router | API Quick Reference |
| All error types with HTTP status and remediation | Error Reference |
| What does the reference service look like? | Reference App |
| Which APIs are stable vs experimental? | Release Posture |
| When should I NOT use Plumego? | When Not to Use |