Custom Endpoints
While APSO generates standard CRUD endpoints, you’ll often need custom endpoints for specific business logic. This guide shows how to add them.
Using the Extensions Pattern
Add custom controllers in src/extensions/controllers/:
// src/extensions/controllers/analytics.controller.ts
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from '../../common/guards/auth.guard';
import { CurrentUser } from '../../common/decorators/current-user.decorator';
import { User } from '../../entities/user.entity';
@Controller('analytics')
@UseGuards(AuthGuard)
export class AnalyticsController {
@Get('dashboard')
async getDashboard(@CurrentUser() user: User) {
return {
projectCount: 10,
taskCount: 45,
completedTasks: 30,
};
}
}Controller Decorators
HTTP Methods
import { Get, Post, Put, Patch, Delete } from '@nestjs/common';
@Get('items') // GET /items
@Post('items') // POST /items
@Put('items/:id') // PUT /items/:id
@Patch('items/:id') // PATCH /items/:id
@Delete('items/:id') // DELETE /items/:idRoute Parameters
import { Param, Query, Body, Headers } from '@nestjs/common';
@Get(':id')
async getById(@Param('id') id: string) {
// Access route parameter
}
@Get()
async search(@Query('q') query: string) {
// Access query parameter
}
@Post()
async create(@Body() dto: CreateDto) {
// Access request body
}
@Get()
async withAuth(@Headers('authorization') auth: string) {
// Access headers
}Response Handling
import { Res, HttpStatus, HttpCode } from '@nestjs/common';
import { Response } from 'express';
@Post()
@HttpCode(HttpStatus.CREATED)
async create(@Body() dto: CreateDto) {
// Returns 201 status
}
@Get('download')
async download(@Res() res: Response) {
res.setHeader('Content-Type', 'application/pdf');
res.send(pdfBuffer);
}Adding Custom Services
Create services for business logic:
// src/extensions/services/reports.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Project } from '../../entities/project.entity';
import { Task } from '../../entities/task.entity';
@Injectable()
export class ReportsService {
constructor(
@InjectRepository(Project)
private projectRepo: Repository<Project>,
@InjectRepository(Task)
private taskRepo: Repository<Task>,
) {}
async getProjectSummary(organizationId: string) {
const [projects, projectCount] = await this.projectRepo.findAndCount({
where: { organizationId },
});
const taskCount = await this.taskRepo.count({
where: { project: { organizationId } },
});
return {
projectCount,
taskCount,
projects: projects.slice(0, 5),
};
}
}Use in controllers:
// src/extensions/controllers/reports.controller.ts
@Controller('reports')
@UseGuards(AuthGuard)
export class ReportsController {
constructor(private readonly reportsService: ReportsService) {}
@Get('summary')
async getSummary(@CurrentUser() user: User) {
return this.reportsService.getProjectSummary(user.organizationId);
}
}Registering Extensions
Register your custom code in the extensions module:
// src/extensions/index.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Project } from '../entities/project.entity';
import { Task } from '../entities/task.entity';
import { AnalyticsController } from './controllers/analytics.controller';
import { ReportsController } from './controllers/reports.controller';
import { ReportsService } from './services/reports.service';
@Module({
imports: [
TypeOrmModule.forFeature([Project, Task]),
],
controllers: [
AnalyticsController,
ReportsController,
],
providers: [
ReportsService,
],
})
export class ExtensionsModule {}Validation
Add validation to DTOs:
// src/extensions/dto/generate-report.dto.ts
import { IsDateString, IsEnum, IsOptional } from 'class-validator';
enum ReportFormat {
PDF = 'pdf',
CSV = 'csv',
JSON = 'json',
}
export class GenerateReportDto {
@IsDateString()
startDate: string;
@IsDateString()
endDate: string;
@IsEnum(ReportFormat)
@IsOptional()
format?: ReportFormat = ReportFormat.JSON;
}Use in controller:
@Post('generate')
async generate(@Body() dto: GenerateReportDto) {
// dto is validated automatically
}Authentication & Authorization
Using Guards
import { UseGuards } from '@nestjs/common';
import { AuthGuard } from '../../common/guards/auth.guard';
import { RolesGuard } from '../../common/guards/roles.guard';
import { Roles } from '../../common/decorators/roles.decorator';
@Controller('admin')
@UseGuards(AuthGuard, RolesGuard)
export class AdminController {
@Get('users')
@Roles('admin')
async getUsers() {
// Only admins can access
}
}Creating Custom Guards
// src/extensions/guards/api-key.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
@Injectable()
export class ApiKeyGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const apiKey = request.headers['x-api-key'];
return this.validateApiKey(apiKey);
}
private validateApiKey(key: string): boolean {
// Your validation logic
return key === process.env.INTERNAL_API_KEY;
}
}Swagger Documentation
Document your endpoints:
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
@Controller('reports')
@ApiTags('Reports')
@ApiBearerAuth()
export class ReportsController {
@Get('summary')
@ApiOperation({ summary: 'Get project summary' })
@ApiResponse({ status: 200, description: 'Summary data' })
@ApiResponse({ status: 401, description: 'Unauthorized' })
async getSummary() {
// ...
}
}Error Handling
Throw appropriate exceptions:
import {
NotFoundException,
BadRequestException,
ForbiddenException,
} from '@nestjs/common';
@Get(':id')
async getReport(@Param('id') id: string, @CurrentUser() user: User) {
const report = await this.reportsService.findOne(id);
if (!report) {
throw new NotFoundException('Report not found');
}
if (report.organizationId !== user.organizationId) {
throw new ForbiddenException('Access denied');
}
return report;
}Related
Last updated on