| name | vendix-backend-api |
| description | API endpoint patterns. |
| metadata | {"scope":["root"],"auto_invoke":"Creating API endpoints"} |
Vendix Backend API Patterns
API Response Pattern - Respuestas estandarizadas, DTOs y estructura de controladores.
🎯 Standard API Response
ResponseService
File: common/responses/response.service.ts
import { Injectable } from '@nestjs/common';
interface SuccessResponse<T> {
success: true;
data: T;
message?: string;
meta?: any;
}
interface ErrorResponse {
success: false;
error: {
message: string;
code?: string;
details?: any;
};
}
@Injectable()
export class ResponseService {
success<T>(data: T, message?: string, meta?: any): SuccessResponse<T> {
return {
success: true,
data,
message,
meta,
};
}
error(message: string, code?: string, details?: any): ErrorResponse {
return {
success: false,
error: {
message,
code,
details,
},
};
}
paginated<T>(data: T[], meta: PaginationMeta) {
return this.success(data, undefined, { pagination: meta });
}
}
interface PaginationMeta {
total: number;
page: number;
limit: number;
total_pages: number;
}
metadata:
scope: [root]
auto_invoke: "Creating API endpoints"
📦 Controller Pattern
Standard Controller Structure
import { Controller, Get, Post, Put, Delete, Body, Param, Query } from '@nestjs/common';
import { {Domain}Service } from './{domain}.service';
import { ResponseService } from '@/common/responses/response.service';
import { {Action}Dto } from './dto/{action}-dto.dto';
import { Public } from '@/common/decorators/public.decorator';
import { Permissions } from '@/common/decorators/permissions.decorator';
@Controller('domains/:domain_id/{resource}')
export class {Resource}Controller {
constructor(
private readonly {resource}_service: {Resource}Service,
private readonly response_service: ResponseService,
) {}
@Get()
() {
result = .{resource}_service.(query_dto);
..(result., result.);
}
()
() {
result = .{resource}_service.(+id);
..(result);
}
()
()
() {
result = .{resource}_service.(create_dto);
..(result, );
}
()
()
() {
result = .{resource}_service.(+id, update_dto);
..(result, );
}
()
()
() {
.{resource}_service.(+id);
..(, );
}
}
📝 DTO Patterns
Create DTO
import { IsString, IsEmail, IsOptional, MinLength, IsNumber, IsBoolean } from 'class-validator';
export class CreateUserDto {
@IsString()
@MinLength(3)
user_name: string;
@IsEmail()
email: string;
@IsString()
@MinLength(8)
password: string;
@IsOptional()
@IsString()
phone_number?: string;
@IsOptional()
@IsBoolean()
is_active?: boolean = true;
}
Update DTO (Partial)
import { PartialType } from '@nestjs/mapped-types';
import { CreateUserDto } from './create-user.dto';
export class UpdateUserDto extends PartialType(CreateUserDto) {}
Query DTO
import { IsOptional, IsString, IsNumber, IsBoolean } from 'class-validator';
import { Type } from 'class-transformer';
export class QueryUserDto {
@IsOptional()
@IsString()
search?: string;
@IsOptional()
@Type(() => Number)
@IsNumber()
page?: number = 1;
@IsOptional()
@Type(() => Number)
@IsNumber()
limit?: number = 10;
@IsOptional()
@IsString()
sort_by?: string = 'created_at';
@IsOptional()
@IsString()
sort_order?: 'asc' | 'desc' = 'desc';
@IsOptional()
@IsBoolean()
include_deleted?: boolean = false;
}
🔄 Service Response Pattern
Standard Service Methods
@Injectable()
export class UserService {
constructor(
private readonly prisma: EcommercePrismaService,
private readonly response_service: ResponseService,
) {}
async findAll(query: QueryUserDto) {
const { page, limit, search, sort_by, sort_order } = query;
const skip = (page - 1) * limit;
const where = {
...this.prisma.organizationWhere,
...(search && {
OR: [
{ user_name: { contains: search, mode: 'insensitive' } },
{ email: { contains: search, mode: 'insensitive' } },
],
}),
};
const [data, total] = await Promise.all([
this.prisma.users.findMany({
where,
skip,
take: limit,
orderBy: { [sort_by]: sort_order },
}),
this.prisma.users.count({ where }),
]);
{
data,
: {
total,
page,
limit,
: .(total / limit),
},
};
}
() {
user = ...({
: {
id,
.....,
},
});
(!user) {
();
}
user;
}
() {
existing = ...({
: { : create_dto. },
});
(existing) {
();
}
hashed_password = bcrypt.(create_dto., );
user = ...({
: {
...create_dto,
: hashed_password,
: ...,
},
: {
: ,
: ,
: ,
: ,
: ,
: ,
},
});
user;
}
() {
.(id);
(update_dto.) {
update_dto. = bcrypt.(update_dto., );
}
user = ...({
: { id },
: update_dto,
: {
: ,
: ,
: ,
: ,
: ,
: ,
},
});
user;
}
() {
.(id);
...({
: { id },
});
}
}
🌐 HTTP Status Codes
Standard Status Codes
| Scenario | Status Code | Description |
|---|
| Success | 200 | Request succeeded |
| Created | 201 | Resource created |
| No Content | 204 | Success, no response body |
| Bad Request | 400 | Invalid input |
| Unauthorized | 401 | Not authenticated |
| Forbidden | 403 | Authenticated but not authorized |
| Not Found | 404 | Resource not found |
| Conflict | 409 | Resource conflict (duplicate) |
| Unprocessable Entity | 422 | Validation error |
| Internal Server Error | 500 | Server error |
Usage in Controllers
@Post()
@HttpCode(HttpStatus.CREATED)
async create(@Body() dto: CreateDto) {
return this.service.create(dto);
}
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
async remove(@Param('id') id: string) {
await this.service.remove(+id);
}
🎯 Error Handling
Exception Filters
File: common/filters/http-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Response } from 'express';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const status = exception.getStatus();
const exception_response = exception.getResponse();
const error_response = {
success: false,
error: {
message: exception.message,
code: exception.name,
details: exception_response,
},
};
response.status(status).json(error_response);
}
}
Global Exception Filter
File: main.ts
import { ValidationPipe } from '@nestjs/common';
import { HttpExceptionFilter } from './common/filters/http-exception.filter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
transform: true,
forbidNonWhitelisted: true,
}),
);
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(3000);
}
🔍 Key Files Reference
| File | Purpose |
|---|
common/responses/response.service.ts | Standardized responses |
common/filters/http-exception.filter.ts | Global error handling |
main.ts | Global pipes and filters |
dto/*.dto.ts | Data transfer objects |
Related Skills
vendix-backend-domain - Domain architecture
vendix-backend-auth - Authentication and authorization
vendix-validation - Validation patterns
vendix-naming-conventions - Naming conventions (CRITICAL)