Skip to content

Controladores REST

Los controladores REST te permiten exponer APIs HTTP desde tu aplicación Wabot. Cada controlador se resuelve en un contenedor hijo por request, lo que permite inyectar servicios con estado aislado.

Conceptos clave

  • @restController(path): registra una clase como controlador REST bajo una ruta base.
  • @onGet(), @onPost(), @onPut(), @onDelete(): definen endpoints HTTP.
  • Validación automática: el body, query params y path params se combinan y validan contra la clase del primer parámetro.
  • @middleware(): aplica middlewares a endpoints específicos.
  • RestRequest: clase base para acceder a headers HTTP.

Paso 1: Definir un controlador

import { restController, onGet, onPost, onPut, onDelete } from '@wabot-dev/framework'
@restController('/api/products')
export class ProductController {
constructor(private productService: ProductService) {}
@onGet()
async list() {
return await this.productService.findAll()
}
@onGet('/:id')
async findById(req: FindByIdRequest) {
return await this.productService.findById(req.id)
}
@onPost()
async create(req: CreateProductRequest) {
return await this.productService.create(req)
}
@onPut('/:id')
async update(req: UpdateProductRequest) {
return await this.productService.update(req.id, req)
}
@onDelete('/:id')
async remove(req: FindByIdRequest) {
await this.productService.delete(req.id)
return { message: 'Producto eliminado' }
}
}

Paso 2: Definir clases de request

Los parámetros de ruta (:id), query params y body se combinan en un solo objeto y se validan automáticamente:

import { isString, isNumber, isNotEmpty, min, isOptional } from '@wabot-dev/framework'
export class FindByIdRequest {
@isString()
@isNotEmpty()
id?: string
}
export class CreateProductRequest {
@isString()
@isNotEmpty()
name?: string
@isNumber()
@min(0)
price?: number
@isString()
@isOptional()
description?: string
}
export class UpdateProductRequest {
@isString()
@isNotEmpty()
id?: string // Viene de :id en la ruta
@isString()
@isOptional()
name?: string
@isNumber()
@min(0)
@isOptional()
price?: number
}

Paso 3: Acceder a headers

Extiende RestRequest para acceder a los headers de la petición:

import { RestRequest, isString, isNotEmpty } from '@wabot-dev/framework'
export class AuthenticatedRequest extends RestRequest {
@isString()
@isNotEmpty()
id?: string
}
// En el controlador:
@onGet('/:id')
async findById(req: AuthenticatedRequest) {
const apiKey = req.headers?.['x-api-key']
// ...
}

Paso 4: Middlewares

Los middlewares implementan IMiddleware y se aplican con el decorador @middleware():

import { injectable, middleware, IMiddleware } from '@wabot-dev/framework'
import type { Request, Response } from 'express'
import type { DependencyContainer } from 'tsyringe'
@injectable()
export class LogMiddleware implements IMiddleware {
async handle(req: Request, res: Response, container: DependencyContainer): Promise<void> {
console.log(`${req.method} ${req.path}`)
}
}
@injectable()
export class ApiKeyMiddleware implements IMiddleware {
async handle(req: Request, res: Response, container: DependencyContainer): Promise<void> {
const apiKey = req.headers['x-api-key']
if (!apiKey || apiKey !== process.env.API_KEY) {
res.status(401).json({ error: 'API key inválida' })
throw new Error('Unauthorized')
}
}
}

Aplicar middlewares a endpoints:

import { restController, onGet, onPost, middleware } from '@wabot-dev/framework'
@restController('/api/products')
export class ProductController {
@onGet()
async list() {
// Endpoint público
return []
}
@onPost()
@middleware(ApiKeyMiddleware)
async create(req: CreateProductRequest) {
// Endpoint protegido con API key
return { created: true }
}
}

Paso 5: Configuración de endpoints

Los decoradores de verbo aceptan configuración opcional:

@onPost({
path: '/upload',
disableJsonParser: true, // Deshabilita el parser JSON
disableUrlEncodedParser: true, // Deshabilita el parser URL-encoded
})
async upload(req: UploadRequest) {
// Manejar upload personalizado
}

Paso 6: Registrar controladores

Para activar tus controladores REST, regístralos en el punto de entrada de tu aplicación:

import { runRestControllers } from '@wabot-dev/framework'
import { ProductController } from '@/controllers/ProductController'
import { UserController } from '@/controllers/UserController'
runRestControllers([ProductController, UserController])

Ejemplo completo

Un CRUD completo para gestión de recursos:

import {
restController, onGet, onPost, onPut, onDelete, middleware,
isString, isNumber, isNotEmpty, isOptional, min, isIn,
singleton, injectable, runRestControllers,
} from '@wabot-dev/framework'
// --- Requests ---
export class ListProductsRequest {
@isString()
@isOptional()
category?: string
@isNumber()
@min(1)
@isOptional()
page?: number
}
export class CreateProductRequest {
@isString()
@isNotEmpty()
name?: string
@isNumber()
@min(0)
price?: number
@isString()
@isIn(['electronics', 'clothing', 'food', 'other'])
category?: string
}
export class UpdateProductRequest {
@isString()
@isNotEmpty()
id?: string
@isString()
@isOptional()
name?: string
@isNumber()
@min(0)
@isOptional()
price?: number
}
export class DeleteProductRequest {
@isString()
@isNotEmpty()
id?: string
}
// --- Servicio ---
@singleton()
export class ProductService {
private products: Map<string, any> = new Map()
findAll(category?: string) {
const all = Array.from(this.products.values())
return category ? all.filter(p => p.category === category) : all
}
findById(id: string) {
return this.products.get(id)
}
create(data: CreateProductRequest) {
const id = crypto.randomUUID()
const product = { id, ...data }
this.products.set(id, product)
return product
}
update(id: string, data: UpdateProductRequest) {
const product = this.products.get(id)
if (!product) throw new Error('Producto no encontrado')
Object.assign(product, data)
return product
}
delete(id: string) {
this.products.delete(id)
}
}
// --- Controlador ---
@restController('/api/products')
export class ProductController {
constructor(private service: ProductService) {}
@onGet()
async list(req: ListProductsRequest) {
return this.service.findAll(req.category)
}
@onGet('/:id')
async findById(req: DeleteProductRequest) {
const product = this.service.findById(req.id!)
if (!product) throw new Error('Producto no encontrado')
return product
}
@onPost()
@middleware(ApiKeyMiddleware)
async create(req: CreateProductRequest) {
return this.service.create(req)
}
@onPut('/:id')
@middleware(ApiKeyMiddleware)
async update(req: UpdateProductRequest) {
return this.service.update(req.id!, req)
}
@onDelete('/:id')
@middleware(ApiKeyMiddleware)
async remove(req: DeleteProductRequest) {
this.service.delete(req.id!)
return { message: 'Producto eliminado' }
}
}
// --- Registro ---
runRestControllers([ProductController])

Siguiente paso

Aprende a crear controladores WebSocket para comunicación en tiempo real: Controladores Socket.