Skip to main content

Tutorial: Build Your First Open Saasframe App

This tutorial walks you through:

  • Bootstrapping an Open Saasframe app (DB + CLI + dev)
  • Adding your own module as a package
  • Overriding the auth login screen from the app overlay

1) Prerequisites

  • Node.js 26+ (enforced by preinstall check; use nvm use 26 or fnm use 24 to switch)
  • Docker & Docker Compose (for PostgreSQL, Redis, and Meilisearch)
  • Copy apps/saasframe/.env.example to apps/saasframe/.env and set:
    • DATABASE_URL=postgres://postgres:postgres@localhost:5432/saasframe
    • JWT_SECRET=some-strong-secret
    • REDIS_URL=redis://localhost:6379

2) Install and Prepare

  • Start Docker services: docker compose up -d
  • Install deps: yarn install
  • Bootstrap the app: yarn initialize

This single command prepares module registries, generates/applies migrations, seeds default roles, provisions an admin user, and loads demo CRM data.

Alternatively, you can run the steps manually:

  • Generate modules (registry, entities, DI): yarn generate
  • Generate DB migrations (per enabled module): yarn db:generate
  • Apply migrations: yarn db:migrate

3) Seed Roles and Create Admin

If you ran yarn initialize, this step is already done. Otherwise:

  • Seed default roles: yarn saasframe auth seed-roles
  • Create the first tenant/org/admin:
    • yarn saasframe auth setup --orgName "Acme" --email [email protected] --password secret --roles superadmin,admin --skip-password-policy

4) Run the App

5) How Modules Load and Override

  • Core modules live in packages (packages/core, packages/search, etc.) and are enabled in apps/saasframe/src/modules.ts.
  • App-level overrides live under apps/saasframe/src/modules/<module>/... and take precedence over packages with the same relative path.
  • Generators discover routes/APIs/DI/i18n/entities for enabled modules and write combined outputs to generated/.

6) Create Your Own Module (as a package)

  1. Scaffold a local package:
packages/my-module/package.json
{
"name": "@saasframe/my-module",
"version": "0.1.0",
"private": true,
"type": "module",
"exports": { "./modules/*": "./src/modules/*" }
}
  1. Add source tree:
packages/my-module/src/modules/my_module/
index.ts # module metadata
acl.ts # (optional) RBAC features
setup.ts # (optional) default role features, tenant init
di.ts # (optional) register(container)
frontend/
page.tsx # serves "/my_module" (Next-style page)
backend/
page.tsx # serves "/backend/my_module"
api/
hello.ts # new API at "/api/my_module/hello"
i18n/
en.json # (optional) module dictionary
data/
entities.ts # (optional) MikroORM entities
validators.ts # (optional) zod validation schemas

Example index.ts:

import type { ModuleInfo } from '@saasframe/shared/modules/registry'
export const metadata: ModuleInfo = {
name: 'my_module',
title: 'My Module',
version: '0.1.0',
description: 'A custom module.',
}
  1. Add a TS alias so imports resolve:
  • In tsconfig.json paths add: "@saasframe/my-module/*": ["./packages/my-module/src/*"]
  1. Enable the module in apps/saasframe/src/modules.ts:
export const enabledModules: ModuleEntry[] = [
{ id: 'auth', from: '@saasframe/core' },
{ id: 'directory', from: '@saasframe/core' },
{ id: 'customers', from: '@saasframe/core' },
{ id: 'my_module', from: '@saasframe/my-module' },
]
  1. Regenerate + run:
  • yarn generate
  • yarn dev

Now visit /my_module and /backend/my_module.

App-level modules

For simpler modules that do not need to be published as separate packages, you can place them directly under apps/saasframe/src/modules/ and use from: '@app' instead. See the Customization Tutorials for this approach.

7) Override the Auth Login Screen

To customize login without touching core, create the override file in the app overlay:

apps/saasframe/src/modules/auth/frontend/login.tsx

This file overrides the package page @saasframe/core/modules/auth/frontend/login.tsx. Delete it to fall back to the package implementation.

8) Override Services (DI)

Use apps/saasframe/src/di.ts to register app-level DI overrides (runs after all module registrars):

import type { AppContainer } from '@saasframe/shared/lib/di/container'
import { asClass } from 'awilix'
// import { CustomAuthService } from './services/CustomAuthService'

export function register(container: AppContainer) {
// container.register({ authService: asClass(CustomAuthService).scoped() })
}

9) Entities and Migrations

  • Place package entities in packages/<pkg>/src/modules/<module>/data/entities.ts.
  • To override/extend in the app: apps/saasframe/src/modules/<module>/data/entities.override.ts.
  • Generate migrations: yarn db:generate (writes to packages/<pkg>/src/modules/<module>/migrations; app-local modules write to apps/saasframe/src/modules/<module>/migrations).
  • Apply migrations: yarn db:migrate.

10) CLI Commands

  • Each module can expose CLI in modules/<module>/cli.ts.
  • List and run via yarn saasframe <module> <command> [...args].
  • Add app-level commands in apps/saasframe/src/cli.ts (listed under module app).

You now have an app running with core modules, your own module package, and an overridden auth login screen -- all without editing core.