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

Code Generation

APSO generates real, production-ready code that you own and can modify.

What Gets Generated

When you run apso server scaffold, APSO creates a complete backend:

my-api/ ├── src/ │ ├── app.module.ts # Root NestJS module │ ├── main.ts # Application entry point │ ├── entities/ # TypeORM entity definitions │ │ ├── customer.entity.ts │ │ └── order.entity.ts │ ├── modules/ # Feature modules │ │ ├── customer/ │ │ │ ├── customer.controller.ts │ │ │ ├── customer.service.ts │ │ │ ├── customer.module.ts │ │ │ └── dto/ │ │ │ ├── create-customer.dto.ts │ │ │ ├── update-customer.dto.ts │ │ │ └── customer-response.dto.ts │ │ └── order/ │ │ └── ... │ ├── common/ # Shared utilities │ │ ├── decorators/ │ │ ├── filters/ │ │ ├── guards/ │ │ └── interceptors/ │ └── extensions/ # Your custom code ├── migrations/ # Database migrations ├── test/ # Test files └── package.json

Generation Philosophy

You Own the Code

Generated code is standard NestJS/TypeORM code. There’s no APSO runtime dependency. You can eject at any time.

Principles

  1. No vendor lock-in — Code works without APSO after generation
  2. Idiomatic patterns — Standard NestJS/FastAPI/Go conventions
  3. Production-ready — Proper error handling, validation, logging
  4. Extensible — Clear extension points for custom logic

Generated Components

Entities

TypeORM entities with decorators matching your schema:

src/entities/customer.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from 'typeorm'; import { Order } from './order.entity'; @Entity('customers') export class Customer { @PrimaryGeneratedColumn('uuid') id: string; @Column({ type: 'varchar', length: 255 }) name: string; @Column({ type: 'varchar', unique: true }) email: string; @Column({ type: 'varchar', default: 'active' }) status: 'active' | 'inactive' | 'churned'; @Column({ type: 'timestamp with time zone', name: 'organization_id' }) organizationId: string; @OneToMany(() => Order, (order) => order.customer) orders: Order[]; @Column({ type: 'timestamp with time zone', default: () => 'CURRENT_TIMESTAMP' }) createdAt: Date; @Column({ type: 'timestamp with time zone', default: () => 'CURRENT_TIMESTAMP' }) updatedAt: Date; }

Controllers

RESTful controllers with full CRUD operations:

src/modules/customer/customer.controller.ts
@Controller('customers') @UseGuards(AuthGuard, OrganizationGuard) export class CustomerController { constructor(private readonly customerService: CustomerService) {} @Get() async findAll(@Query() query: FindCustomersDto, @Org() org: Organization) { return this.customerService.findAll(org.id, query); } @Get(':id') async findOne(@Param('id') id: string, @Org() org: Organization) { return this.customerService.findOne(org.id, id); } @Post() async create(@Body() dto: CreateCustomerDto, @Org() org: Organization) { return this.customerService.create(org.id, dto); } @Patch(':id') async update( @Param('id') id: string, @Body() dto: UpdateCustomerDto, @Org() org: Organization ) { return this.customerService.update(org.id, id, dto); } @Delete(':id') async remove(@Param('id') id: string, @Org() org: Organization) { return this.customerService.remove(org.id, id); } }

Services

Business logic with built-in organization scoping:

src/modules/customer/customer.service.ts
@Injectable() export class CustomerService { constructor( @InjectRepository(Customer) private readonly customerRepository: Repository<Customer>, ) {} async findAll(organizationId: string, query: FindCustomersDto) { const qb = this.customerRepository .createQueryBuilder('customer') .where('customer.organizationId = :organizationId', { organizationId }); // Apply filters if (query.status) { qb.andWhere('customer.status = :status', { status: query.status }); } // Apply pagination return qb .skip(query.offset || 0) .take(query.limit || 20) .getManyAndCount(); } // ... other methods }

DTOs

Validated data transfer objects:

src/modules/customer/dto/create-customer.dto.ts
import { IsString, IsEmail, IsOptional, IsEnum } from 'class-validator'; export class CreateCustomerDto { @IsString() name: string; @IsEmail() email: string; @IsOptional() @IsEnum(['active', 'inactive', 'churned']) status?: 'active' | 'inactive' | 'churned'; }

Regeneration

You can regenerate code without losing custom work:

# Regenerate after schema changes apso server scaffold

Safe Regeneration

  • extensions/ directory is never overwritten
  • Custom imports/modifications outside extensions will be lost
  • Always commit before regenerating

Available Frameworks

FrameworkCommandStatus
TypeScript/NestJSapso server scaffoldStable
Python/FastAPIapso server scaffold --template pythonBeta
Go/Ginapso server scaffold --template goAlpha

Next Steps

Last updated on