Skip to main content

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

  1. A request enters the gin engine and passes through middleware in the same order NestJS used: CLSSecHeadersCORSLoggingErrorsGlobalThrottle.
  2. CLS (Continuation Local Storage) creates a per-request context holding userId, selfId, organizationId, lineOaId, lineOaHash, roleId, isSuperAdmin, and lang. The JWT guard populates these values, and every service reads them from here to scope queries by tenant.
  3. 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.
    • 401APP_001
    • 500APP_000
    • array-shaped validation errors → APP_006
    • messages present in the ErrorResponse table → that message's key
    • anything else → APP_CUSTOM
  4. Throttle applies both a global rate limit and per-endpoint limits (reset-password, for example, allows 3 calls per 10 seconds).
  5. All routes are grouped under the /api prefix and split into two groups.
    • public — bypasses the JWT guard (the equivalent of NestJS @Public())
    • authed — protected by the global JWT guard
  6. Unmatched paths return 404 with an Express-style message in the form Cannot <METHOD> <url>.
  7. Successful responses go through the helpers in httpx/success.go so that key ordering and localized messages match the original service byte for byte.

Key Files & Functions

FileResponsibility
cmd/api/main.goBootstrap: load config, build deps, construct the auth module, assemble the router, start the scheduler, handle graceful shutdown
cmd/api/modules.goRegistry of every featureModules entry (RegisterRoutes) plus registerSchedulers
internal/server/router.goNew() assembles the gin engine, middleware chain, the /api group, and the public/authed groups
internal/core/middleware/cls.goCreates the per-request CLS context
internal/core/middleware/errors.goException filter that turns errors into the JSON error envelope
internal/core/middleware/throttle.goGlobalThrottle() and per-route Throttle(limit, ttlMs)
internal/core/middleware/cors.go, secheaders.go, logging.goCORS, security headers, and access logging
internal/core/httpx/codes.goThe ErrorResponse table mapping error codes to Thai/English messages
internal/core/httpx/apperror.goAppError plus the NotFound, BadRequest, Unauthorized, Forbidden, and InternalServerError constructors
internal/core/httpx/success.go, respond.goSuccess response shape and AbortWithError
internal/core/httpx/jstime.goJSTime, which reproduces JavaScript Date serialization byte for byte
internal/core/pagination/pagination.goThe {data, total} and {data, total, page, limit, totalPages} shapes
internal/core/validation/validation.goDTO validation modelled on class-validator (array errors become APP_006)
internal/core/clsctx/clsctx.goCLS getters and setters such as SelfID, OrganizationID, LineOaID, RoleID, IsSuperAdmin
internal/core/logger/logger.goThe 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 containerinternal/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.