Cómo crear tu primera API REST con NestJS paso a paso

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

  1. Instalamos el CLI de Nest.
  2. Creamos el proyecto.
  3. Generamos el recurso tareas con un solo comando.
  4. Definimos la entidad y los DTOs.
  5. Escribimos la lógica en el servicio.
  6. Conectamos todo en el controlador.
  7. Agregamos validaciones.
  8. 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.

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *