Core HTTP & Middleware Pipeline
Overview
The core layer of cms-api-go defines two things every module in the service depends on: the shape of every response and the middleware order of every request.
Feature modules never write JSON responses themselves. They go through shared helpers in
internal/core/httpx, which is why all 280-plus endpoints share one envelope, one set of
error codes (APP_xxx), and automatic bilingual (Thai/English) messages.
The service is a method-for-method port of the original NestJS application, so it deliberately preserves the legacy behaviour in full — including a few quirks inherited from the TypeScript code that carry in-code comments telling maintainers not to "fix" them, because cms-web relies on the existing response shapes.
Business Flow
- A request enters the gin engine and passes through middleware in the same order NestJS used:
CLS→SecHeaders→CORS→Logging→Errors→GlobalThrottle. - CLS (Continuation Local Storage) creates a per-request context holding
userId,selfId,organizationId,lineOaId,lineOaHash,roleId,isSuperAdmin, andlang. The JWT guard populates these values, and every service reads them from here to scope queries by tenant. - The Errors middleware plays the role of the NestJS
HttpExceptionFilter: it converts every error into the envelope{"code": "...", "message": "...", "data": ...}, mapping status to code as follows.401→APP_001500→APP_000- array-shaped validation errors →
APP_006 - messages present in the
ErrorResponsetable → that message's key - anything else →
APP_CUSTOM
- Throttle applies both a global rate limit and per-endpoint limits (reset-password, for example, allows 3 calls per 10 seconds).
- All routes are grouped under the
/apiprefix and split into two groups.public— bypasses the JWT guard (the equivalent of NestJS@Public())authed— protected by the global JWT guard
- Unmatched paths return 404 with an Express-style message in the form
Cannot <METHOD> <url>. - Successful responses go through the helpers in
httpx/success.goso that key ordering and localized messages match the original service byte for byte.
Key Files & Functions
| File | Responsibility |
|---|---|
cmd/api/main.go | Bootstrap: load config, build deps, construct the auth module, assemble the router, start the scheduler, handle graceful shutdown |
cmd/api/modules.go | Registry of every featureModules entry (RegisterRoutes) plus registerSchedulers |
internal/server/router.go | New() assembles the gin engine, middleware chain, the /api group, and the public/authed groups |
internal/core/middleware/cls.go | Creates the per-request CLS context |
internal/core/middleware/errors.go | Exception filter that turns errors into the JSON error envelope |
internal/core/middleware/throttle.go | GlobalThrottle() and per-route Throttle(limit, ttlMs) |
internal/core/middleware/cors.go, secheaders.go, logging.go | CORS, security headers, and access logging |
internal/core/httpx/codes.go | The ErrorResponse table mapping error codes to Thai/English messages |
internal/core/httpx/apperror.go | AppError plus the NotFound, BadRequest, Unauthorized, Forbidden, and InternalServerError constructors |
internal/core/httpx/success.go, respond.go | Success response shape and AbortWithError |
internal/core/httpx/jstime.go | JSTime, which reproduces JavaScript Date serialization byte for byte |
internal/core/pagination/pagination.go | The {data, total} and {data, total, page, limit, totalPages} shapes |
internal/core/validation/validation.go | DTO validation modelled on class-validator (array errors become APP_006) |
internal/core/clsctx/clsctx.go | CLS getters and setters such as SelfID, OrganizationID, LineOaID, RoleID, IsSuperAdmin |
internal/core/logger/logger.go | The structured logger shared by every service |
Health check: GET /api and GET /api/ return 200 with the body Hello World! and a
Content-Type of text/html; charset=utf-8.
Connections to Other Services
- Permission — none required. This is the bottom layer that every feature builds on.
- Dependency container —
internal/app/deps.go(Deps) collects the dependencies injected into every module: Postgres (GORM), Redis, the RabbitMQ publisher, Storage (S3/MinIO), the LINE API client, Redirects, Mailer, CSVEngine (a pure-Go DuckDB replacement), BigQuery, and the Scheduler. - Boot behaviour — a Postgres failure is fatal (10 retries, 3 seconds apart, then exit), while Redis and RabbitMQ degrade silently without blocking startup.