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.jsonGeneration 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
- No vendor lock-in — Code works without APSO after generation
- Idiomatic patterns — Standard NestJS/FastAPI/Go conventions
- Production-ready — Proper error handling, validation, logging
- 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 scaffoldSafe Regeneration
extensions/directory is never overwritten- Custom imports/modifications outside extensions will be lost
- Always commit before regenerating
Available Frameworks
| Framework | Command | Status |
|---|---|---|
| TypeScript/NestJS | apso server scaffold | Stable |
| Python/FastAPI | apso server scaffold --template python | Beta |
| Go/Gin | apso server scaffold --template go | Alpha |
Next Steps
Last updated on