Quickstart
Build a production-ready REST API in 5 minutes. By the end of this guide you will have a running NestJS backend with full CRUD endpoints for a task management API — generated entirely from a schema file.
Prerequisites
Before you begin, make sure you have:
- Node.js 18+ — Download (run
node --versionto check) - Docker — Download (used for local PostgreSQL)
Docker is only needed for local development. In production, you can point to any PostgreSQL instance.
Install the CLI
Install the APSO CLI globally:
npm
npm install -g @apso/cliVerify it installed correctly:
apso --versionCreate a New Project
Scaffold the project
Run the following command to create a new APSO server project:
apso init --name task-apiThis creates a new APSO project in a task-api directory with all the boilerplate you need.
cd task-api
npm installDefine your schema
Open the .apsorc file in the project root. This is where you define your entire data model. Replace its contents with the following schema for a task management API:
{
"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" }
]
},
{
"name": "Task",
"created_at": true,
"updated_at": true,
"fields": [
{ "name": "title", "type": "text" },
{ "name": "description", "type": "text", "nullable": true },
{ "name": "status", "type": "enum", "values": ["Todo", "InProgress", "Done"], "default": "Todo" },
{ "name": "priority", "type": "integer", "default": 3 },
{ "name": "dueDate", "type": "date", "nullable": true }
]
},
{
"name": "Tag",
"created_at": true,
"fields": [
{ "name": "name", "type": "text" },
{ "name": "color", "type": "text", "nullable": true, "length": 7 }
]
}
],
"relationships": [
{ "from": "Project", "to": "Task", "type": "OneToMany" },
{ "from": "Task", "to": "Tag", "type": "ManyToMany", "to_name": "tags" }
]
}This schema defines three entities and their relationships:
| Entity | Purpose | Key fields |
|---|---|---|
| Project | Groups related tasks | name, status (Active/Archived) |
| Task | Individual work items | title, status, priority, dueDate |
| Tag | Labels for categorizing tasks | name, color |
The relationships array tells APSO how entities connect. A Project has many Tasks (OneToMany), and Tasks can have many Tags (ManyToMany). APSO automatically generates the inverse side of each relationship, so you only define it once.
Version 2 format required. Always include "version": 2 at the top of your .apsorc file. This uses the current schema format with separate entities and relationships arrays.
Generate the backend code
Run the scaffold command to generate your entire backend:
apso generateThis generates:
- TypeORM entities with decorators, validation, and relationships
- NestJS modules with controllers, services, and DTOs for each entity
- CRUD endpoints — create, read, update, delete, and list with filtering
- OpenAPI/Swagger documentation
All generated code goes into src/autogen/. Never edit files in this directory — they are overwritten on every scaffold. Use the src/extensions/ directory for custom logic instead.
Start the database
Make sure Docker is running, then start a local PostgreSQL instance:
npm run composeThis uses Docker Compose to spin up a PostgreSQL container configured for your project.
Provision the database schema
Create the tables in your database:
npm run provisionThis connects to your local PostgreSQL instance and creates all the tables, columns, indexes, and constraints defined in your schema.
For rapid prototyping, you can skip manual provisioning by adding DATABASE_SYNC=true to your .env file. This auto-syncs your models to the database on every server start. Do not use this in production.
Start the development server
npm run start:devYour API is now running at http://localhost:3000Â . The server watches for file changes and automatically reloads.
Test Your API
Open a new terminal and try these requests. Every entity in your schema gets a full set of REST endpoints automatically.
Create a project
curl -s -X POST http://localhost:3000/project \
-H "Content-Type: application/json" \
-d '{
"name": "Website Redesign",
"description": "Q2 website overhaul"
}' | jqResponse:
{
"id": 1,
"name": "Website Redesign",
"description": "Q2 website overhaul",
"status": "Active",
"created_at": "2025-01-15T10:30:00.000Z",
"updated_at": "2025-01-15T10:30:00.000Z"
}Create a task in that project
curl -s -X POST http://localhost:3000/task \
-H "Content-Type: application/json" \
-d '{
"title": "Design new homepage",
"description": "Create mockups for the new landing page",
"priority": 1,
"dueDate": "2025-02-01",
"projectId": 1
}' | jqCreate a second task
curl -s -X POST http://localhost:3000/task \
-H "Content-Type: application/json" \
-d '{
"title": "Set up analytics",
"status": "Todo",
"priority": 2,
"projectId": 1
}' | jqCreate a tag
curl -s -X POST http://localhost:3000/tag \
-H "Content-Type: application/json" \
-d '{
"name": "Design",
"color": "#3B82F6"
}' | jqList all tasks
curl -s http://localhost:3000/task | jqGet a single task
curl -s http://localhost:3000/task/1 | jqUpdate a task
curl -s -X PATCH http://localhost:3000/task/1 \
-H "Content-Type: application/json" \
-d '{
"status": "InProgress"
}' | jqDelete a task
curl -s -X DELETE http://localhost:3000/task/2No jq? Remove | jq from the commands above. It is optional and only used for pretty-printing JSON output. Install it with brew install jq (macOS) or apt install jq (Linux).
Explore the Generated Code
Your project now has the following structure:
task-api/
├── .apsorc # Your schema definition
├── .env # Database connection config
├── docker-compose.yml # Local PostgreSQL
├── package.json
├── src/
│ ├── main.ts # Application entry point
│ ├── app.module.ts # Root NestJS module
│ ├── autogen/ # Generated code (do not edit)
│ │ ├── Project/
│ │ │ ├── Project.entity.ts # TypeORM entity
│ │ │ ├── Project.service.ts # CRUD service
│ │ │ ├── Project.controller.ts
│ │ │ ├── Project.module.ts
│ │ │ └── dto/ # Create/Update DTOs
│ │ ├── Task/
│ │ │ └── ...
│ │ └── Tag/
│ │ └── ...
│ └── extensions/ # Your custom code (never overwritten)
│ └── ...
└── ...The key directories:
| Directory | What it contains | Safe to edit? |
|---|---|---|
src/autogen/ | Generated entities, controllers, services, DTOs | No — overwritten on scaffold |
src/extensions/ | Your custom business logic, endpoints, hooks | Yes — never touched by APSO |
.apsorc | Your schema definition | Yes — this is your source of truth |
What Just Happened?
With a single schema file and a few commands, APSO generated:
- 5 REST endpoints per entity —
GET /,GET /:id,POST /,PATCH /:id,DELETE /:id - TypeORM entities with proper column types, decorators, and validation
- Foreign keys and join tables for all relationships
- DTOs with validation — class-validator decorators for request body validation
- NestJS modules wired together with dependency injection
That is a fully functional API with 15 endpoints, input validation, and relational data — from 45 lines of JSON.
Next Steps
Full .apsorc configuration: field types, validation, indexes, and more
Add custom endpoints and business logic without losing generated code
The Extensions PatternAdd Better Auth, Auth0, Clerk, or bring your own auth provider
AuthenticationIsolate data by organization with scopeBy scoping
Use the auto-generated TypeScript SDK with React or Next.js
Connect a FrontendDeploy to APSO Cloud, Docker, or self-host anywhere
Deploy to Production