Design a schema
Apso uses a declarative schema to define the data model. You describe entities, fields, and relationships in .apsorc, then the CLI generates framework-native persistence, request, service, and HTTP layers for TypeScript, Python, or Go.
id uuidname stringslug string uniqueworkspaceId relationuserId stringrole enumworkspaceId relationownerId relationstatus enumprojectId relationassigneeId relationdueAt datetimeGET/projectsPOST/projectsPATCH/tasks/:idEvery tenant-owned query is scoped by workspaceId.
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
Define entitiesChoose portable text, numeric, date, enum, JSON, UUID, and array types
Field typesConfigure OneToMany, ManyToOne, OneToOne, and ManyToMany once per relationship
RelationshipsApply application-layer data isolation with scopeBy
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 generate 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 generate 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 runapso generate. 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 init --name my-api
# 2. Edit .apsorc to define your schema
# 3. Generate code
apso generate
# 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.