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.