HTTP Pipeline¶
Purpose¶
The HTTP pipeline is the boundary between the outside world and the core runtime. It translates HTTP requests into typed Go function calls, applies cross-cutting concerns (auth, logging, recovery), and translates return values back into HTTP responses.
EERP uses Echo v4 as the HTTP framework. Middleware is composed at startup and applied to route groups rather than individual handlers.
Responsibilities¶
- Accept and parse incoming HTTP requests
- Execute middleware in a defined order
- Route requests to the correct ORM-generated or auth handler
- Serialize responses to JSON
- Return uniform error envelopes for auth failures
- Provide observability (request ID, structured access logs)
Middleware Chain¶
flowchart LR
Request -->|1| RequestID["Request ID\n(inject trace ID)"]
RequestID -->|2| Logger["Zap Access Logger\n(method, path, status, duration)"]
Logger -->|3| Recover["Panic Recovery\n(500 + log stack trace)"]
Recover -->|4| CORS["CORS Headers"]
CORS -->|public routes| AuthHandler["Auth Handler\n(login / refresh / logout)"]
CORS -->|protected routes| JWT["JWT Middleware\n(validate token, inject Identity)"]
JWT -->|5| Permissions["Permission Middleware\n(derive + check permission)"]
Permissions -->|6| Handler["ORM-generated CRUD Handler"] The first four middleware (RequestID, Logger, Recover, CORS) are applied globally to all routes. The JWT and Permission middleware are applied only to the protected group.
Route Groups¶
Routes are divided into two groups mounted under /api/v1:
Public auth group¶
Mounted at /api/v1/auth with no authentication middleware:
| Method | Path | Purpose |
|---|---|---|
POST | /api/v1/auth/login | Validate credentials, issue access + refresh tokens |
POST | /api/v1/auth/refresh | Exchange a valid refresh token for a new pair |
POST | /api/v1/auth/logout | Revoke all refresh tokens for the user, clear cookie |
Protected API group¶
All other /api/v1/* routes are mounted with JWT middleware → Permission middleware applied as Echo group middleware. Every registered ORM table gets CRUD routes in this group — see Automatic Router.
Lifecycle of a Request¶
sequenceDiagram
participant Client
participant Echo as Echo Router
participant MW as Global Middleware
participant JWT as JWT Middleware
participant Perm as Permission Middleware
participant Handler
participant DB as PostgreSQL
Client->>Echo: HTTP Request
Echo->>MW: Route matched → run middleware chain
MW->>MW: Inject request ID into context
MW->>MW: Log request start (zap)
MW->>MW: Recover from panics
MW->>MW: Set CORS headers
MW->>JWT: Validate Authorization header
JWT-->>MW: Identity injected into context
JWT->>Perm: Pass to permission check
Perm->>Perm: Derive permission from method + path
Perm->>Perm: Check PermissionRepository (DB + cache)
Perm->>Handler: Permission granted
Handler->>DB: ORM query
DB-->>Handler: Data
Handler-->>Client: 200 JSON
MW->>MW: Log request end (status, duration) Error Envelope¶
All authentication and authorisation errors follow a uniform JSON envelope:
{
"error": {
"code": "UNAUTHENTICATED",
"message": "missing or invalid token",
"request_id": "01J..."
}
}
code value | HTTP status | Condition |
|---|---|---|
UNAUTHENTICATED | 401 | Missing token, invalid signature, expired token |
FORBIDDEN | 403 | Valid identity but insufficient permissions |
The request_id field is the value injected by the RequestID middleware, allowing correlation with access logs.
Response envelope
The error envelope above is used for auth errors. Successful ORM CRUD responses return the entity or list directly as JSON — there is no outer {data, meta} wrapper.
Request Context Keys¶
Each middleware enriches the Echo context passed down the chain:
| Key | Added by | Contents |
|---|---|---|
| Request ID | RequestID middleware | UUID for this request (also set as X-Request-ID response header) |
| Identity | JWT middleware | Authenticated auth.Identity{UserID, TenantID, Roles} |
Handlers retrieve the identity via typed accessors (see Authentication):
identity, ok := auth.IdentityFromContext(c.Request().Context())
// or, inside a guaranteed-protected handler:
identity := auth.MustIdentity(c.Request().Context())
Interactions¶
graph LR
Config["eerp-config.json\n(master_key)"] --> JWT["JWT Middleware"]
JWT -->|injects| Context["Echo context\n(Identity)"]
Context --> PermMW["Permission Middleware"]
PermMW -->|checks| PermRepo["PermissionRepository"]
PermRepo -->|queries| DB["PostgreSQL"]
ORMRegistry["ORM Registry"] --> Router["Auto-Router\n(server.RegisterRoutes)"]
Router -->|mounts| ProtectedGroup["Protected /api/v1/* group"]
AuthRoutes["Auth Handlers"] -->|mounted separately| PublicGroup["Public /api/v1/auth group"] See also: Authentication, Permissions, Automatic Router.
Extension Points¶
| Extension | How |
|---|---|
| Rate limiting | Add a rate-limit middleware before JWT in the protected group |
| Custom middleware | Use e.Use() for global or g.Use() for group-scoped Echo middleware |
| WebSocket support | Register a dedicated WS handler on a separate Echo group before the protected group |
| Request tracing | Enrich the RequestID middleware to propagate W3C Trace Context headers |