ProjectBook Backend is a production-grade modular Go API with strict layering and policy-validated routing.
This document is the canonical architecture reference for this repository.
Enforced data flow:
Handler -> Service -> Repository -> Store -> Backend
Mandatory boundaries:
- Handlers own HTTP transport only.
- Services own business orchestration and write transaction boundaries.
- Repositories own query logic and persistence mapping.
- Stores own execution semantics only.
Do not bypass this flow.
Entrypoint:
cmd/api/main.go
Core runtime composition:
internal/core/app
Module registration surface:
internal/modules/modules.go
Current runtime module order:
authhomeprojectartifactsresourcespagescalendarsidebaractivityteamhealthsystem
The API process performs fail-fast startup in this order:
- Load env configuration (
config.Load). - Run config lint (
Config.Lint) including feature dependency checks. - Initialize logging and app shell.
- Initialize dependencies in
initDependencies:- Postgres pool and relational store
- Redis client
- metrics service
- auth mode and goAuth engine
- rate limiter
- cache manager
- permissions resolver + permissions lifecycle startup sync
- Mongo client + database + bootstrap + Mongo document store
- tracing service
- Bind dependencies into modules.
- Register routes from all modules.
- Start HTTP server.
Startup constraints currently enforced:
- Postgres must be enabled.
- Redis must be enabled.
- Mongo must be enabled.
- Auth requires Redis + Postgres.
- Cache/rate-limit require Redis.
- Permissions require Postgres.
Global middleware is assembled in internal/core/httpx/globalmiddleware.go.
Execution order (outermost to innermost):
- request id
- client ip
- recoverer
- CORS
- security headers
- max body bytes
- request timeout
- tracing
- access log
- router dispatch
This order is intentional for diagnostics, safety, and policy correctness.
CORS origin policy notes:
- Browser origins are controlled via
allowedOrigins(with legacyHTTP_MIDDLEWARE_CORS_ALLOW_ORIGINSalias support). denyOriginsis optional and evaluated before the allow-list.- Localhost origins are allowed by default for development unless explicitly denied.
Policies are route-scoped middleware decorators under internal/core/policy.
Each route is validated at registration with policy metadata rules.
Required protected-route stage order:
AuthRequiredProjectRequired/ProjectMatchFromPathResolvePermissions- RBAC (
RequirePermission,RequireAnyPermission,RequireAllPermissions) RateLimit- cache read/invalidate
- cache-control
Validator-enforced safety rules include:
- auth is required for project/resolver/RBAC policies
- resolver is required before RBAC checks
- project path routes must include project policies
- authenticated cache reads must vary by user or project identity
Every module follows:
dto.go: transport contracts and validationhandler.go: request extraction and response shapingservice.go: business workflows and transaction orchestrationrepo.go: persistence operations and mappingsroutes.go: route + policy registrationmodule.go: module constructor and dependency binding
Dependency access uses modulekit.Runtime surfaces:
- auth engine + mode
- relational store
- document store
- cache manager
- limiter
- permissions resolver
- shared dependencies
- Backend: Postgres
- Store contract:
storage.RelationalStore - Execution entrypoint:
store.Execute(...) - Transaction entrypoint:
store.WithTx(...)
- Backend: MongoDB
- Store contract:
storage.DocumentStore - Used by hybrid modules for rich document payloads and revisions
Current module backend families:
- Relational only:
auth,home,project,calendar,activity,team,system,health - Relational + document:
artifacts,resources,pages,sidebar
Write paths are service-owned transactions.
Pattern:
- Service validates request and current state.
- Service opens
store.WithTx(ctx, fn). - Service calls repository methods inside
fn. - Repository executes relational/document operations through store APIs.
- Store commits or rolls back.
Read paths do not require forced transaction wrapping.
- Engine: goAuth
- Integration location:
internal/core/auth - Provider bridge: store-backed
StoreUserProvider - User persistence:
UserRepositoryover relational store - System session-context route issues a backend-signed permission-context token for frontend server-side permission hydration (
internal/modules/system/routes.go)
Auth mode surface:
jwt_onlyhybrid(default)strict
Authorization is not delegated to goAuth.
ProjectBook authorization model:
- request-scoped project isolation (
ProjectRequired,ProjectMatchFromPath) - permission resolution (
ResolvePermissions) - RBAC bitmask checks (
RequirePermissionfamily)
Cache is Redis-backed and route-opt-in.
Core components:
- manager:
internal/core/cache/manager.go - policies:
CacheRead,CacheInvalidate,CacheControl - optional wrappers:
CacheReadOptional,CacheInvalidateOptional,CacheControlOptional
Design model:
- key isolation via
VaryBy - freshness/invalidation via
TagSpecsversion bumping
Rate limiting is Redis-backed per-route policy.
Core policy:
RateLimitRateLimitWithKeyer
Typical scopes used in this API:
- IP (public auth endpoints)
- User (account-scoped operations)
- Project (project mutation routes)
- user/project/token-hash fallback (selected protected routes)
On startup (when enabled), permissions lifecycle performs consistency tasks:
- role permission seed/resync
- project member permission mask resync for non-custom memberships
- resolver cache invalidation for impacted users/projects
This keeps RBAC enforcement deterministic across route checks.
Health routes:
GET /healthz: livenessGET /readyz: dependency readiness report
Metrics:
- request/route instrumentation
- cache and rate-limit outcome metrics
Tracing:
- OpenTelemetry lifecycle via core tracing service
Readiness probes are registered per dependency during startup wiring.
- Startup is fail-fast for invalid config and dependency wiring failures.
- Policy misconfiguration fails route registration immediately.
- Runtime dependency errors are mapped to explicit API errors (for example dependency unavailable).
When changing architecture-sensitive code:
- keep module flow
handler -> service -> repository -> store - keep write transaction boundaries in services only
- do not expose db-driver/query objects in service contracts
- do not bypass policy validation
- do not bypass auth/project/resolver/RBAC order on protected routes
- avoid global mutable state