Skip to Content
🚀 APSO is now in public beta. Get started →
Get StartedQuickstart

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 --version to 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 install -g @apso/cli

Verify it installed correctly:

apso --version

Create a New Project

Scaffold the project

Run the following command to create a new APSO server project:

apso init --name task-api

This creates a new APSO project in a task-api directory with all the boilerplate you need.

cd task-api npm install

Define 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:

.apsorc
{ "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:

EntityPurposeKey fields
ProjectGroups related tasksname, status (Active/Archived)
TaskIndividual work itemstitle, status, priority, dueDate
TagLabels for categorizing tasksname, 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 generate

This 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 compose

This 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 provision

This 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:dev

Your 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" }' | jq

Response:

{ "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 }' | jq

Create 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 }' | jq

Create a tag

curl -s -X POST http://localhost:3000/tag \ -H "Content-Type: application/json" \ -d '{ "name": "Design", "color": "#3B82F6" }' | jq

List all tasks

curl -s http://localhost:3000/task | jq

Get a single task

curl -s http://localhost:3000/task/1 | jq

Update a task

curl -s -X PATCH http://localhost:3000/task/1 \ -H "Content-Type: application/json" \ -d '{ "status": "InProgress" }' | jq

Delete a task

curl -s -X DELETE http://localhost:3000/task/2

No 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:

DirectoryWhat it containsSafe to edit?
src/autogen/Generated entities, controllers, services, DTOsNo — overwritten on scaffold
src/extensions/Your custom business logic, endpoints, hooksYes — never touched by APSO
.apsorcYour schema definitionYes — this is your source of truth

What Just Happened?

With a single schema file and a few commands, APSO generated:

  1. 5 REST endpoints per entity — GET /, GET /:id, POST /, PATCH /:id, DELETE /:id
  2. TypeORM entities with proper column types, decorators, and validation
  3. Foreign keys and join tables for all relationships
  4. DTOs with validation — class-validator decorators for request body validation
  5. 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

Last updated on