🇺🇸 EN
Cloud
·8 min read·↗·🇺🇸 EN

Cómo crear tu primera API REST con NestJS y Node.js

Portada principal del artículo: Cómo crear tu primera API REST con NestJS y Node.js

Al crear prototipos o aplicaciones de ejemplo, como desarrolladores frontend solemos necesitar datos reales de un servidor. Servidores simulados como json-server o APIs de prueba gratuitas funcionan bien para demos básicas. Sin embargo, se quedan cortos rápidamente cuando necesitas lógica de negocio personalizada, procesamiento de archivos o endpoints POST a medida.

¿Cómo puede un desarrollador frontend construir un backend sólido y listo para producción en Node.js sin pelear con el código desorganizado de Express?

Mi solución favorita es NestJS. Como NestJS utiliza TypeScript, módulos, controladores e inyección de dependencias, el modelo mental resulta limpio, estructurado y familiar.

En esta guía, te mostraré cómo construir una API REST con NestJS desde cero, manejar parámetros de ruta dinámicos, procesar datos en peticiones POST, configurar CORS y consumir la API desde Angular moderno.


1. ¿Por qué NestJS para aplicaciones Node.js?

Node.js te da mucha libertad, pero en proyectos de Express puro, esa libertad suele traducirse en código desordenado y sin consistencia arquitectónica.

NestJS resuelve este problema ofreciendo una arquitectura modular lista para usar:

  • Controladores: Manejan las peticiones HTTP entrantes y devuelven respuestas.
  • Proveedores / Servicios: Encapsulan la lógica de negocio, consultas a bases de datos e integraciones con terceros.
  • Módulos: Agrupan controladores y servicios relacionados en límites de dominio claros.
  • Pipes y Guards: Validan el cuerpo de las peticiones entrantes y protegen endpoints privados.

Empecemos generando nuestro proyecto.


2. Generar el proyecto de NestJS

Primero, asegúrate de tener instalado Node.js (v18 o superior). Luego, instala la CLI oficial de NestJS:

npm install -g @nestjs/cli

Tip: También puedes usar npx @nestjs/cli new jumbo_api si no quieres instalar paquetes globales en tu máquina.

Ahora genera un nuevo proyecto llamado jumbo_api:

nest new jumbo_api

Cuando el generador termine, entra en la carpeta del proyecto e inicia el servidor de desarrollo:

cd jumbo_api
npm run start:dev

Abre http://localhost:3000 en tu navegador. Verás la respuesta por defecto "Hello World!".

El comando start:dev ejecuta la aplicación en modo watch, recompilando automáticamente cada vez que guardas cambios.


3. Entender la estructura principal del proyecto

Abre el proyecto generado en tu editor. Verás cuatro archivos principales dentro de src/:

  1. main.ts: El punto de entrada que inicia la aplicación usando NestFactory.create():
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
 
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();
  1. app.module.ts: El módulo raíz donde se registran los controladores y proveedores:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
 
@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
  1. app.controller.ts: Mapea las rutas HTTP a métodos:
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
 
@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}
 
  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}

Ahora creemos nuestro propio controlador personalizado.


4. Crear un controlador con endpoints GET

Vamos a crear un controlador cards para gestionar tarjetas de membresía. Ejecuta el generador de la CLI de Nest:

nest generate controller cards

Este comando crea src/cards/cards.controller.ts y lo importa automáticamente en AppModule.

Retornar una lista de objetos

Abre src/cards/cards.controller.ts y agrega el método getAll:

import { Controller, Get } from '@nestjs/common';
 
export interface Card {
  id: number;
  code: string;
  validDate: string;
}
 
@Controller('cards')
export class CardsController {
  @Get('list')
  getAll(): Card[] {
    return [
      {
        id: 1,
        code: 'CARD-1001',
        validDate: new Date().toLocaleDateString('en-US'),
      },
      {
        id: 2,
        code: 'CARD-1002',
        validDate: new Date().toLocaleDateString('en-US'),
      },
    ];
  }
}

Como el controlador tiene @Controller('cards') y el método tiene @Get('list'), al ingresar a http://localhost:3000/cards/list verás el array en formato JSON:

Nest serializa automáticamente los arrays y objetos de JavaScript a JSON y configura la cabecera Content-Type: application/json.


5. Leer parámetros dinámicos de ruta

¿Qué pasa si queremos un endpoint como /cards/generate/:name que lea un parámetro directamente desde la URL?

Para esto usamos el decorador @Param('name'):

import { Controller, Get, Param } from '@nestjs/common';
 
@Controller('cards')
export class CardsController {
  @Get('generate/:name')
  generateCard(@Param('name') name: string) {
    return {
      id: Math.floor(Math.random() * 1000),
      code: `CARD-${Math.random().toString(36).substring(2, 7).toUpperCase()}`,
      validDate: new Date().toLocaleDateString('en-US'),
      holder: name,
    };
  }
}

Al abrir http://localhost:3000/cards/generate/lebronjames, Nest extrae lebronjames de la URL y se lo pasa directamente a tu método:

Ahora veamos cómo manejar datos en peticiones POST.


6. Manejar peticiones POST y el cuerpo de la solicitud

Para recibir datos JSON en una petición POST, usamos los decoradores @Post() y @Body():

import { Controller, Post, Body, HttpCode, HttpStatus } from '@nestjs/common';
 
export interface SubscribeDto {
  name: string;
}
 
@Controller('cards')
export class CardsController {
  @Post('subscribe')
  @HttpCode(HttpStatus.OK)
  subscribe(@Body() body: SubscribeDto) {
    return {
      id: Math.floor(Math.random() * 10000),
      name: body.name,
      active: true,
      createdAt: new Date().toISOString(),
    };
  }
}

Presta atención al decorador @HttpCode(HttpStatus.OK). Por defecto, Nest devuelve el código HTTP 201 Created para peticiones POST. Agregar @HttpCode(HttpStatus.OK) cambia el código de estado a 200 OK cuando sea apropiado.

Envía una petición POST con Postman o curl:

curl -X POST http://localhost:3000/cards/subscribe \
  -H "Content-Type: application/json" \
  -d '{"name": "Dany Walls"}'

El servidor responderá con nuestro objeto JSON estructurado:

Antes de conectar nuestro frontend, queda un paso fundamental: habilitar CORS.


7. Habilitar CORS en NestJS

Si tu frontend corre en http://localhost:4200 y tu API se ejecuta en http://localhost:3000, el navegador bloqueará las peticiones debido a la política de mismo origen (Same-Origin Policy).

En NestJS, habilitar Cross-Origin Resource Sharing toma solo una línea. Abre src/main.ts:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
 
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
 
  // Habilitar CORS para clientes frontend
  app.enableCors();
 
  await app.listen(3000);
}
bootstrap();

También puedes pasar opciones como app.enableCors({ origin: 'http://localhost:4200' }) para restringir el acceso en entornos de producción.

Ahora pasemos a consumir la API en Angular.


8. Consumir la API en Angular moderno

Crea una aplicación standalone de Angular:

npx @angular/cli new jumbo_web --standalone --routing=false --style=scss

Configurar HttpClient en app.config.ts

En Angular moderno, configuramos provideHttpClient() en src/app/app.config.ts:

import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideHttpClient } from '@angular/common/http';
 
export const appConfig: ApplicationConfig = {
  providers: [
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideHttpClient(),
  ],
};

Crear el servicio de tarjetas con Signals

Crea un servicio para interactuar con la API de NestJS:

// src/app/cards.service.ts
import { Injectable, inject, signal } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { toSignal } from '@angular/core/rxjs-interop';
 
export interface Card {
  id: number;
  code: string;
  validDate: string;
}
 
@Injectable({ providedIn: 'root' })
export class CardsService {
  #http = inject(HttpClient);
  #apiUrl = 'http://localhost:3000/cards';
 
  // Convierte automáticamente el Observable a una Signal reactiva
  cards = toSignal(this.#http.get<Card[]>(`${this.#apiUrl}/list`), { initialValue: [] });
  activeCard = signal<Card | null>(null);
 
  generate(name: string) {
    this.#http.post<Card>(`${this.#apiUrl}/subscribe`, { name })
      .subscribe((card) => {
        this.activeCard.set(card);
      });
  }
}

Mostrar los datos con el nuevo flujo de control (@if y @for)

En src/app/app.component.ts:

import { Component, inject } from '@angular/core';
import { CardsService } from './cards.service';
 
@Component({
  selector: 'app-root',
  standalone: true,
  template: `
    <main class="container">
      <h1>Membership Cards</h1>
 
      <div class="form-group">
        <label for="name">Your Name:</label>
        <input id="name" #nameInput placeholder="Enter your name" />
        <button (click)="createCard(nameInput.value)">Generate Card</button>
      </div>
 
      @if (cardsService.activeCard(); as card) {
        <div class="alert-success">
          <p>Card generated for: <strong>{{ card.name }}</strong> (ID: {{ card.id }})</p>
        </div>
      }
 
      <h2>Available Cards</h2>
      <div class="cards-grid">
        @for (item of cardsService.cards(); track item.id) {
          <div class="card">
            <span>Code: {{ item.code }}</span>
            <span>Valid Until: {{ item.validDate }}</span>
          </div>
        } @empty {
          <p>No cards found.</p>
        }
      </div>
    </main>
  `,
})
export class AppComponent {
  cardsService = inject(CardsService);
 
  createCard(name: string) {
    if (name.trim()) {
      this.cardsService.generate(name);
    }
  }
}

Nota para Angular Legacy (v16 y anteriores): Puedes usar *ngIf="cardsService.activeCard()" y *ngFor="let item of cardsService.cards()" importando CommonModule. Angular moderno (v17+) utiliza la sintaxis de flujo de control con @if y @for mostrada arriba.

Ejecuta npm start en tu proyecto de Angular, abre http://localhost:4200 y ya tienes tu aplicación full-stack conectada.


9. Empaquetar la API con Docker

Para desplegar la API de NestJS de forma consistente en cualquier entorno en la nube, crea un archivo Dockerfile dentro de jumbo_api/:

# Multi-stage production build
FROM node:22-alpine AS build
 
WORKDIR /usr/src/app
 
COPY package*.json ./
RUN npm ci
 
COPY . .
RUN npm run build
 
# Production runtime stage
FROM node:22-alpine AS runner
 
WORKDIR /usr/src/app
ENV NODE_ENV=production
 
COPY package*.json ./
RUN npm ci --omit=dev
 
COPY --from=build /usr/src/app/dist ./dist
 
EXPOSE 3000
CMD ["node", "dist/main.js"]

Construye y prueba la imagen de tu contenedor:

docker build -t jumbo-api:latest .
docker run -p 3000:3000 jumbo-api:latest

Tu API ahora está empaquetada en un contenedor aislado y ligero, listo para Google Cloud Run, AWS ECS o cualquier runtime de contenedores.


Conclusión y próximos pasos

Construir APIs de Node.js con NestJS te da estructura sin sacrificar la velocidad de desarrollo:

  1. Apóyate en la modularidad: Usa controladores para el enrutamiento HTTP y servicios para la lógica de negocio.
  2. Usa tipado estricto: Define DTOs (Data Transfer Objects) e interfaces para compartir contratos entre el frontend y el backend.
  3. Habilita CORS desde el inicio: Evita errores molestos en el navegador configurando app.enableCors() durante la configuración inicial.

Si quieres seguir ampliando tus habilidades en backend y nube, echa un vistazo a estas guías relacionadas:

Parte de la serie Cloud

Explora más guías prácticas y arquitecturas reales en la serie Cloud.

Ver serie completa

Preguntas Frecuentes

¿Qué es NestJS y por qué usarlo con Node.js?

NestJS es un framework progresivo de Node.js construido con TypeScript. Proporciona una arquitectura modular con controladores, servicios e inyección de dependencias, haciendo que el desarrollo backend sea estructurado y fácil de mantener.

¿Cómo instalo la CLI de NestJS?

Puedes instalar la CLI oficial de NestJS de forma global usando npm i -g @nestjs/cli, o ejecutar comandos directamente sin instalación global usando npx @nestjs/cli new nombre-del-proyecto.

¿Cómo habilito CORS en NestJS?

Llama a app.enableCors() en la instancia de tu aplicación Nest en main.ts antes de llamar a app.listen(port).

¿Cómo consumo una API de NestJS en Angular moderno?

Usa provideHttpClient() en tu app.config.ts, inyecta HttpClient usando la función inject() y convierte observables a signals usando toSignal().

Artículos Relacionados

Compartir este artículo

Si esta guía te resultó útil, compártela con tu equipo o con otros desarrolladores.


Software real. Lecciones reales.

Comparto las lecciones que aprendí por las malas, para que puedas evitarlas o estar preparado cuando ocurran.

User avatar
User avatar
User avatar
User avatar
+13K

Únete a 13,800+ desarrolladores y lectores.

Cero spam. Cancela tu suscripción en cualquier momento.