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

CLI Configuration

The .apsorc file is a JSON configuration file that describes your entire data model. It is the single source of truth that the Apso CLI reads to generate your backend code.

File Location

Place the .apsorc file in the root of your project directory (the same directory where you run apso server scaffold). The CLI uses the rc library to locate the file, searching upward from the current directory through parent directories.

my-api/ ├── .apsorc <-- here ├── package.json ├── src/ │ ├── autogen/ │ └── extensions/ └── ...

Version 2 Format

The current schema format is version 2. Always set "version": 2 at the top of your .apsorc file. Version 1 is a legacy format with limited features and is not recommended for new projects.

Top-Level Structure

.apsorc
{ "version": 2, "rootFolder": "src", "language": "typescript", "apiType": "Rest", "auth": { ... }, "entities": [ ... ], "relationships": [ ... ] }
FieldTypeRequiredDescription
versionintegerYesMust be 2
rootFolderstringYesDirectory where generated source code is placed (e.g., "src")
languagestringNoDefault target language: "typescript", "python", or "go". Can be overridden by the --language CLI flag.
apiTypestringNo"Rest" (default) or "Graphql". GraphQL requires version 2 and TypeScript.
authobjectNoAuthentication provider configuration. See Authentication.
entitiesarrayYesArray of entity definitions
relationshipsarrayYesArray of relationship definitions between entities

Entities

Each entity represents a database table and generates a complete set of backend files (entity class, DTO, service, controller, module).

{ "entities": [ { "name": "Project", "created_at": true, "updated_at": true, "primaryKeyType": "serial", "fields": [ { "name": "name", "type": "text", "length": 255 }, { "name": "status", "type": "enum", "values": ["Active", "Archived"], "default": "Active" }, { "name": "description", "type": "text", "nullable": true } ] } ] }

Entity Properties

PropertyTypeRequiredDefaultDescription
namestringYes—Entity name in PascalCase. Becomes the class name and (lowercased) the table name.
fieldsarrayYes—Array of field definitions (at least one required)
created_atbooleanNofalseAdd a created_at timestamp column with @CreateDateColumn()
updated_atbooleanNofalseAdd an updated_at timestamp column with @UpdateDateColumn()
primaryKeyTypestringNo"serial"Primary key type: "serial" (auto-increment integer) or "uuid"
uniquesarrayNo—Multi-field unique constraints
scopeBystring | string[]No—Fields for multi-tenant data isolation. See Data Scoping.
scopeOptionsobjectNo—Fine-tune scope enforcement behavior

Auto-Generated Columns

Every entity automatically receives these columns (you do not need to define them in fields):

  • id — primary key (@PrimaryGeneratedColumn() or @PrimaryGeneratedColumn("uuid"))
  • created_at — if "created_at": true is set
  • updated_at — if "updated_at": true is set
  • Foreign key columns — generated automatically from relationships (e.g., workspaceId)

Fields

Each field maps to a database column with appropriate TypeORM decorators and validation rules.

{ "name": "email", "type": "text", "length": 255, "unique": true, "is_email": true }

Field Properties

PropertyTypeRequiredDescription
namestringYesColumn name (camelCase recommended)
typestringYesData type (see table below)
uniquebooleanNoAdd a unique constraint
nullablebooleanNoAllow null values. Adds @IsOptional() validator.
lengthintegerNoMaximum string length. Adds @MaxLength() validator.
defaultstring | number | booleanNoDefault column value
is_emailbooleanNoAdd @IsEmail() validation
valuesstring[]Required for enumAllowed values for enum fields
precisionintegerNoTotal digits for decimal/numeric types
scaleintegerNoDigits after decimal point for decimal/numeric types

Supported Field Types

TypePostgreSQL TypeTypeORM DecoratorAuto Validation
texttext@Column({ type: 'text' })@IsString(), @IsNotEmpty()
integerinteger@Column({ type: 'integer' })@IsNumber()
floatfloat@Column({ type: 'float' })@IsNumber()
decimaldecimal(p,s)@Column({ type: 'decimal', precision, scale })@IsNumber()
numericnumeric(p,s)@Column({ type: 'numeric', precision, scale })@IsNumber()
booleanboolean@Column({ type: 'boolean' })@IsBoolean()
enumenum@Column({ type: 'enum', enum: [...] })—
jsonjsonb@Column('jsonb')—
datedate@Column({ type: 'date' })—
timestamptztimestamptz@Column({ type: 'timestamptz' })—
pointpoint@Column({ type: 'point' })— (requires PostGIS)
linestringlinestring@Column({ type: 'linestring' })— (requires PostGIS)
polygonpolygon@Column({ type: 'polygon' })— (requires PostGIS)
multipointmultipoint@Column({ type: 'multipoint' })— (requires PostGIS)
multilinestringmultilinestring@Column({ type: 'multilinestring' })— (requires PostGIS)
multipolygonmultipolygon@Column({ type: 'multipolygon' })— (requires PostGIS)
geometrygeometry@Column({ type: 'geometry' })— (requires PostGIS)
geographygeography@Column({ type: 'geography' })— (requires PostGIS)
geometrycollectiongeometrycollection@Column({ type: 'geometrycollection' })— (requires PostGIS)

PostGIS types require the PostGIS extension installed in your PostgreSQL database:

CREATE EXTENSION IF NOT EXISTS postgis;

Field Examples

Text with length and email validation:

{ "name": "email", "type": "text", "length": 255, "is_email": true, "unique": true }

Enum with default:

{ "name": "role", "type": "enum", "values": ["User", "Admin", "Owner"], "default": "User" }

Decimal with precision:

{ "name": "price", "type": "decimal", "precision": 10, "scale": 2, "default": 0 }

Nullable JSON:

{ "name": "metadata", "type": "json", "nullable": true }

Relationships

Relationships are defined in a separate top-level array. Each relationship generates the appropriate TypeORM decorators, foreign key columns, and join tables.

Important: Define only ONE side of each relationship. Apso auto-generates the inverse side. Defining both sides causes duplicate properties and compilation errors.

{ "relationships": [ { "from": "Workspace", "to": "User", "type": "ManyToOne", "to_name": "owner" }, { "from": "Project", "to": "Workspace", "type": "ManyToOne" }, { "from": "Workspace", "to": "Project", "type": "OneToMany" } ] }

Relationship Properties

PropertyTypeRequiredDescription
fromstringYesSource entity name
tostringYesTarget entity name
typestringYesOneToMany, ManyToOne, ManyToMany, or OneToOne
to_namestringNoCustom property name on the source entity for the relationship (e.g., "owner" instead of the default "user")
nullablebooleanNoWhether the foreign key is nullable
cascadeDeletebooleanNoCascade delete related entities when the source is deleted
bi_directionalbooleanNoGenerate both sides of a ManyToMany relationship
joinTableNamestringNoCustom join table name (ManyToMany only)
joinColumnNamestringNoCustom foreign key column name in join table for the from entity (ManyToMany only)
inverseJoinColumnNamestringNoCustom foreign key column name in join table for the to entity (ManyToMany only)

Relationship Examples

ManyToOne with custom name:

{ "from": "Project", "to": "User", "type": "ManyToOne", "to_name": "createdBy", "nullable": true }

Generates on the Project entity:

@ManyToOne(() => User) @JoinColumn({ name: 'createdById' }) createdBy: User; @Column({ type: 'integer', nullable: true }) createdById: number;

OneToMany:

{ "from": "User", "to": "Project", "type": "OneToMany" }

Generates on the User entity:

@OneToMany(() => Project, (project) => project.user) projects: Project[];

ManyToMany:

{ "from": "User", "to": "Role", "type": "ManyToMany", "to_name": "roles" }

Generates:

@ManyToMany(() => Role, (role) => role.users) @JoinTable() roles: Role[];

Self-referencing relationship:

{ "from": "Category", "to": "Category", "type": "ManyToOne", "to_name": "parent", "nullable": true }

Multi-Field Unique Constraints

Define unique constraints that span multiple columns:

{ "name": "WorkspaceUser", "fields": [ { "name": "email", "type": "text" }, { "name": "role", "type": "enum", "values": ["User", "Admin"] } ], "uniques": [ { "name": "UQ_workspace_user_email", "fields": ["email", "workspaceId"] } ] }

Scope Configuration

The scopeBy and scopeOptions properties on entities control multi-tenant data isolation. When configured, the CLI generates NestJS guards that automatically filter, inject, and verify scope values on every request.

{ "name": "Project", "scopeBy": "workspaceId", "scopeOptions": { "injectOnCreate": true, "enforceOn": ["find", "get", "create", "update", "delete"], "bypassRoles": ["superadmin"] }, "fields": [ { "name": "name", "type": "text" } ] }
scopeOptions PropertyTypeDefaultDescription
injectOnCreatebooleantrueAuto-inject scope value on POST requests
enforceOnstring[]All operationsWhich operations enforce scope: find, get, create, update, delete
bypassRolesstring[][]Roles that skip scope checking

For full details on authentication and scoping, see the Authentication documentation.

Complete Example

.apsorc
{ "version": 2, "rootFolder": "src", "language": "typescript", "apiType": "Rest", "auth": { "provider": "better-auth", "sessionEntity": "session", "userEntity": "User", "accountUserEntity": "AccountUser", "organizationField": "organizationId" }, "entities": [ { "name": "User", "created_at": true, "updated_at": true, "fields": [ { "name": "email", "type": "text", "length": 255, "is_email": true, "unique": true }, { "name": "fullName", "type": "text", "nullable": true } ] }, { "name": "Workspace", "created_at": true, "updated_at": true, "fields": [ { "name": "name", "type": "text" } ] }, { "name": "WorkspaceUser", "created_at": true, "updated_at": true, "fields": [ { "name": "email", "type": "text", "length": 255, "is_email": true }, { "name": "invite_code", "type": "text", "length": 64 }, { "name": "role", "type": "enum", "values": ["User", "Admin"], "default": "Admin" }, { "name": "status", "type": "enum", "values": ["Active", "Invited", "Inactive", "Deleted"] }, { "name": "activeAt", "type": "date", "nullable": true } ] }, { "name": "Project", "created_at": true, "updated_at": true, "scopeBy": "workspaceId", "fields": [ { "name": "name", "type": "text" }, { "name": "status", "type": "enum", "values": ["Active", "Archived"], "default": "Active" } ] } ], "relationships": [ { "from": "User", "to": "WorkspaceUser", "type": "OneToMany", "nullable": true }, { "from": "Workspace", "to": "WorkspaceUser", "type": "OneToMany" }, { "from": "Workspace", "to": "Project", "type": "OneToMany" }, { "from": "Project", "to": "User", "type": "ManyToOne", "to_name": "owner" } ] }

Schema Validation

The .apsorc file is validated against a JSON Schema (apsorc.schema.json) included with the CLI package. Key validation rules:

  • version must be 2
  • Every entity must have a name and at least one fields entry
  • Every field must have a name and type
  • Enum fields must include a values array
  • Relationship type must be one of OneToMany, ManyToOne, ManyToMany, OneToOne
  • Relationship from and to must reference entity names defined in the entities array
Last updated on