Architecture
Understanding the startup sequence and where responsibilities live saves time when you add endpoints, tables, or whole new modules.
High-level startup sequence
At a high level, cmd/main.go does the following:
config.Init()— builds aconfig.Configvalue. Today the important field isDatabase(*gorm.DB), created using environment variables from.env.providers.NewApp(cfg)— constructs theAppstruct:App.DB— the same*gorm.DBinstance.App.Webserver— agin.Default()engine (middleware and routes attach here).App.Injector— asamber/doinjector with the database registered under a named key (pkg/constants) so constructors can resolve dependencies.
- CLI branch — if
os.Argscontains migration/seed/script flags,script.Commands(app)runs and may exit before the HTTP server starts. - HTTP middleware — e.g. CORS applied to
App.Webserver. RegisterModulechain — each feature module registers providers and routes:user.RegisterModule(app)first (providesUserRepositoryand user HTTP).auth.RegisterModule(app)second (depends on user repository binding for login/register flows).
run(app)— binds and listens (Runon Gin).
This mirrors larger backends where a central App object carries shared infrastructure while modules stay decoupled.
The App struct (mental model)
Think of App as the composition root:
| Field | Role |
|---|---|
DB | Shared database handle for scripts and anything that bypasses DI. |
Webserver | Gin engine — routes and global middleware. |
Injector | DI container for module wiring (controllers still receive *do.Injector where constructors need DB by name). |
Keeping both DB and Injector avoids forcing every line of legacy code through DI while still allowing incremental adoption.
Request path (happy path)
For an authenticated JSON request:
- Gin matches route + method; global middleware (CORS, recovery, etc.) runs.
- Route group may attach
Authenticate(jwtService)— validatesAuthorization: Bearer, setsuser_idon context. - Controller (under
internal/presentation/controllers) binds JSON to DTO structs, calls validation helpers, invokes service methods. - Service (under
internal/app/service) encodes business rules: hashing, token issuance, orchestrating repositories. - Repository interface (under
domain/repositories) describes persistence operations. - PostgreSQL implementation (under
internal/infrastructure/postgresql) executes GORM queries.
Controllers should stay thin — if you notice SQL or business rules creeping in, move them down a layer.
Why RegisterModule order matters
The auth module reuses the user repository interface to look up emails, persist users on register, etc. That repository is registered inside user.RegisterModule. If you reversed the order, auth’s provider wiring could run before UserRepository exists in the injector, causing runtime panics when resolving dependencies.
Rule of thumb: register foundational modules (users, billing accounts, tenants) before modules that depend on them.
internal/ and cross-module imports
Go enforces that internal packages are only visible to siblings under the same subtree. Practical consequences in this starter:
modules/user/dtoandmodules/user/domain/repositorieslive outsideinternalsomodules/authcan import user contracts.modules/auth/jwtlives outsideinternalsomiddlewarescan import the JWT service without violating the rule.
If you introduce a new cross‑cutting concern (for example a pkg/observability tracer), keep it either in pkg/ or behind small interfaces in providers/ — not deep inside one module’s internal tree.
Error handling and responses
Controllers typically build JSON with helpers from pkg/utils (response wrappers). Keep status codes consistent with your API style guide — the starter demonstrates common patterns for success and validation errors.
Where to extend
| Need | Likely touch points |
|---|---|
| New authenticated resource | New module with RegisterModule, new migration, new routes under /api/.... |
New public field on User | Entity + migration + DTO + repository methods + validation. |
| Stricter JWT | modules/auth/jwt claims, expiry constants, middleware checks. |
The next page, Project structure, maps these concepts to folders and files you will actually open in your editor.