En el ecosistema de Node.js, Express ha sido durante mucho tiempo la opción estándar para construir backends de aplicaciones JavaScript. Sin embargo, en mi trayectoria técnica, he observado que la falta de una arquitectura estructurada y definida provoca que las bases de código de Express se vuelvan caóticas e imposibles de mantener a medida que los equipos de trabajo crecen. En este API REST NestJS TypeScript tutorial te enseñaré a construir una API REST robusta, escalable y completamente tipada desde cero, implementando patrones empresariales nativos.
¿Por qué NestJS para APIs REST en 2025?
NestJS es un framework progresivo de Node.js diseñado para construir aplicaciones de backend altamente eficientes, confiables y escalables. Está construido con TypeScript de forma nativa (aunque permite escribir JavaScript puro) y combina conceptos avanzados de Programación Orientada a Objetos, Programación Funcional y Programación Reactiva. Su arquitectura está fuertemente inspirada en Angular, lo que significa que proporciona una separación de conceptos clara y estructurada mediante inyección de dependencias.
En el panorama de desarrollo moderno, NestJS destaca por ofrecer una estructura opinada. Esto elimina debates innecesarios entre desarrolladores sobre dónde colocar la lógica de la base de datos, los controladores HTTP o los filtros de validación. Al forzar un diseño modular estándar, garantiza la mantenibilidad a largo plazo de las aplicaciones, lo que lo convierte en la opción ideal para proyectos empresariales complejos y microservicios robustos en la nube.
Instalación y estructura del proyecto base
Para inicializar un proyecto con NestJS, requerimos tener instalado Node.js (versión 18 o superior recomendada) en nuestra máquina de desarrollo. La forma más recomendada y ágil de trabajar con el framework es a través de su interfaz de línea de comandos (CLI) oficial. Esta herramienta automatiza la generación de archivos, clases lógicas y módulos respetando el estándar del framework.
Instala el CLI de NestJS de forma global mediante npm ejecutando la terminal en tu entorno de desarrollo. Una vez completado, inicializa el proyecto con el comando de creación y accede al directorio. Estructuralmente, verás que la carpeta principal src contendrá el archivo de inicio main.ts, que configura la aplicación Express o Fastify interna, y el módulo raíz app.module.ts que orquesta las dependencias lógicas del sistema.
Crear el primer módulo, controlador y servicio
La arquitectura de NestJS se basa en módulos que encapsulan lógica de negocio relacionada. Para crear un recurso completo de forma ágil, el CLI nos ofrece generadores automáticos. En nuestro ejemplo simularemos la gestión de un catálogo de productos. Ejecuta el generador de recursos lógicos desde la raíz del proyecto para crear la estructura de carpetas.
Este generador creará una carpeta llamada productos con cuatro componentes fundamentales: el módulo que registra los componentes ante NestJS, el controlador que intercepta las peticiones HTTP (GET, POST, etc.) y define las rutas de la API REST, el servicio que encapsula la lógica de negocio y consultas, y las clases de transferencia de datos (DTOs) que modelan las estructuras lógicas esperadas.
Conectar con la base de datos usando TypeORM
Una API REST profesional requiere persistir la información de forma segura en un gestor relacional como PostgreSQL o MySQL. NestJS integra de forma nativa diversos ORMs, siendo TypeORM uno de los más populares debido a su excelente tipado estático con TypeScript. Para conectar nuestra API, debemos instalar los paquetes de soporte y los drivers correspondientes.
Una vez instaladas las dependencias, configuramos el módulo de base de datos en app.module.ts mediante el método estático TypeOrmModule.forRoot(). Definiremos nuestras entidades lógicas utilizando decoradores de clase como @Entity() y @PrimaryGeneratedColumn() para mapear las clases directamente a tablas físicas de la base de datos, lo que nos permite realizar consultas complejas sin escribir código SQL de forma explícita.
Validación de datos con class-validator
Validar la información entrante antes de procesarla en la base de datos es indispensable para prevenir inyecciones de código y estados corruptos en el sistema. NestJS resuelve esto de manera brillante a través de los denominados ValidationPipes globales y la integración directa con la librería class-validator.
Al definir nuestros Data Transfer Objects (DTO), podemos decorar cada propiedad con validaciones semánticas e intuitivas, como @IsString(), @IsNotEmpty() o @IsPositive(). Si un cliente envía una petición HTTP con datos que no se ajustan a estas reglas, NestJS interceptará la consulta de forma automática y retornará una respuesta de error 400 (Bad Request) detallando qué campos fallaron, sin que tengamos que escribir validaciones manuales en el servicio.
Autenticación segura con JSON Web Tokens (JWT)
Para proteger los endpoints sensibles de nuestra API REST, implementaremos un sistema de autenticación basado en JSON Web Tokens (JWT). Utilizaremos las librerías oficiales de Passport en NestJS, que estructuran la autenticación mediante estrategias y guardias (guards). El flujo de autenticación consiste en recibir las credenciales de usuario, validar su identidad en la base de datos y retornar un token firmado asimétricamente.
Este token devuelto será requerido en el encabezado HTTP Authorization de cada petición subsecuente a rutas protegidas. Implementaremos un guardia personalizado usando el decorador @UseGuards(JwtAuthGuard) a nivel de controlador para restringir accesos a usuarios no autenticados, garantizando la seguridad perimetral de nuestra API en producción.
Código completo del controlador y servicio
A continuación se detalla la implementación lógica completa del controlador y del servicio de productos de NestJS:
// productos.controller.ts
import { Controller, Get, Post, Body, Param, ParseIntPipe, UseGuards } from '@nestjs/common';
import { ProductosService } from './productos.service';
import { CreateProductoDto } from './dto/create-producto.dto';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
@Controller('productos')
export class ProductosController {
constructor(private readonly productosService: ProductosService) {}
@Post()
@UseGuards(JwtAuthGuard) // Ruta protegida con JWT
create(@Body() createProductoDto: CreateProductoDto) {
return this.productosService.create(createProductoDto);
}
@Get()
findAll() {
return this.productosService.findAll();
}
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.productosService.findOne(id);
}
}
// productos.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { CreateProductoDto } from './dto/create-producto.dto';
interface Producto {
id: number;
nombre: string;
precio: number;
}
@Injectable()
export class ProductosService {
private productos: Producto[] = [];
create(createProductoDto: CreateProductoDto): Producto {
const nuevoProducto: Producto = {
id: this.productos.length + 1,
...createProductoDto,
};
this.productos.push(nuevoProducto);
return nuevoProducto;
}
findAll(): Producto[] {
return this.productos;
}
findOne(id: number): Producto {
const producto = this.productos.find((p) => p.id === id);
if (!producto) {
throw new NotFoundException(`Producto con ID ${id} no encontrado`);
}
return producto;
}
}
El decorador @Injectable() marca la clase de servicio ante el contenedor IoC (Inversión de Control) de NestJS, permitiendo su inyección automática en el constructor del controlador.
Errores comunes y cómo solucionarlos
El error más recurrente al iniciar con NestJS es el fallo de inyección en el contenedor: "Nest cannot resolve dependencies of the XService. Please make sure that the argument Y at index [0] is available in the ZModule context". Esto se debe a que olvidaste importar el módulo de soporte correspondiente (por ejemplo, el módulo de TypeORM) dentro de la sección imports del módulo de tu recurso o no declaraste el servicio en providers.
Otro problema clásico es el fallo en el tipado de los controladores HTTP cuando se reciben parámetros numéricos por la URL, retornando errores de base de datos porque el valor se lee como una cadena de texto. Resuelve esto agregando pipes de transformación nativos como ParseIntPipe en los decoradores de parámetros (@Param('id', ParseIntPipe)) para forzar la conversión automática de tipos en tiempo de ejecución de forma limpia.
Conclusión
NestJS simplifica radicalmente el desarrollo en Node.js al dotar a los proyectos de backend de una arquitectura modular sólida, predecible y altamente tipada con TypeScript. Si estás planeando el diseño de la arquitectura de software o base de datos para tu próxima aplicación web corporativa, te invito a conocer mis servicios.
¿Necesitas implementar esto en un proyecto real? Revisa mis servicios de desarrollo o contáctame directamente.