Saltearse al contenido

Guía de Decoradores

Guía completa para usar decoradores en Veloce.

Veloce usa decoradores de TypeScript para proporcionar una API limpia y declarativa para definir rutas, validación y dependencias. Los decoradores son funciones especiales que pueden modificar clases, métodos y parámetros en tiempo de diseño.

Para usar decoradores, habilítalos en tu tsconfig.json:

{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}

Marca una clase como controlador y opcionalmente establece un prefijo de ruta para todas las rutas.

@Controller('/api/users')
class UserController {
@Get('/')
async list() {
// Ruta: GET /api/users/
return [];
}
@Get('/:id')
async get(@Param('id') id: string) {
// Ruta: GET /api/users/:id
return { id };
}
}

Sin prefijo:

@Controller()
class RootController {
@Get('/health')
async health() {
// Ruta: GET /health
return { status: 'ok' };
}
}

Marca una clase como manejador de WebSocket.

@WebSocket('/chat')
class ChatHandler {
@OnConnect()
handleConnect(client: WebSocketConnection) {
console.log('Cliente conectado');
}
@OnMessage()
handleMessage(client: WebSocketConnection, message: any) {
client.broadcast(message);
}
}

Marca una clase como resolver de GraphQL.

@Resolver()
class UserResolver {
@Query()
async users() {
return [];
}
@Mutation()
async createUser(@Arg('input', UserInput) input: any) {
return { id: 1, ...input };
}
}
@Controller('/users')
class UserController {
@Get()
async list() {
// GET /users
return [];
}
@Get('/:id')
async get(@Param('id') id: string) {
// GET /users/:id
return { id };
}
@Get('/search')
async search(@Query() query: any) {
// GET /users/search?q=...
return [];
}
}
@Post()
async create(@Body(UserSchema) user: User) {
// POST /users
return user;
}
@Post('/bulk')
async createMany(@Body(z.array(UserSchema)) users: User[]) {
// POST /users/bulk
return users;
}
@Put('/:id')
async update(
@Param('id') id: string,
@Body(UserSchema) user: User
) {
// PUT /users/:id
return { id, ...user };
}
@Delete('/:id')
async delete(@Param('id') id: string) {
// DELETE /users/:id
return { success: true };
}
@Patch('/:id')
async patch(
@Param('id') id: string,
@Body(PartialUserSchema) data: Partial<User>
) {
// PATCH /users/:id
return { id, ...data };
}

Responde a todos los métodos HTTP.

@All('/webhook')
async webhook() {
// Maneja GET, POST, PUT, DELETE, etc.
return { received: true };
}

Extrae y valida el cuerpo de la petición.

const UserSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
age: z.number().min(18).optional(),
});
@Post('/users')
async createUser(@Body(UserSchema) user: z.infer<typeof UserSchema>) {
// user está validado y tipado
return user;
}

Extrae y valida parámetros de consulta.

const SearchSchema = z.object({
q: z.string().min(1),
page: z.string().transform(Number).default('1'),
limit: z.string().transform(Number).default('10'),
});
@Get('/search')
async search(@Query(SearchSchema) query: z.infer<typeof SearchSchema>) {
return {
query: query.q,
page: query.page,
limit: query.limit,
};
}

Extrae parámetros de ruta.

@Get('/users/:id')
async getUser(@Param('id') id: string) {
return { id };
}
@Get('/users/:userId/posts/:postId')
async getPost(
@Param('userId') userId: string,
@Param('postId') postId: string
) {
return { userId, postId };
}

Extrae y valida encabezados.

@Get('/protected')
async getProtected(@Header('authorization') auth: string) {
return { auth };
}

Extrae y valida cookies.

@Get('/profile')
async getProfile(@Cookie('session') sessionId: string) {
return { sessionId };
}

Inyecta dependencias.

class DatabaseService {
async getUsers() {
return [];
}
}
@Controller('/users')
class UserController {
@Get('/')
async list(@Depends(DatabaseService) db: DatabaseService) {
return await db.getUsers();
}
}
@Post('/users/:id/posts')
async createPost(
@Param('id') userId: string,
@Body(PostSchema) post: Post,
@Header('authorization') auth: string,
@Depends(DatabaseService) db: DatabaseService
) {
// Todos los parámetros son extraídos y validados
return await db.createPost(userId, post);
}
// ✓ Bueno
const UserSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
});
@Post('/users')
async createUser(@Body(UserSchema) user: z.infer<typeof UserSchema>) {
return user;
}
// ✗ Malo
@Post('/users')
async createUser(@Body() user: any) {
return user;
}
// ✓ Bueno
const UserSchema = z.object({
name: z.string(),
email: z.string().email(),
});
type User = z.infer<typeof UserSchema>;
@Post('/users')
async createUser(@Body(UserSchema) user: User) {
// user está completamente tipado
}
// ✓ Bueno - Un controlador por recurso
@Controller('/users')
class UserController {
// Todas las rutas relacionadas con usuarios
}
@Controller('/posts')
class PostController {
// Todas las rutas relacionadas con posts
}
// ✓ Bueno
class UserService {
async getUsers() {
return [];
}
}
@Controller('/users')
class UserController {
@Get('/')
async list(@Depends(UserService) userService: UserService) {
return await userService.getUsers();
}
}
@Get('/users/:id')
async getUser(@Param('id') id: string) {
const user = await db.getUserById(id);
if (!user) {
throw new HTTPException(404, 'Usuario no encontrado');
}
return user;
}