Schema Design
Apso uses a declarative schema system to define your data model. You describe your entities, fields, and relationships in a single .apsorc configuration file, and the Apso CLI generates a complete, production-ready NestJS backend — TypeORM entities, CRUD controllers, services, DTOs, validation, and database migrations.
The .apsorc File
Your schema lives in a JSON file called .apsorc at the root of your service project. Every .apsorc file follows the version 2 format with four top-level properties:
{
"version": 2,
"rootFolder": "src",
"entities": [
{
"name": "Project",
"created_at": true,
"updated_at": true,
"fields": [
{ "name": "name", "type": "text" },
{ "name": "description", "type": "text", "nullable": true },
{ "name": "status", "type": "enum", "values": ["Active", "Archived"], "default": "Active" }
]
}
],
"relationships": [
{ "from": "Project", "to": "Workspace", "type": "ManyToOne" }
]
}| Property | Type | Required | Description |
|---|---|---|---|
version | 2 | Yes | Must always be 2. Identifies the schema format version. |
rootFolder | string | Yes | Directory where generated code is placed (typically "src"). |
entities | array | Yes | Array of entity definitions. Each entity becomes a database table and API resource. |
relationships | array | Yes | Array of relationship definitions connecting entities. |
Schema Components
Define entities with fields, timestamps, indexes, primary key types, and validation rules
Entity DefinitionComplete reference: text, integer, boolean, decimal, enum, json, PostGIS spatial types, and more
Field TypesOneToMany, ManyToOne, OneToOne, and ManyToMany — with the critical one-side-only rule
RelationshipsApplication-layer data isolation with scopeBy — Apso’s answer to Row-Level Security
Multi-TenancySchema Design Principles
1. Start simple, iterate
Begin with the minimum viable schema. You can add fields, entities, and relationships later — re-run apso server scaffold and your code regenerates. Start with two or three core entities and expand from there.
2. Use meaningful names
Entity and field names flow directly into your database columns, API endpoints, and TypeScript types. Choose names that are:
- PascalCase for entities:
ProjectMember, notproject_memberorPM - Singular:
Project, notProjects - Descriptive:
ApplicationServiceMetric, notASM
3. Define relationships once
This is the single most important rule in Apso schema design. Only define one side of each relationship. Apso auto-generates the inverse side. Defining both sides causes duplicate properties, TypeScript compilation errors, and entity conflicts. See Relationships for details.
4. Plan for multi-tenancy early
If you are building a SaaS application, add scopeBy to your tenant-scoped entities from the start. Retrofitting data isolation later is significantly harder than designing for it upfront. See Multi-Tenancy.
What Gets Generated
From your .apsorc file, running apso server scaffold generates:
| Component | Location | Description |
|---|---|---|
| TypeORM Entities | src/autogen/{Entity}/ | Type-safe entity classes with decorators, validation, and relationships |
| Controllers | src/autogen/{Entity}/ | REST endpoints with full CRUD (GET, POST, PATCH, DELETE) |
| Services | src/autogen/{Entity}/ | Business logic layer wrapping TypeORM repositories |
| DTOs | src/autogen/{Entity}/ | Create and Update data transfer objects with class-validator decorators |
| Modules | src/autogen/{Entity}/ | NestJS modules wiring controllers, services, and entity repositories |
| Enums | src/autogen/enums.ts | TypeScript enums for all enum-typed fields |
| Auth Guards | src/guards/ | Authentication and scope guards (when auth / scopeBy configured) |
Never modify files in
src/autogen/. They are regenerated every time you scaffold. Place all custom business logic insrc/extensions/{Entity}/instead. See Extending Generated Code for the pattern.
Workflow
The typical development workflow with Apso:
# 1. Create a new project
apso server new --name my-api
# 2. Edit .apsorc to define your schema
# 3. Generate code
apso server scaffold
# 4. Start the database
npm run compose
# 5. Provision the schema
npm run provision
# 6. Start the dev server
npm run start:devWhen you change your schema, repeat steps 2-3. For rapid prototyping, enable DATABASE_SYNC=true in your .env to skip manual migrations.