Skip to content

Contract-First Design

FORGE takes a contract-first approach to application generation. Every application produced by FORGE adheres to strict contracts that define API endpoints, database schema, permissions, and frontend pages. These contracts act as the specification layer between backends and frontends, ensuring that any supported backend can power any supported frontend without modification.

The contract defines what must exist. The implementation defines how it works.

Why Contracts?

When generating full-stack applications across multiple backend frameworks (Rust, Laravel, FastAPI, Node.js) and multiple frontend frameworks (Next.js, Nuxt.js, Angular), consistency is non-negotiable. Without contracts, each backend would implement slightly different API shapes, each frontend would expect different response formats, and swapping one technology for another would require rewriting integration code.

TIP

Think of contracts like a building blueprint. Whether the house is built with brick, wood, or steel, the floor plan stays the same. Rooms are in the same places, doors open the same way, and plumbing connects identically.

                         FORGE CLI

              ┌─────────────┼─────────────┐
              │             │             │
              ▼             ▼             ▼
        ┌───────────┐ ┌───────────┐ ┌───────────┐
        │ Provider  │ │  Backend  │ │ Frontend  │
        │ Contracts │ │ Contracts │ │ Contracts │
        └─────┬─────┘ └─────┬─────┘ └─────┬─────┘
              │             │             │
     ┌────┬──┴──┐    ┌─────┼─────┐  ┌────┼────┐
     ▼    ▼     ▼    ▼     ▼     ▼  ▼    ▼    ▼
   Twilio Uni  More Rust Laravel  … Next Nuxt Angular

API Contracts

API contracts define the exact endpoint paths, HTTP methods, request bodies, and response shapes that every generated backend must implement. The source of truth is an OpenAPI specification that all backends conform to.

Endpoint Definitions

Every FORGE application exposes the same API surface regardless of backend:

AUTH
├── POST /api/v1/auth/register    → { data: { user, token } }
├── POST /api/v1/auth/login       → { data: { user, token } }
├── POST /api/v1/auth/logout      → { message }
├── POST /api/v1/auth/refresh     → { data: { token } }
└── GET  /api/v1/auth/me          → { data: { user } }

ADMIN (requires authentication + permissions)
├── GET    /api/v1/admin/users        → { data: [...], meta: {...} }
├── POST   /api/v1/admin/users        → { data: user }
├── GET    /api/v1/admin/users/:id    → { data: user }
├── PUT    /api/v1/admin/users/:id    → { data: user }
├── DELETE /api/v1/admin/users/:id    → { message }
├── ... (same pattern for roles, contents, menus, settings, etc.)

PUBLIC (no authentication required)
├── GET /api/v1/contents/:slug   → { data: content }
└── GET /api/v1/menus            → { data: [...] }

Response Format

All backends return responses in a consistent envelope:

json
// Success (single resource)
{
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "email": "user@example.com",
    "name": "Ahmed"
  }
}

// Success (list with pagination)
{
  "data": [{ ... }, { ... }],
  "meta": {
    "total": 42,
    "page": 1,
    "per_page": 15
  }
}

// Error
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": {
      "email": ["The email field is required"]
    }
  }
}

Standard Error Codes

Every backend uses the same set of error codes:

CodeHTTP StatusMeaning
VALIDATION_ERROR422Request body failed validation
UNAUTHORIZED401Missing or invalid authentication
FORBIDDEN403Authenticated but lacks permission
NOT_FOUND404Resource does not exist
RATE_LIMITED429Too many requests
INTERNAL_ERROR500Unexpected server error

WARNING

Backends must never return raw framework errors. All errors must be wrapped in the standard error envelope. This ensures frontends can parse every response with a single error handler.

OpenAPI as Source of Truth

The API contract is formally defined as an OpenAPI 3.0 specification:

yaml
# contracts/api/openapi.yaml
openapi: 3.0.3
info:
  title: FORGE Application API
  version: 1.0.0

paths:
  /api/v1/auth/login:
    post:
      summary: Login user
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, password]
              properties:
                email:
                  type: string
                  format: email
                password:
                  type: string
                  minLength: 8
      responses:
        '200':
          description: Login successful
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/ValidationError'

TypeScript types for the frontend are generated directly from this OpenAPI spec, guaranteeing type-safe API consumption.

Database Contracts

Database contracts define the required tables, columns, types, and indexes that every generated application must have. While column naming may vary slightly by backend convention (snake_case vs camelCase), the logical schema is identical.

Required Tables

Every FORGE application includes these base tables:

TablePurpose
languagesSupported application languages
usersUser accounts
rolesNamed role definitions
permissionsGranular permission entries
role_userMany-to-many: user-role assignment
permission_roleMany-to-many: role-permission assignment
password_resetsPassword reset tokens
otp_codesOne-time password codes (if OTP auth enabled)
settingsKey-value application settings
translationsTranslatable UI strings
api_keysExternal API key management
audit_logsAction audit trail
mediaPolymorphic file attachments
contentsCMS content pages
menusNavigation menu structures

Column Contracts

Each table has required columns. For example, users:

sql
CREATE TABLE users (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name        VARCHAR(255) NOT NULL,
    email       VARCHAR(255) UNIQUE NOT NULL,
    phone       VARCHAR(50) UNIQUE,
    password    VARCHAR(255) NOT NULL,
    avatar      VARCHAR(500),
    is_active   BOOLEAN NOT NULL DEFAULT true,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

TIP

All tables use UUID primary keys and include created_at / updated_at timestamps. This convention is enforced across every backend generator.

Index Contracts

Performance-critical indexes are part of the contract:

sql
-- Users
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_phone ON users(phone);

-- Contents
CREATE INDEX idx_contents_slug ON contents(slug);
CREATE INDEX idx_contents_type ON contents(type);
CREATE INDEX idx_contents_status ON contents(status);

-- Audit logs
CREATE INDEX idx_audit_logs_user ON audit_logs(user_id);
CREATE INDEX idx_audit_logs_action ON audit_logs(action);
CREATE INDEX idx_audit_logs_created ON audit_logs(created_at);

Permission Contracts

FORGE uses a role-based access control (RBAC) system. The permission contract defines which permissions must exist for each module and how they map to API endpoints.

Permission Naming Convention

Permissions follow the pattern {module}.{action}:

users.view        users.create        users.edit        users.delete
roles.view        roles.create        roles.edit        roles.delete
permissions.view
contents.view     contents.create     contents.edit     contents.delete
menus.view        menus.create        menus.edit        menus.delete
settings.view     settings.edit
translations.view translations.edit
api_keys.view     api_keys.create     api_keys.edit     api_keys.delete
audit.view

Permission-to-Endpoint Mapping

Each admin endpoint requires a specific permission:

EndpointRequired Permission
GET /api/v1/admin/usersusers.view
POST /api/v1/admin/usersusers.create
PUT /api/v1/admin/users/:idusers.edit
DELETE /api/v1/admin/users/:idusers.delete
GET /api/v1/admin/contentscontents.view
POST /api/v1/admin/contentscontents.create
PUT /api/v1/admin/contents/:idcontents.edit
DELETE /api/v1/admin/contents/:idcontents.delete

Default Roles

Every generated application seeds two roles:

RolePermissions
super_adminAll permissions (wildcard)
adminAll permissions except permissions.view, api_keys.*, audit.view

WARNING

The super_admin role should be assigned sparingly. In production, most administrators should use the admin role or custom roles with specific permissions.

Page Contracts

Page contracts define which frontend pages must exist in every generated application. This ensures that regardless of the frontend framework, the user-facing application has complete coverage.

Required Public Pages

/                          Home page
/login                     User login
/register                  User registration
/password/forgot           Forgot password
/password/reset            Reset password with token
/:slug                     Dynamic CMS content page

Required Protected Pages

/profile                   View profile (requires auth)
/profile/edit              Edit profile (requires auth)
/profile/password          Change password (requires auth)

Required Admin Pages

/admin                     Dashboard
/admin/users               User list          (users.view)
/admin/users/create        Create user        (users.create)
/admin/users/:id/edit      Edit user          (users.edit)
/admin/roles               Role list          (roles.view)
/admin/roles/create        Create role        (roles.create)
/admin/roles/:id/edit      Edit role          (roles.edit)
/admin/permissions         Permission list    (permissions.view)
/admin/contents            Content list       (contents.view)
/admin/contents/create     Create content     (contents.create)
/admin/contents/:id        View content       (contents.view)
/admin/contents/:id/edit   Edit content       (contents.edit)
/admin/menus               Menu list          (menus.view)
/admin/menus/create        Create menu        (menus.create)
/admin/menus/:id/edit      Edit menu          (menus.edit)
/admin/settings            App settings       (settings.view)
/admin/translations        Translation mgmt   (translations.view)
/admin/api-keys            API key mgmt       (api_keys.view)
/admin/audit               Audit logs         (audit.view)

Each admin page enforces its required permission. If a user navigates to a page they lack permission for, the frontend displays an "Access Denied" view without making the API call.

Validating Contracts

FORGE provides CLI commands to verify that a generated application conforms to its contracts.

API Validation

bash
# Verify all required endpoints exist and return correct shapes
forge validate:api

# Validate a specific backend
forge validate:api --backend=rust

The validator starts the backend server, sends requests to every contracted endpoint, and verifies:

  • The endpoint exists and does not return 404
  • The response matches the expected JSON schema
  • Error responses use the standard error envelope
  • Authentication and permission checks are enforced

Page Validation

bash
# Verify all required pages exist in the frontend
forge validate:pages

# Validate a specific frontend
forge validate:pages --frontend=nextjs

The page validator checks that:

  • Every required route file exists in the project
  • Protected pages include authentication guards
  • Admin pages include permission checks
  • Public pages are accessible without authentication

Full Contract Test Suite

bash
# Run the complete contract test suite
forge test:contracts

This runs integration tests that exercise the full stack: the backend serves the API, and automated tests verify every contracted behavior end-to-end.

TIP

Run forge test:contracts before deploying to production. It catches contract violations that unit tests might miss, such as a missing permission check on an admin endpoint.

Benefits

BenefitDescription
Interchangeable backendsStart with Rust, switch to Laravel later -- same API, same frontend
Interchangeable frontendsStart with Next.js, add Nuxt.js later -- same API, same behavior
Swappable providersSwitch from Twilio to Unifonic with a config change
TestabilityMock any provider or backend behind the contract interface
Auto-generated docsOpenAPI spec produces API documentation automatically
Type safetyTypeScript types generated from OpenAPI for frontend consumption
Upgrade safetyContract tests catch breaking changes before they reach production

Released under the MIT License.