Métodos POST en controladores

Intermedio
Nest
Nest
Actualizado: 05/06/2025

¡Desbloquea el curso completo!

IA
Ejercicios
Certificado
Entrar

Introducción

En el desarrollo de aplicaciones web, además de recuperar información con los métodos GET, es esencial la capacidad de enviar nuevos datos al servidor para su creación o procesamiento. El método HTTP POST está diseñado específicamente para este propósito. A diferencia de GET, las solicitudes POST suelen incluir un cuerpo de mensaje con los datos a enviar y tienen efectos secundarios en el servidor, como la creación de un nuevo recurso. En esta lección, exploraremos cómo implementar y utilizar los métodos POST en los controladores de NestJS para manejar la creación de recursos y el envío de información desde el cliente al servidor.

¿Qué es un método POST?

El método POST se utiliza para enviar datos al servidor para que sean procesados, lo que comúnmente resulta en la creación de un nuevo recurso en la base de datos o en la realización de una acción específica que modifica el estado del servidor. Las solicitudes POST no son idempotentes por naturaleza (repetir la solicitud puede crear múltiples recursos idénticos o realizar la misma acción varias veces), y no son seguras, ya que modifican el estado del servidor.

¿Qué es un DTO (Data Transfer Object)?

Antes de continuar, es importante recordar un concepto clave en el manejo de datos en aplicaciones web: el DTO (Data Transfer Object).

Un DTO es un objeto simple que se utiliza para transferir datos entre diferentes capas de una aplicación o entre la aplicación y el cliente. Su propósito principal es definir la estructura y el tipo de los datos que se esperan o se envían. En el contexto de las APIs RESTful, los DTOs son muy útiles para:

  • Definir la forma del cuerpo de la solicitud (request body) que se recibirá en un método POST (o PUT).
  • Especificar la estructura de la respuesta (response body) que se enviará de vuelta al cliente.
  • Validar los datos entrantes, asegurando que cumplen con las expectativas antes de ser procesados.

En TypeScript, un DTO se representa comúnmente como una interfaz o una clase. Por ejemplo, si esperamos crear un producto con un nombre, una descripción y un precio, podemos definir un DTO CreateProductDto de la siguiente manera:

// src/productos/dto/create-product.dto.ts (ejemplo de ubicación común para DTOs)

interface CreateProductDto {
  nombre: string;
  descripcion: string;
  precio: number;
}

Este DTO nos indica claramente qué propiedades y de qué tipo esperamos para la creación de un producto, mejorando la claridad y la seguridad de nuestra API.

Creación de un controlador POST en NestJS

Para definir una ruta POST en un controlador NestJS, utilizamos el decorador @Post(). Las solicitudes POST a menudo incluyen un cuerpo de datos, que en NestJS se accede mediante el decorador @Body().

Veamos un ejemplo:

// src/productos/productos.controller.ts

import { Controller, Get, Post, Param, Query, Body, HttpStatus, HttpCode } from '@nestjs/common';

// Interfaz para el DTO de creación de producto
interface CreateProductDto {
  nombre: string;
  descripcion: string;
  precio: number;
}

@Controller('productos')
export class ProductosController {
  @Get()
  findAll(): string {
    return 'Esta acción devuelve todos los productos';
  }

  @Get(':id')
  findOne(@Param('id') id: string): string {
    return `Esta acción devuelve el producto con ID: ${id}`;
  }

  @Get('buscar')
  findByCategory(@Query('categoria') categoria: string): string {
    return `Buscando productos en la categoría: ${categoria}`;
  }

  @Post()
  @HttpCode(HttpStatus.CREATED) // Opcional: Define el código de estado HTTP 201 Created
  create(@Body() createProductDto: CreateProductDto): string {
    console.log(createProductDto); // Imprime el objeto recibido en la consola del servidor
    return `Producto "${createProductDto.nombre}" creado exitosamente con precio ${createProductDto.precio}.`;
  }
}

En este ejemplo:

  • interface CreateProductDto: Define la estructura de los datos que esperamos recibir en el cuerpo de la solicitud POST. Esto es una buena práctica para tipar los datos entrantes.
  • @Post(): Indica que este método responderá a las solicitudes HTTP POST. Al igual que con @Get(), si no se especifica una ruta dentro de @Post(), el método manejará las solicitudes POST a la ruta base del controlador, que es /productos.
  • @HttpCode(HttpStatus.CREATED): Este decorador opcional establece el código de estado HTTP de la respuesta a 201 Created. Por defecto, NestJS devuelve un 200 OK para solicitudes POST exitosas. Sin embargo, la convención RESTful para la creación de recursos es 201 Created, lo que indica que la solicitud ha sido exitosa y se ha creado un nuevo recurso.
  • @Body() createProductDto: CreateProductDto: El decorador @Body() extrae el cuerpo completo de la solicitud HTTP. NestJS parsea automáticamente los cuerpos JSON en objetos JavaScript. createProductDto es el nombre de la variable donde se almacenarán los datos del cuerpo, y CreateProductDto es la interfaz que define su tipo.
  • console.log(createProductDto): Muestra el objeto de datos recibido en la consola del servidor, lo cual es útil para la depuración.
  • El método devuelve una cadena de texto confirmando la creación, utilizando los datos recibidos.

Ejemplo de solicitud POST con curl:

curl -X POST -H "Content-Type: application/json" -d '{"nombre": "Laptop Gaming", "descripcion": "Potente laptop para juegos", "precio": 1200.00}' http://localhost:3000/productos

Al ejecutar este comando en nuestra consola CMD, la consola del servidor NestJS mostrará el objeto recibido, y la respuesta en la terminal de curl será: "Producto "Laptop Gaming" creado exitosamente con precio 1200.00.".

Manejo de la Lógica de Negocio y Persistencia

Es importante recordar que, al igual que con los métodos GET, los controladores en NestJS no deben contener la lógica de negocio compleja ni la interacción directa con la base de datos. Esa responsabilidad recae en los servicios. En una aplicación real, el método create del controlador delegaría la tarea de guardar el producto a un servicio inyectado.

Inyección de un Servicio para la creación

Aunque la implementación detallada de un servicio se verá en lecciones futuras, el concepto sería el siguiente:

// src/productos/productos.controller.ts (modificado conceptualmente)

// ... imports ...
import { ProductosService } from './productos.service'; // Asumimos la existencia de este servicio

@Controller('productos')
export class ProductosController {
  constructor(private readonly productosService: ProductosService) {} // Inyección del servicio

  // ... métodos GET ...

  @Post()
  @HttpCode(HttpStatus.CREATED)
  create(@Body() createProductDto: CreateProductDto): any { // La respuesta podría ser el producto creado
    const nuevoProducto = this.productosService.create(createProductDto); // Llamada al servicio
    return nuevoProducto; // Retorna el producto creado (por ejemplo, con un ID generado)
  }
}

En este escenario, el ProductosService contendría el código para interactuar con una base de datos y guardar el createProductDto.

Validación

Al recibir datos mediante una solicitud POST, es crucial asegurarnos de que estos cumplen con el formato y las restricciones esperadas. Sin una validación adecuada, podríamos procesar datos inválidos, lo que llevaría a errores o incluso vulnerabilidades. NestJS se integra muy bien con librerías como class-validator y class-transformer para simplificar esta tarea.

Para usar class-validator y class-transformer, primero debes instalarlos en tu proyecto:

Guarda tu progreso

Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.

Progreso guardado
Asistente IA
Ejercicios
Iniciar sesión gratis

Más de 25.000 desarrolladores ya confían en CertiDevs

npm install class-validator class-transformer
# o
yarn add class-validator class-transformer

Ahora, en tu DTO, puedes añadir decoradores proporcionados por class-validator para definir las reglas de validación.

// src/productos/dto/create-product.dto.ts

import { IsString, IsInt, IsNotEmpty, IsNumber, Min } from 'class-validator';

export class CreateProductDto {
  @IsNotEmpty({ message: 'El nombre no puede estar vacío.' })
  @IsString({ message: 'El nombre debe ser una cadena de texto.' })
  nombre: string;

  @IsNotEmpty({ message: 'La descripción no puede estar vacía.' })
  @IsString({ message: 'La descripción debe ser una cadena de texto.' })
  descripcion: string;

  @IsNotEmpty({ message: 'El precio no puede estar vacío.' })
  @IsNumber({}, { message: 'El precio debe ser un número.' })
  @Min(0, { message: 'El precio no puede ser negativo.' })
  precio: number;
}

Con estos decoradores, el ValidationPipe de NestJS (que configuraremos a continuación) se encargará de verificar que los datos entrantes cumplan estas condiciones antes de que el controlador los procese.

Para que NestJS aplique automáticamente estas reglas de validación a todas las solicitudes que usan DTOs, necesitas configurar un ValidationPipe globalmente en tu archivo principal de la aplicación (main.ts).

// src/main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common'; // ¡Importante!

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe({
    whitelist: true, // Remueve automáticamente propiedades del payload que no están definidas en el DTO
    forbidNonWhitelisted: true, // Si es true, lanza un error si hay propiedades no definidas en el DTO
    transform: true, // Transforma los objetos de entrada en instancias de los DTOs
  }));
  await app.listen(3000);
}
bootstrap();

Con el ValidationPipe configurado, si una solicitud POST a /productos envía datos que no cumplen con CreateProductDto (por ejemplo, nombre vacío o precio no numérico), NestJS automáticamente lanzará una excepción BadRequestException (HTTP 400 Bad Request) con los detalles de la validación.

Manejo de respuestas y errores

Una vez que se ha recibido y validado el cuerpo de la solicitud, es importante responder adecuadamente. NestJS proporciona herramientas para facilitar el manejo de respuestas y errores.

Enviando una Respuesta Exitosa

La forma más sencilla y recomendada en NestJS para enviar una respuesta exitosa es devolver directamente un valor desde el método del controlador. NestJS se encargará de serializar este valor (por ejemplo, a JSON si devuelves un objeto o un arreglo) y enviarlo con el código de estado HTTP adecuado.

Ejemplo: Devolviendo el recurso creado

// src/productos/productos.controller.ts

import { Controller, Post, Body, HttpCode, HttpStatus } from '@nestjs/common';
import { CreateProductDto } from './dto/create-product.dto'; // Importamos nuestro DTO

// Definimos la interfaz del producto que será la respuesta
interface Product {
  id: number;
  nombre: string;
  descripcion: string;
  precio: number;
}

@Controller('productos')
export class ProductosController {
  private readonly products: Product[] = []; // Simulación de una "base de datos"

  // ... métodos GET ...

  @Post()
  @HttpCode(HttpStatus.CREATED) // Código de estado 201 Created para una creación exitosa
  create(@Body() createProductDto: CreateProductDto): Product {
    // Lógica para "guardar" el producto y asignarle un ID único
    const newProduct: Product = {
      id: this.products.length > 0 ? Math.max(...this.products.map(p => p.id)) + 1 : 1,
      ...createProductDto,
    };
    this.products.push(newProduct); // Añadir a nuestra "base de datos" simulada
    return newProduct; // NestJS serializará este objeto a JSON y lo enviará con 201 Created
  }
}

En este caso, si la creación es exitosa, el cliente recibirá el objeto newProduct en formato JSON y un código de estado 201 Created.

Control Explícito de la Respuesta (@Res())

Para un control más preciso sobre la respuesta HTTP (por ejemplo, establecer encabezados personalizados, redirigir, o manejar el flujo de manera muy específica), puedes inyectar el objeto Response de la plataforma subyacente (Express o Fastify) usando el decorador @Res(). Sin embargo, al usar @Res(), te vuelves completamente responsable de enviar la respuesta manualmente, y ya no puedes simplemente devolver con return un valor.

Ejemplo:

// src/productos/productos.controller.ts

import { Controller, Post, Body, Res, HttpStatus } from '@nestjs/common';
import { Response } from 'express'; // Importa el tipo Response de Express si usas Express
import { CreateProductDto } from './dto/create-product.dto';

interface Product {
  id: number;
  nombre: string;
  descripcion: string;
  precio: number;
}

@Controller('productos')
export class ProductosController {
  private readonly products: Product[] = [];

  @Post()
  create(@Body() createProductDto: CreateProductDto, @Res() res: Response) {
    const newProduct: Product = {
      id: this.products.length > 0 ? Math.max(...this.products.map(p => p.id)) + 1 : 1,
      ...createProductDto,
    };
    this.products.push(newProduct);
    // Envía la respuesta explícitamente
    res.status(HttpStatus.CREATED).send(newProduct);
  }
}

Aunque este enfoque proporciona un control máximo, generalmente se recomienda devolver valores directamente siempre que sea posible, ya que es más declarativo y permite que NestJS gestione la mayor parte de la complejidad de la respuesta, facilitando además la integración con interceptors y exception filters (temas avanzados).

Manejo de Errores con HttpException

En cualquier aplicación robusta, es inevitable que ocurran errores. NestJS proporciona un mecanismo de filtrado de excepciones potente que permite un manejo de errores centralizado y consistente. La clase HttpException es la herramienta principal para lanzar errores HTTP controlados desde tus controladores o servicios.

Cuando lanzas una HttpException, NestJS intercepta este error y genera automáticamente una respuesta HTTP con el código de estado y el mensaje que especifiques, deteniendo la ejecución normal del método.

Ejemplo: Lanzar un error si el producto ya existe por nombre

Continuando con nuestro ProductosController, supongamos que no queremos permitir la creación de productos con nombres duplicados.

// src/productos/productos.controller.ts

import {
  Controller,
  Post,
  Body,
  HttpCode,
  HttpStatus,
  HttpException, // ¡Importamos HttpException!
} from '@nestjs/common';
import { CreateProductDto } from './dto/create-product.dto';

interface Product {
  id: number;
  nombre: string;
  descripcion: string;
  precio: number;
}

@Controller('productos')
export class ProductosController {
  private readonly products: Product[] = []; // Simulación de almacenamiento

  // ... métodos GET ...

  @Post()
  @HttpCode(HttpStatus.CREATED)
  create(@Body() createProductDto: CreateProductDto): Product {
    // 1. Comprobamos si ya existe un producto con el mismo nombre
    const exists = this.products.find(item => item.nombre === createProductDto.nombre);
    if (exists) {
      // 2. Si existe, lanzamos una HttpException con el código de estado 400 Bad Request
      throw new HttpException('Ya existe un producto con este nombre.', HttpStatus.BAD_REQUEST);
    }

    // 3. Si no existe, procedemos a "crear" el nuevo producto
    const newProduct: Product = {
      id: this.products.length > 0 ? Math.max(...this.products.map(p => p.id)) + 1 : 1,
      ...createProductDto,
    };
    this.products.push(newProduct);
    return newProduct;
  }
}

En este ejemplo:

  • Si se intenta crear un producto con un nombre que ya existe en nuestra lista simulada, se lanza una HttpException.
  • El primer argumento es el mensaje de error ('Ya existe un producto con este nombre.').
  • El segundo argumento es el código de estado HTTP que se enviará al cliente (HttpStatus.BAD_REQUEST, que corresponde a un código 400).

Cuando se lanza esta excepción, la respuesta HTTP que el cliente recibirá será automáticamente estructurada por NestJS de la siguiente manera (por defecto):

{
  "statusCode": 400,
  "message": "Ya existe un producto con este nombre.",
  "error": "Bad Request"
}

NestJS también proporciona subclases específicas de HttpException para errores comunes, como NotFoundException (404), ForbiddenException (403), UnauthorizedException (401), etc., que puedes usar para mayor claridad y semántica HTTP.

Conclusión

El manejo de solicitudes POST en NestJS es un proceso integral que abarca desde la definición de rutas hasta la validación de los datos entrantes, la gestión de respuestas adecuadas y el tratamiento elegante de los errores. Al combinar el uso de DTOs con class-validator para la validación, y las capacidades de NestJS para el manejo de respuestas HTTP y la gestión de excepciones con HttpException, puedes construir APIs robustas, predecibles y fáciles de mantener. Estas prácticas son fundamentales para cualquier aplicación que necesite interactuar de forma segura y eficiente con sus clientes.

Aprendizajes de esta lección

  • Comprender el propósito y el funcionamiento del método POST y su uso para enviar datos al servidor.
  • Aprender cómo crear un controlador POST en NestJS.
  • Familiarizarse con el concepto de DTO (Data Transfer Object).
  • Conocer la importancia de la validación de datos en NestJS.

Completa Nest y certifícate

Únete a nuestra plataforma y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.

Asistente IA

Resuelve dudas al instante

Ejercicios

Practica con proyectos reales

Certificados

Valida tus conocimientos

Más de 25.000 desarrolladores ya se han certificado con CertiDevs

⭐⭐⭐⭐⭐
4.9/5 valoración