Skip to content

Developer Guide

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

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.

Open internal/api/server.go for the server setup. The actual route registration happens in two stages:

  1. internal/api/server_init.gosetupRoutes() calls registerRoutesViaRegistry() which delegates to routes.RegisterAllRoutes()
  2. 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 with Auth: AuthNone and Public: false)

The global middleware chain runs before any route-specific middleware:

  1. internal/middleware/cors.go — CORS header handling
  2. internal/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:

  1. internal/middleware/tenant.go — Tenant resolution: X-FB-Tenant header → JWT claims → default tenant. Sets tenant_id, tenant_slug, and the merged tenant_config in fiber locals
  2. internal/middleware/tenant_db.go — Database pool resolution. GetPoolForSchema() implements the branch > tenant > main priority chain
  3. internal/middleware/branch.go — Branch context from X-Fluxbase-Branch header

⑤ Auth Middleware — Who is making the request

Section titled “⑤ Auth Middleware — Who is making the request”

With tenant context established, authentication validates the user:

  1. internal/middleware/auth.go — JWT validation, role extraction, RequireAuth / RequireServiceKey. This is where the Authorization header is parsed and claims land in fiber locals
  2. internal/auth/jwt.go — How tokens are created, what claims they carry (TokenClaims struct), 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):

  1. internal/api/rest_crud.go — The CRUD handler. See how SELECT, INSERT, UPDATE, DELETE are built from URL path and query params. Handles all /api/v1/tables/{table} requests
  2. internal/api/query_parser.go — URL query parsing: ?select=, ?order=, ?col.eq= become structured filter conditions
  3. internal/api/query_builder.go — Filters → SQL WHERE, ORDER BY, LIMIT
  4. internal/database/connection.go — The connection pool, transaction helpers, and the sync.RWMutex safety pattern
  5. internal/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:

  1. internal/tenantdb/manager.go — The Manager struct. Read CreateTenantDatabase() to see the full provisioning flow: database creation → bootstrap → FDW setup → declarative schema
  2. internal/tenantdb/fdw.go — Foreign Data Wrapper setup: postgres_fdw configuration, per-tenant roles with NOBYPASSRLS, shared schema imports
  3. internal/tenantdb/router.go — Per-tenant connection pool cache with LRU eviction
  4. internal/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.

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, settings
  • auth.sql — Users, sessions, identities, OTP codes, client keys
  • bootstrap.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:

  1. internal/api/routes/functions.go — Route definitions and middleware
  2. internal/api/function_handler.go — HTTP handlers for CRUD and invocation
  3. internal/functions/handler.go — Proxying to the Deno runtime
  4. internal/functions/loader.go — Loading functions from disk at startup
  5. internal/functions/storage.go — Database storage for function metadata
  6. internal/runtime/runtime.go — The Deno runtime wrapper

If the feature should be tenant-scoped:

  • Add tenant_id to the database table
  • Create RLS policies for tenant_service role
  • Ensure the middleware chain includes TenantMiddleware for the route group
  • Use middleware.GetTenantID(c) in handlers to get the current tenant

Add tests alongside the source file:

func TestGetMyResource_Found_ReturnsResource(t *testing.T) {
// Test implementation
}
  • 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.md if it changes configuration or architecture