Developer Guide
Code Tour
Section titled “Code Tour”The best way to understand Fluxbase is to follow a request through the system. The diagram below shows the path a typical API request takes. Below it, each numbered step lists the files you should open and read to understand what happens at that stage.
graph TD REQ["① HTTP Request"] --> ROUTER["② Fiber Router<br/>api/server.go"] ROUTER --> CORS["③ CORS Middleware"] CORS --> RATE["③ Rate Limiter"] RATE --> TENANT["④ Tenant Middleware<br/>Resolve tenant context"] TENANT --> TENANTDB["④ Tenant DB Middleware<br/>Resolve database pool"] TENANTDB --> AUTH["⑤ Auth Middleware<br/>JWT validation, role extraction"] AUTH --> HANDLER["⑥ Handler<br/>internal/api/*.go"] HANDLER --> SERVICE["⑦ Service Layer<br/>internal/{module}/service.go"] SERVICE --> DB["⑧ PostgreSQL<br/>pgx/v5 pool"] DB --> SERVICE SERVICE --> HANDLER HANDLER --> RESP["⑨ JSON Response"]
style REQ fill:#e1f5fe style RESP fill:#e8f5e9 style DB fill:#fff3e0① Entry Point — How the server starts
Section titled “① Entry Point — How the server starts”Before any request arrives, the server boots up and wires everything together. Read these files to understand what happens at startup:
Open cmd/fluxbase/main.go and follow the main() function. You’ll see config loading, database pool creation, service initialization, and the API server start — this is where every subsystem is wired together. Next, open internal/config/config.go and scan the Config struct. The field names map directly to YAML keys and FLUXBASE_* env vars, so this file is a complete map of everything that’s configurable. For the defaults, flip through internal/config/config_defaults.go.
② Routing — Where requests land
Section titled “② Routing — Where requests land”Open internal/api/server.go for the server setup. The actual route registration happens in two stages:
internal/api/server_init.go—setupRoutes()callsregisterRoutesViaRegistry()which delegates toroutes.RegisterAllRoutes()internal/api/routes/registry.go— The centralized route registry. Every route is registered with explicit auth requirements (AuthRequirement), and middleware is auto-injected based on those declarations. The registry also validates consistency (e.g., no route withAuth: AuthNoneandPublic: false)
③ Middleware — CORS and rate limiting
Section titled “③ Middleware — CORS and rate limiting”The global middleware chain runs before any route-specific middleware:
internal/middleware/cors.go— CORS header handlinginternal/middleware/ratelimit.go— Rate limiting per route
④ Tenant & Branch Context — Which database to talk to
Section titled “④ Tenant & Branch Context — Which database to talk to”Tenant middleware runs before auth so that tenant context is available for authentication (e.g., per-tenant JWT secrets). Open these in order:
internal/middleware/tenant.go— Tenant resolution:X-FB-Tenantheader → JWT claims → default tenant. Setstenant_id,tenant_slug, and the mergedtenant_configin fiber localsinternal/middleware/tenant_db.go— Database pool resolution.GetPoolForSchema()implements the branch > tenant > main priority chaininternal/middleware/branch.go— Branch context fromX-Fluxbase-Branchheader
⑤ Auth Middleware — Who is making the request
Section titled “⑤ Auth Middleware — Who is making the request”With tenant context established, authentication validates the user:
internal/middleware/auth.go— JWT validation, role extraction,RequireAuth/RequireServiceKey. This is where theAuthorizationheader is parsed and claims land in fiber localsinternal/auth/jwt.go— How tokens are created, what claims they carry (TokenClaimsstruct), and how roles map to PostgreSQL roles
⑥ ⑦ ⑧ Handler → Service → Database — The actual work
Section titled “⑥ ⑦ ⑧ Handler → Service → Database — The actual work”This is the three-layer architecture every feature follows. Trace it with the REST CRUD (the most-used feature):
internal/api/rest_crud.go— The CRUD handler. See howSELECT,INSERT,UPDATE,DELETEare built from URL path and query params. Handles all/api/v1/tables/{table}requestsinternal/api/query_parser.go— URL query parsing:?select=,?order=,?col.eq=become structured filter conditionsinternal/api/query_builder.go— Filters → SQLWHERE,ORDER BY,LIMITinternal/database/connection.go— The connection pool, transaction helpers, and thesync.RWMutexsafety patterninternal/database/schema.go— Schema introspection: how Fluxbase discovers tables, columns, types, and relationships to power the auto-generated API
Digging deeper — Multi-tenancy internals
Section titled “Digging deeper — Multi-tenancy internals”Once you’ve traced a request, the multi-tenancy subsystem is the most complex piece worth understanding:
internal/tenantdb/manager.go— TheManagerstruct. ReadCreateTenantDatabase()to see the full provisioning flow: database creation → bootstrap → FDW setup → declarative schemainternal/tenantdb/fdw.go— Foreign Data Wrapper setup:postgres_fdwconfiguration, per-tenant roles withNOBYPASSRLS, shared schema importsinternal/tenantdb/router.go— Per-tenant connection pool cache with LRU evictioninternal/config/tenant_loader.go+internal/config/tenant_merge.go— Per-tenant config override loading and deep-merge logic
For the full multi-tenancy architecture (database-per-tenant, FDW, connection routing, RLS roles), see the Multi-Tenancy Guide.
Database schemas — The foundation
Section titled “Database schemas — The foundation”Internal schemas are managed declaratively — the SQL files show the current state, not a migration history. When the server starts, internal/migrations/declarative.go diffs the current database state against the desired schema files using pgschema and applies only the changes needed. This means you can read any schema file and see exactly what the tables, RLS policies, and grants look like right now — no git archaeology required. This is especially valuable for security reviews.
The SQL files live in internal/database/schema/schemas/. Start with:
platform.sql— Tenants, service keys, users, memberships, settingsauth.sql— Users, sessions, identities, OTP codes, client keysbootstrap.sql— The SQL that runs on every startup (schemas, extensions, roles, privileges)
Tracing a complete feature — Edge Functions
Section titled “Tracing a complete feature — Edge Functions”To see how a full feature fits together end-to-end, trace the edge functions system:
internal/api/routes/functions.go— Route definitions and middlewareinternal/api/function_handler.go— HTTP handlers for CRUD and invocationinternal/functions/handler.go— Proxying to the Deno runtimeinternal/functions/loader.go— Loading functions from disk at startupinternal/functions/storage.go— Database storage for function metadatainternal/runtime/runtime.go— The Deno runtime wrapper
Tests — How to test
Section titled “Tests — How to test”internal/api/rest_crud_test.go— Table-driven unit tests with mock dependenciestest/e2e/— E2E tests against a real databaseinternal/testutil/— Shared helpers, mocks, and assertions
How to Add a New Feature
Section titled “How to Add a New Feature”Tenant Scoping
Section titled “Tenant Scoping”If the feature should be tenant-scoped:
- Add
tenant_idto the database table - Create RLS policies for
tenant_servicerole - Ensure the middleware chain includes
TenantMiddlewarefor the route group - Use
middleware.GetTenantID(c)in handlers to get the current tenant
Write Tests
Section titled “Write Tests”Add tests alongside the source file:
func TestGetMyResource_Found_ReturnsResource(t *testing.T) { // Test implementation}Update Documentation
Section titled “Update Documentation”- Add API endpoint to
docs/src/content/docs/api/http/index.md - Add guide to
docs/src/content/docs/guides/if it’s a new feature - Update
CLAUDE.mdif it changes configuration or architecture
Related Documentation
Section titled “Related Documentation”- Multi-Tenancy - Multi-tenancy architecture and configuration
- Row Level Security - RLS implementation details
- Configuration - Complete configuration reference
- HTTP API - HTTP API endpoint reference