Appearance
Add a NestJS API Module
Add a new feature module to services/api following the conventions used across the codebase.
Module structure
Each feature module lives in services/api/src/modules/<feature>/ and contains:
note/
├── note.module.ts
├── note.controller.ts
├── note.service.ts
└── dto/
├── create-note.dto.ts
└── update-note.dto.tsStep 1 — Create the module files
note.module.ts
typescript
import { Module } from '@nestjs/common'
import { NoteController } from './note.controller'
import { NoteService } from './note.service'
@Module({
controllers: [NoteController],
providers: [NoteService],
})
export class NoteModule {}note.service.ts
typescript
import { Injectable } from '@nestjs/common'
import { PrismaService } from '../../prisma/prisma.service'
@Injectable()
export class NoteService {
constructor(private readonly prisma: PrismaService) {}
// ...
}note.controller.ts
typescript
import { Controller, Get } from '@nestjs/common'
import { ApiTags } from '@nestjs/swagger'
import { NoteService } from './note.service'
import { CheckAbility } from '../../casl/decorators'
import { TenantId } from '../../auth/decorators'
@ApiTags('Notes')
@Controller('tenants/:tenantId/notes')
export class NoteController {
constructor(private readonly noteService: NoteService) {}
@Get()
@CheckAbility({ action: 'read', subject: 'Entry' })
findAll(@TenantId() tenantId: string) {
return this.noteService.findAll(tenantId)
}
}Step 2 — Create DTOs
Use class-validator decorators to validate incoming request bodies:
typescript
// dto/create-note.dto.ts
import { IsString, MinLength } from 'class-validator'
import { ApiProperty } from '@nestjs/swagger'
export class CreateNoteDto {
@ApiProperty()
@IsString()
@MinLength(1)
content: string
}Step 3 — Register the module in AppModule
Open services/api/src/app.module.ts and add your new module to the imports array:
typescript
import { NoteModule } from './modules/note/note.module'
@Module({
imports: [
// ... existing modules
NoteModule,
],
})
export class AppModule {}Step 4 — Apply guards correctly
Every tenant-scoped route must be protected by both authentication (handled globally by AuthGuard) and authorization. Use @CheckAbility to specify which CASL ability is required:
typescript
@CheckAbility({ action: 'manage', subject: 'Entry' })The TenantGuard (applied globally) resolves the caller's role and builds the CASL ability automatically. See the Permissions reference for available actions and subjects.
Step 5 — Regenerate the SDK
After starting the API (pnpm dev:api), regenerate the typed frontend client:
bash
pnpm openapiYour new endpoints are now available in src/api/.