Skip to content

Code Generation

FORGE's primary job is to turn a declarative configuration file into a fully working application. The code generation pipeline transforms Tera templates into production-ready source code for your chosen backend and frontend frameworks.

Generation Pipeline

Every code generation operation follows the same five-step pipeline:

  ┌──────────────┐     ┌──────────────┐     ┌──────────────┐
  │  1. Command  │────▶│  2. Config   │────▶│  3. Context  │
  │  (CLI input) │     │  (forge.yaml)│     │  (variables) │
  └──────────────┘     └──────────────┘     └──────────────┘


                       ┌──────────────┐     ┌──────────────┐
                       │  5. Output   │◀────│  4. Render   │
                       │  (files)     │     │  (Tera engine)│
                       └──────────────┘     └──────────────┘
  1. Command -- The user runs a CLI command (forge new, forge make:model, etc.)
  2. Config -- FORGE reads forge.yaml to understand the project structure, enabled features, languages, and providers
  3. Context -- A context object is built containing all template variables (app name, auth method, languages, etc.)
  4. Render -- The Tera engine processes .tera templates, evaluating conditionals and substituting variables
  5. Output -- Generated files are written to the project directory with the .tera extension stripped

Creating a New Project

The forge new command scaffolds an entire application from scratch:

bash
forge new --name=myapp --backend=rust --frontend=nextjs

This single command generates:

myapp/
├── forge.yaml                   # Project configuration
├── forge.lock                   # Version tracking
├── .env.example                 # Environment template
├── .gitignore

├── apps/
│   ├── api/                     # Rust + Axum backend
│   │   ├── Cargo.toml
│   │   ├── src/
│   │   │   ├── main.rs
│   │   │   ├── config/
│   │   │   ├── models/
│   │   │   ├── handlers/
│   │   │   ├── middleware/
│   │   │   ├── routes/
│   │   │   ├── services/
│   │   │   ├── dto/
│   │   │   ├── providers/
│   │   │   ├── error/
│   │   │   └── utils/
│   │   ├── migrations/
│   │   └── tests/
│   │
│   ├── web/                     # Next.js public web app
│   │   ├── package.json
│   │   ├── next.config.js
│   │   ├── app/
│   │   ├── components/
│   │   └── lib/
│   │
│   └── admin/                   # Next.js admin dashboard
│       ├── package.json
│       ├── app/
│       └── components/

└── infra/
    ├── docker/
    │   ├── docker-compose.yml
    │   └── docker-compose.dev.yml
    └── caddy/
        └── Caddyfile

TIP

After generation, FORGE runs post-generation hooks automatically. For a Rust backend, this includes cargo check to verify the generated code compiles. For Next.js frontends, it runs npm install to set up dependencies.

Interactive Mode

Running forge new without flags launches an interactive wizard:

bash
$ forge new

Welcome to FORGE Application Factory!

? Project name: myapp
? Display name: My Application
? Backend framework: (Use arrow keys)
 Rust (Axum) - High performance, type-safe
    Laravel (PHP) - Rapid development, rich ecosystem
    FastAPI (Python) - Modern, fast Python API
    Node.js (Express) - JavaScript everywhere

? Frontend framework: (Use arrow keys)
 Next.js - React with SSR
    Nuxt.js - Vue with SSR
    Angular - Enterprise TypeScript
    Vanilla - TypeScript + Vite

? Authentication method:
 Email + Password
    Mobile + OTP
    Both

? Default language: English (en)
? Additional languages: Arabic (ar)

? SMS provider: Twilio
? Email provider: SMTP
? Storage provider: Local filesystem

Creating myapp...
 Generated backend (Rust + Axum)
 Generated frontend (Next.js)
 Generated admin panel (Next.js)
 Generated infrastructure configs
 Created 17 database migrations
 Seeded default permissions and roles
 Initialized git repository

Your application is ready at ./myapp
Run `cd myapp && forge serve` to start developing.

The make Commands

While forge new creates an entire project, the make commands generate individual components within an existing project. These are the workhorses of day-to-day development.

forge make:model

Generates a database model with its associated files. You can pass inline field definitions using name:type syntax to generate typed columns, Rust struct fields, and admin form inputs in one step.

bash
# Without fields (empty struct, empty migration)
forge make:model Product

# With inline fields
forge make:model Product -m title:string price:decimal is_active:boolean

This creates:

FilePurpose
src/models/product.rsModel struct with typed fields
migrations/00018_create_products_table.sqlDatabase migration with columns

forge make:model --all

The --all flag generates the complete resource stack — model, migration, handler, and admin CRUD pages:

bash
forge make:model Product --all title:string price:decimal status:lookup category:belongs_to

This creates everything needed for full CRUD operations:

FilePurpose
src/models/product.rsModel struct with typed fields
src/dto/product.rsRequest and response types
src/handlers/admin/products.rsAdmin CRUD handlers
src/handlers/products.rsPublic handlers (if applicable)
src/services/product.rsBusiness logic service
src/routes/admin.rsUpdated with product routes
migrations/00018_create_products_table.sqlMigration with typed columns and FKs
seeders/products.sqlDefault seed data
admin/app/(dashboard)/products/page.tsxAdmin list page
admin/app/(dashboard)/products/create/page.tsxAdmin create form (with inputs per field)
admin/app/(dashboard)/products/[id]/edit/page.tsxAdmin edit form

WARNING

The --all flag also registers permissions (products.view, products.create, products.edit, products.delete) and adds the resource to the admin sidebar. Always review the generated code before committing.

forge make:migration

Generates a database migration file. Like make:model, you can pass inline fields to generate typed columns:

bash
# Empty migration
forge make:migration add_price_to_products

# Migration with typed columns
forge make:migration create_products_table title:string price:decimal status:lookup

Creates:

migrations/00019_add_price_to_products.sql

The migration number is automatically incremented based on existing migrations.

forge make:handler

Generates an API handler:

bash
forge make:handler Products

Creates:

FilePurpose
src/handlers/products.rsHandler functions
src/handlers/admin/products.rsAdmin handler functions

forge make:dto

Generates Data Transfer Object types:

bash
forge make:dto Product

Creates:

FilePurpose
src/dto/product.rsCreateProductRequest, UpdateProductRequest, ProductResponse

forge make:service

Generates a business logic service:

bash
forge make:service Product

Creates:

FilePurpose
src/services/product.rsService with CRUD methods

forge make:seeder

Generates a database seeder:

bash
forge make:seeder Products

Creates:

FilePurpose
seeders/products.sqlINSERT statements for default data

forge make:page

Generates frontend pages:

bash
forge make:page Products --admin

Creates:

FilePurpose
admin/app/(dashboard)/products/page.tsxList page with data table
admin/app/(dashboard)/products/columns.tsxTable column definitions
admin/app/(dashboard)/products/create/page.tsxCreate form
admin/app/(dashboard)/products/[id]/edit/page.tsxEdit form

Field Definitions

The make:model and make:migration commands accept inline field definitions using name:type syntax. Each field is parsed into a fully-resolved definition that flows through every generated file — from SQL columns to Rust struct fields to admin form inputs.

Inline Syntax

Pass fields as positional arguments after the model or migration name:

bash
forge make:model Product --all title:string price:decimal is_active:boolean status:lookup

Each field follows the pattern:

<field_name>:<field_type>

Type Reference

TypeSQLRustTypeScriptForm
stringVARCHAR(255)StringstringText input
textTEXTStringstringTextarea
integerINTEGERi32numberNumber input
bigintBIGINTi64numberNumber input
decimalDECIMAL(10,2)BigDecimalnumberNumber input
booleanBOOLEANboolbooleanSwitch toggle
dateDATENaiveDatestringDate picker
datetimeTIMESTAMPTZDateTime<Utc>stringDatetime picker
uuidUUIDUuidstringText input
jsonJSONBserde_json::ValueRecord<string, unknown>Textarea
lookupUUIDlookupsUuidstringSelect dropdown
belongs_toUUID → related tableUuidstringSelect dropdown

Lookup Fields

A lookup field creates a foreign key to the centralized lookups table. The field name becomes the parent slug used to filter lookup values:

bash
forge make:model Product --all status:lookup priority:lookup

This generates:

  • status_id UUID REFERENCES lookups(id) — filtered by slug "status"
  • priority_id UUID REFERENCES lookups(id) — filtered by slug "priority"

BelongsTo Fields

A belongs_to field creates a foreign key to another model's table, derived from the field name:

bash
forge make:model Product --all category:belongs_to user:belongs_to

This generates:

  • category_id UUID REFERENCES categories(id)
  • user_id UUID REFERENCES users(id)

Interactive Mode

If you prefer not to type fields inline, use --interactive (-i) to launch a guided builder:

bash
forge make:model Product --all --interactive

The builder prompts for each field's name and type, validates input, and shows the resolved SQL type as confirmation.

Generated Output Example

Given this command:

bash
forge make:model Article --all title:string body:text is_published:boolean author:belongs_to

The generated migration includes:

sql
CREATE TABLE articles (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    title VARCHAR(255) NOT NULL,
    body TEXT NOT NULL,
    is_published BOOLEAN NOT NULL DEFAULT false,
    author_id UUID REFERENCES users(id),
    created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

The generated Rust model includes:

rust
pub struct Article {
    pub id: Uuid,
    pub title: String,
    pub body: String,
    pub is_published: bool,
    pub author_id: Option<Uuid>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

And the admin create form renders appropriate inputs for each field type — text inputs for strings, a textarea for the body, a switch for the boolean, and a select dropdown for the author relation.

Command Reference

Here is the complete list of make commands and what each generates:

forge make:model <Name>              Model file
forge make:model <Name> --all        Full resource stack (model + all below)
forge make:migration <name>          Database migration
forge make:handler <Name>            API handler(s)
forge make:dto <Name>                Request/response types
forge make:service <Name>            Business logic service
forge make:seeder <Name>             Database seed file
forge make:page <Name>               Frontend page(s)
forge make:page <Name> --admin       Admin panel pages
forge make:page <Name> --web         Public web pages

TIP

Use forge make:model Product --all for new resources. It generates everything in one command and ensures all pieces are properly wired together -- routes registered, sidebar updated, permissions seeded.

How Context Variables Flow

When a make command runs, FORGE builds a context from multiple sources:

┌──────────────┐
│  forge.yaml  │──→ app.name, auth.method, languages, providers
└──────────────┘
       +
┌──────────────┐
│  CLI flags   │──→ model name, --all, --admin, --web
└──────────────┘
       +
┌──────────────┐
│  CLI fields  │──→ fields[], has_fields, lookup_fields, relation_fields
│  (name:type) │
└──────────────┘
       +
┌──────────────┐
│  Derived     │──→ table name (pluralized, snake_case),
│  values      │    struct name (PascalCase), route path (kebab-case),
└──────────────┘    next migration number, timestamps


┌──────────────┐
│   Template   │──→ Full context passed to Tera engine
│   Context    │
└──────────────┘

For example, forge make:model ProductCategory --all title:string status:lookup produces this context:

yaml
model:
  name: "ProductCategory"            # PascalCase
  snake: "product_category"          # snake_case
  kebab: "product-category"          # kebab-case
  plural: "product_categories"       # Pluralized
  table: "product_categories"        # Database table name

  # From forge.yaml
  app_name: "myapp"
  auth_method: "email_password"
  languages: [{ code: "en", ... }, { code: "ar", ... }]

  # From CLI field definitions
  has_fields: true
  fields:
    - name: "title"
      field_type: "string"
      column_name: "title"
      sql_type: "VARCHAR(255)"
      rust_type: "String"
      ts_type: "string"
      form_component: "Input"
      zod_schema: "z.string().min(1)"
      is_relation: false
    - name: "status"
      field_type: "lookup"
      column_name: "status_id"
      sql_type: "UUID"
      rust_type: "Uuid"
      ts_type: "string"
      form_component: "Select"
      is_relation: true
      is_lookup: true
      lookup_slug: "status"
      foreign_table: "lookups"

Generation vs. Runtime

It is important to understand that FORGE operates at generation time, not runtime. Once code is generated, FORGE's job is done. The generated application runs independently without any dependency on the FORGE CLI.

Generation Time (forge)          Runtime (your app)
─────────────────────           ──────────────────
forge.yaml + templates    →     Source code
forge make:model Product  →     product.rs, migration, handler, etc.
forge serve               →     Starts dev servers (convenience)

The generated code has no        Your app runs with standard
dependency on FORGE at           tooling: cargo, npm, docker
runtime.

WARNING

If you modify generated files and then re-run a make command that targets the same file, FORGE will warn you about the conflict. It will not silently overwrite your changes.

Post-Generation Hooks

After generating files, FORGE can run verification commands defined in the backend or frontend manifest:

yaml
# manifest.yaml
hooks:
  post_generate:
    - "cd apps/api && cargo check"
    - "cd apps/web && npm run lint"
    - "cd apps/admin && npm run lint"

These hooks catch generation errors immediately. If cargo check fails, FORGE reports the error so you can investigate before writing any custom code on top of a broken foundation.

Released under the MIT License.