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 26orfnm use 24to switch) - Docker & Docker Compose (for PostgreSQL, Redis, and Meilisearch)
- Copy
apps/saasframe/.env.exampletoapps/saasframe/.envand set:DATABASE_URL=postgres://postgres:postgres@localhost:5432/saasframeJWT_SECRET=some-strong-secretREDIS_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 admin@acme.com --password secret --roles superadmin,admin --skip-password-policy
4) Run the App
yarn dev- Open http://localhost:3000/backend
5) How Modules Load and Override
- Core modules live in packages (
packages/core,packages/search, etc.) and are enabled inapps/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)
- 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/*" }
}
- 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.',
}
- Add a TS alias so imports resolve:
- In
tsconfig.jsonpaths add:"@saasframe/my-module/*": ["./packages/my-module/src/*"]
- 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' },
]
- Regenerate + run:
yarn generateyarn dev
Now visit /my_module and /backend/my_module.
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 topackages/<pkg>/src/modules/<module>/migrations; app-local modules write toapps/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 moduleapp).
You now have an app running with core modules, your own module package, and an overridden auth login screen -- all without editing core.