En esta página

Controladores y rutas HTTP

15 min lectura TextoCap. 1 — Fundamentos de NestJS

Los controladores en NestJS

Los controladores son la capa de entrada de tu API. Son responsables de recibir peticiones HTTP, delegar el trabajo pesado a los servicios y devolver una respuesta apropiada. Un principio clave: los controladores no deben contener lógica de negocio. Su única responsabilidad es mapear peticiones HTTP a llamadas de servicio.

Declarando un controlador

El decorador @Controller() acepta un prefijo de ruta opcional. Todas las rutas definidas dentro de la clase tendrán ese prefijo automáticamente:

@Controller('api/v1/usuarios') // todas las rutas empiezan con /api/v1/usuarios
export class UsuariosController {}

También puedes usar @Controller({ path: 'usuarios', version: '1' }) si usas el versionado de APIs de NestJS.

Verbos HTTP y rutas

NestJS proporciona decoradores para todos los verbos HTTP estándar:

Decorador Verbo HTTP Uso típico
@Get() GET Leer recursos
@Post() POST Crear recursos
@Put() PUT Reemplazar recursos completos
@Patch() PATCH Actualizar parcialmente
@Delete() DELETE Eliminar recursos
@Options() OPTIONS Preflight CORS
@Head() HEAD Metadatos sin cuerpo
@All() Todos Catch-all para cualquier verbo

Cada decorador acepta una ruta relativa al prefijo del controlador:

@Controller('articulos')
export class ArticulosController {
  @Get()             // GET /articulos
  @Get(':id')        // GET /articulos/42
  @Get(':id/tags')   // GET /articulos/42/tags
  @Post()            // POST /articulos
  @Delete(':id')     // DELETE /articulos/42
}

Extrayendo datos de la petición

@Param — Parámetros de ruta

@Get(':categoria/:id')
findOne(
  @Param('categoria') categoria: string,
  @Param('id') id: string,
) {
  return { categoria, id };
}

// Para obtener todos los parámetros como objeto:
@Get(':categoria/:id')
findOne(@Param() params: Record<string, string>) {
  return params; // { categoria: 'ropa', id: '42' }
}

@Query — Parámetros de consulta

// GET /productos?pagina=2&limite=20&orden=precio_asc
@Get()
findAll(
  @Query('pagina') pagina = '1',
  @Query('limite') limite = '20',
  @Query('orden') orden?: string,
) {
  return this.service.findAll({ pagina, limite, orden });
}

@Body — Cuerpo de la petición

@Post()
create(@Body() crearDto: CrearUsuarioDto) {
  return this.service.create(crearDto);
}

// Extraer solo una propiedad del cuerpo:
@Patch(':id/estado')
cambiarEstado(
  @Param('id') id: string,
  @Body('activo') activo: boolean,
) {
  return this.service.cambiarEstado(id, activo);
}

@Headers — Cabeceras HTTP

@Get()
findAll(@Headers('authorization') auth: string) {
  // Lectura manual de cabeceras (para casos especiales)
  return this.service.findAll();
}

Data Transfer Objects (DTOs)

Los DTOs son clases simples de TypeScript que definen la forma esperada de los datos de entrada. Son fundamentales para tener tipado estricto y son la base para la validación automática que veremos en la lección de Pipes.

// dto/crear-usuario.dto.ts
export class CrearUsuarioDto {
  nombre: string;
  email: string;
  password: string;
  rol?: 'usuario' | 'admin';
}

La convención de nombres en NestJS es CrearXxxDto para creación y ActualizarXxxDto para actualizaciones. Muchos proyectos usan el paquete @nestjs/mapped-types para reutilizar DTOs:

import { PartialType } from '@nestjs/mapped-types';
import { CrearUsuarioDto } from './crear-usuario.dto';

// ActualizarUsuarioDto tiene todas las propiedades de CrearUsuarioDto pero opcionales
export class ActualizarUsuarioDto extends PartialType(CrearUsuarioDto) {}

Códigos de estado HTTP

Por defecto, NestJS devuelve 200 OK para GET, PUT, PATCH, DELETE y 201 Created para POST. Para personalizar el código de estado, usa @HttpCode:

@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT) // 204
remove(@Param('id') id: string) {
  return this.service.remove(id);
}

@Post('login')
@HttpCode(HttpStatus.OK) // 200 en lugar del 201 por defecto
login(@Body() loginDto: LoginDto) {
  return this.authService.login(loginDto);
}

HttpStatus es un enum de NestJS con todos los códigos de estado HTTP estándar, lo que mejora la legibilidad respecto a usar números directamente.

Manejo de rutas comodín

NestJS soporta wildcards en las rutas usando la sintaxis de Express:

@Get('ab*cd')
findAll() {
  return 'Esta ruta responde a abcd, ab_cd, abecd, etc.';
}

// Parámetros opcionales
@Get(':id?')
findOne(@Param('id') id?: string) {
  return id ? this.service.findOne(id) : this.service.findAll();
}

Prefijos globales de ruta

En lugar de poner /api/v1 en cada controlador, puedes configurar un prefijo global en main.ts:

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.setGlobalPrefix('api/v1'); // todas las rutas tendrán este prefijo
  await app.listen(3000);
}

Si necesitas excluir algunas rutas del prefijo global (como /health o /):

app.setGlobalPrefix('api/v1', {
  exclude: [{ path: 'health', method: RequestMethod.GET }],
});

Respuestas asíncronas

NestJS maneja promesas y observables de RxJS de forma transparente. Puedes devolver una promesa o un Observable directamente desde un método de controlador:

@Get()
async findAll(): Promise<Producto[]> {
  return this.service.findAll(); // retorna una Promise<Producto[]>
}

@Get(':id')
findOne(@Param('id') id: string): Observable<Producto> {
  return this.service.findOne(id); // retorna un Observable<Producto>
}

NestJS resolverá automáticamente la promesa o el observable antes de enviar la respuesta al cliente.

Versionado de API

NestJS tiene soporte integrado para versionar tu API de varias formas:

// main.ts — activar versionado por URI
import { VersioningType } from '@nestjs/common';

app.enableVersioning({
  type: VersioningType.URI,
});

// En el controlador:
@Controller({ path: 'usuarios', version: '1' })
export class UsuariosV1Controller {}

@Controller({ path: 'usuarios', version: '2' })
export class UsuariosV2Controller {}

Esto generará rutas como /v1/usuarios y /v2/usuarios automáticamente.

Los controladores son la puerta de entrada a tu API, pero solos no hacen mucho. En la próxima lección aprenderemos sobre los providers y servicios —el lugar donde realmente vive la lógica de negocio de tu aplicación.

ParseIntPipe vs conversión manual
Siempre usa `ParseIntPipe` para parámetros numéricos de ruta en lugar de llamar `parseInt()` manualmente. Si el parámetro no es un número válido, `ParseIntPipe` lanza automáticamente un `BadRequestException` con un mensaje descriptivo, sin necesidad de código adicional.
No mezcles @Res() con el flujo automático
Cuando inyectas `@Res()` en un método, NestJS desactiva la serialización automática de la respuesta. Debes llamar `res.send()` o `res.json()` manualmente. Si necesitas el objeto response para algo específico como establecer cookies, usa `@Res({ passthrough: true })` para mantener el flujo automático de NestJS.