Cuando empecé con Node.js armaba mis APIs con Express, y cada proyecto terminaba con una estructura distinta. NestJS me resolvió ese problema: te da una estructura clara desde el primer momento.
En este artículo construimos una API de tareas con las cuatro operaciones básicas (crear, leer, actualizar y borrar) y al final la probamos con peticiones reales.
Qué es NestJS
NestJS es un framework para Node.js que usa TypeScript y organiza el código en módulos, controladores y servicios. Por debajo corre sobre Express, así que todo lo que ya sabes de Node te sirve.
Cada pieza tiene un trabajo:
- El controlador recibe las peticiones HTTP (GET, POST, etc.).
- El servicio guarda la lógica.
- El módulo junta las dos piezas anteriores.
Lo que necesitas
- Node.js instalado, con una versión LTS reciente.
- Un editor de código. Yo uso VS Code.
- Saber un poco de JavaScript o TypeScript.
Revisa tu versión de Node con este comando:
node -v
Paso 1: Instala el CLI de Nest
Nest trae una herramienta de línea de comandos que crea proyectos y archivos por ti. Instálala así:
npm i -g @nestjs/cli
Paso 2: Crea el proyecto
nest new mi-primera-api
El CLI te pregunta qué gestor de paquetes quieres usar (npm, yarn o pnpm). Elige el que uses siempre. Cuando termine, entra a la carpeta y arranca el servidor:
cd mi-primera-api npm run start:dev
Abre http://localhost:3000 en el navegador. Si ves un “Hello World!”, tu servidor ya funciona. El modo start:dev se reinicia solo cada vez que guardas un cambio, así que no tienes que apagarlo y prenderlo.
Paso 3: Mira la estructura
Dentro de src encuentras estos archivos:
src/ ├── app.controller.ts ├── app.module.ts ├── app.service.ts └── main.ts
main.ts es el punto de entrada y ahí arranca la aplicación. app.module.ts es el módulo principal. app.controller.ts y app.service.ts son el ejemplo del “Hello World”.
No hace falta que entiendas todo ahora. Lo vas a ver más claro con el código que escribimos enseguida.
Paso 4: Genera el recurso de tareas
Aquí el CLI te ahorra bastante trabajo. En lugar de crear los archivos a mano, escribe:
nest g resource tareas
Te pregunta el tipo de transporte. Elige REST API. Cuando pregunte si quieres generar los puntos de entrada del CRUD, responde Yes.
Nest crea la carpeta tareas con módulo, controlador, servicio, DTOs y entidad, y además registra el módulo nuevo en el módulo principal.
Paso 5: Define la entidad
La entidad describe cómo es una tarea. Abre src/tareas/entities/tarea.entity.ts y déjala así:
export class Tarea {
id: number;
titulo: string;
descripcion?: string;
completada: boolean;
}
En este primer ejemplo no usamos base de datos. Guardamos las tareas en memoria y dejamos la base de datos real para el siguiente artículo.
Paso 6: Crea los DTOs
Un DTO (Data Transfer Object) define qué datos aceptas cuando alguien crea o actualiza una tarea. Abre src/tareas/dto/create-tarea.dto.ts:
export class CreateTareaDto {
titulo: string;
descripcion?: string;
}
El DTO de actualización (update-tarea.dto.ts) ya viene casi listo. Usa PartialType, que copia los campos del DTO de creación y los vuelve opcionales. Solo le agregamos el campo completada:
import { PartialType } from '@nestjs/mapped-types';
import { CreateTareaDto } from './create-tarea.dto';
export class UpdateTareaDto extends PartialType(CreateTareaDto) {
completada?: boolean;
}
Paso 7: Escribe el servicio
Aquí vive la lógica. Abre src/tareas/tareas.service.ts y reemplaza todo el contenido por esto:
import { Injectable, NotFoundException } from '@nestjs/common';
import { CreateTareaDto } from './dto/create-tarea.dto';
import { UpdateTareaDto } from './dto/update-tarea.dto';
import { Tarea } from './entities/tarea.entity';
@Injectable()
export class TareasService {
private tareas: Tarea[] = [];
private siguienteId = 1;
create(createTareaDto: CreateTareaDto): Tarea {
const nueva: Tarea = {
id: this.siguienteId++,
titulo: createTareaDto.titulo,
descripcion: createTareaDto.descripcion,
completada: false,
};
this.tareas.push(nueva);
return nueva;
}
findAll(): Tarea[] {
return this.tareas;
}
findOne(id: number): Tarea {
const tarea = this.tareas.find((t) => t.id === id);
if (!tarea) {
throw new NotFoundException(`No existe la tarea con id ${id}`);
}
return tarea;
}
update(id: number, updateTareaDto: UpdateTareaDto): Tarea {
const tarea = this.findOne(id);
Object.assign(tarea, updateTareaDto);
return tarea;
}
remove(id: number): void {
const tarea = this.findOne(id);
this.tareas = this.tareas.filter((t) => t.id !== tarea.id);
}
}
El servicio guarda las tareas en un arreglo y tiene un método para cada operación. Si alguien pide una tarea que no existe, lanzamos un NotFoundException y Nest responde con un error 404 sin que escribamos nada más.
Paso 8: Conecta el controlador
El controlador recibe la petición y se la pasa al servicio. Abre src/tareas/tareas.controller.ts:
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
ParseIntPipe,
} from '@nestjs/common';
import { TareasService } from './tareas.service';
import { CreateTareaDto } from './dto/create-tarea.dto';
import { UpdateTareaDto } from './dto/update-tarea.dto';
@Controller('tareas')
export class TareasController {
constructor(private readonly tareasService: TareasService) {}
@Post()
create(@Body() createTareaDto: CreateTareaDto) {
return this.tareasService.create(createTareaDto);
}
@Get()
findAll() {
return this.tareasService.findAll();
}
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.tareasService.findOne(id);
}
@Patch(':id')
update(
@Param('id', ParseIntPipe) id: number,
@Body() updateTareaDto: UpdateTareaDto,
) {
return this.tareasService.update(id, updateTareaDto);
}
@Delete(':id')
remove(@Param('id', ParseIntPipe) id: number) {
return this.tareasService.remove(id);
}
}
Presta atención a dos detalles. Los decoradores (@Get, @Post, @Body y compañía) le dicen a Nest qué hace cada método. Y ParseIntPipe convierte el id de la URL, que llega como texto, en un número. Si alguien manda algo que no es un número, Nest responde con un error 400.
Paso 9: Valida los datos
Ahora mismo cualquiera puede mandar una tarea sin título y la API la acepta. Lo arreglamos con validaciones. Instala estas dos librerías:
npm i class-validator class-transformer
Actualiza el DTO de creación:
import { IsString, IsNotEmpty, IsOptional } from 'class-validator';
export class CreateTareaDto {
@IsString()
@IsNotEmpty()
titulo: string;
@IsString()
@IsOptional()
descripcion?: string;
}
Y agrega la validación de completada en el de actualización:
import { PartialType } from '@nestjs/mapped-types';
import { IsBoolean, IsOptional } from 'class-validator';
import { CreateTareaDto } from './create-tarea.dto';
export class UpdateTareaDto extends PartialType(CreateTareaDto) {
@IsBoolean()
@IsOptional()
completada?: boolean;
}
Por último, activa las validaciones en toda la app. Abre src/main.ts:
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
await app.listen(3000);
}
bootstrap();
La opción whitelist: true descarta cualquier campo que no exista en el DTO, así nadie te cuela datos que no esperabas.
Paso 10: Prueba la API
Con el servidor corriendo (npm run start:dev), abre otra terminal y usa curl. También puedes usar Postman, Insomnia o la extensión Thunder Client de VS Code.
Para crear una tarea:
curl -X POST http://localhost:3000/tareas \
-H "Content-Type: application/json" \
-d '{"titulo": "Aprender NestJS", "descripcion": "Escribir el primer artículo"}'
Para ver todas las tareas:
curl http://localhost:3000/tareas
Para ver una sola:
curl http://localhost:3000/tareas/1
Para marcarla como completada:
curl -X PATCH http://localhost:3000/tareas/1 \
-H "Content-Type: application/json" \
-d '{"completada": true}'
Para eliminarla:
curl -X DELETE http://localhost:3000/tareas/1
Intenta también mandar una tarea sin título. La API responde con un error 400 y te explica qué falló, y eso es la validación haciendo su trabajo.
Lo que hicimos
- Instalamos el CLI de Nest.
- Creamos el proyecto.
- Generamos el recurso
tareascon un solo comando. - Definimos la entidad y los DTOs.
- Escribimos la lógica en el servicio.
- Conectamos todo en el controlador.
- Agregamos validaciones.
- Probamos cada endpoint.
Lo que sigue
Nuestra API funciona, pero pierde las tareas cada vez que reiniciamos el servidor, porque viven en memoria. En el siguiente artículo la conectamos a una base de datos para que los datos se queden guardados.
Si algo no te funcionó, cuéntame en los comentarios qué error te salió y lo revisamos.
