Stage 7 · Master
Identity Service: HTTP API Layer
Routing, Route Groups, and the stdlib Baseline
Meridian uses net/http today, not Gin. This lesson teaches the routing concepts every backend needs, shows how the current repository applies them with http.NewServeMux, and explains where Gin could become a future upgrade rather than a fictional present-tense dependency.
What Routing Really Does
Routing is the process that turns an incoming HTTP request into a specific chain of code: match a method and path, attach the right middleware, extract any path state, and hand the request to exactly one handler. Good routing makes the resource model legible. Poor routing hides scope, duplicates policy, and forces every handler to rediscover context that the path already knew.
The term route group is conceptually broader than any one library. A route group is simply a set of routes that share a path prefix, a middleware chain, or both. Whether the implementation uses net/http, Gin, chi, or another router is a secondary question. The underlying design problem is always the same: where does shared request policy attach, and how does the URL express resource containment?
The Real Meridian Baseline
The current repository uses the standard library only. apps/gateway/internal/router/router.go builds an http.NewServeMux, registers GET /healthz, and returns an http.Handler. apps/identity-service/cmd/identity-service/main.go does the same. No go.mod file includes Gin, and no service has path parameters or grouped middleware yet. That is the ground truth this chapter must respect.
No service in meridian imports Gin today. This lesson teaches routing concepts using the real stdlib net/http baseline; Gin is discussed only as a possible future upgrade path, not as already-implemented code.
Route Groups as a Concept
Even with stdlib net/http, the route tree should still be designed in groups. For identity-service, public auth endpoints form one group. Authenticated membership endpoints form another. Tenant-scoped administration endpoints form a third. The code can implement those groups with helper functions, nested muxes, prefix dispatch, or a richer router later. What matters first is the structure of the tree and the middleware boundaries it implies.
| Group | Typical prefix | Shared middleware | Why the grouping exists |
|---|---|---|---|
| Public health/auth | /healthz, /v1/auth/* | Panic recovery, request logging | Callers may be unauthenticated |
| Authenticated identity reads | /v1/users/me, /v1/memberships/* | JWT verification | Caller identity must exist before the handler runs |
| Tenant-scoped administration | /v1/tenants/{tenantID}/* | JWT verification + tenant scope + permission checks | The path now carries the tenant boundary explicitly |
The most common routing mistake in multi-tenant systems is flattening everything into short URLs and hoping headers or body fields carry the real scope. A better design makes important boundaries obvious in the route tree. Once the path contains /v1/tenants/{tenantID}/memberships, a reviewer can immediately ask whether the tenant in the path matches the tenant in the authenticated actor context. Routing structure should invite that question, not obscure it.
Designing the Identity Route Tree
identity-service owns tenants, users, memberships, roles, and permissions. Its future HTTP surface should therefore separate identity-establishing routes from tenant-administering routes. The first set answers 'who is the caller?' The second set answers 'what may the caller do inside this tenant?' Combining both concerns into one flat handler list leads to duplicated checks and weak mental models.
When Gin Would Earn Its Place
A router library earns its place when the route tree becomes rich enough that the standard library starts fighting readability. Named path parameters, nested groups, shared middleware chains, and request helpers can all make a router worthwhile. But introducing one should solve an actual codebase problem, not retroactively decorate a small service. Meridian is not there yet. It has /healthz routes, one recovery middleware in the gateway, and empty domain packages. The correct present-tense lesson is the stdlib baseline, not a speculative framework migration.
apps/gateway/cmd/gateway/main.gomodifyResponsibility: Continues to wire config, logging, middleware, and the top-level router into one http.Server.
Why now: Process startup should remain boring even as the route tree grows.
apps/gateway/internal/router/router.gomodifyResponsibility: Will expand from /healthz-only routing into public API route registration and reverse-proxy mounting.
Why now: The gateway is the natural place to make the public route tree explicit.
apps/identity-service/internal/http/router.gocreateResponsibility: Would hold identity-service route registration once business endpoints are added.
Why now: Keeping route registration out of cmd/identity-service/main.go preserves the same separation the gateway already has.
- Treat route groups as a design concept first and a library feature second.
- Use the real net/http baseline when describing meridian today.
- Design the route tree so tenant and authorization boundaries become visible in the path and middleware ordering.
- Adopt Gin only if route complexity genuinely justifies it later.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.