Una API REST es la columna vertebral de casi cualquier aplicación moderna. Es cómo el frontend habla con el backend, cómo dos sistemas intercambian información y cómo construyes servicios que otros pueden consumir.

Si trabajas en desarrollo web en 2026, saber construir una API REST profesional no es opcional. Es la habilidad base sobre la que se construyen aplicaciones móviles, plataformas SaaS, microservicios y prácticamente cualquier producto digital serio.

En este tutorial vas a construir una API REST funcional con Node.js y Express desde cero. Al final tendrás un servidor corriendo con rutas reales, manejo de errores, arquitectura MVC y una estructura que escala. Es la misma base que usamos en Dualsym cuando arrancamos un proyecto backend nuevo.


¿Qué es una API REST? (rápido)

REST (Representational State Transfer) es un estilo de arquitectura para diseñar APIs basado en:

PrincipioSignificado
StatelessCada petición es independiente, no guarda estado en el servidor
RecursosCada URL identifica un recurso (/users, /products)
Verbos HTTPGET (leer), POST (crear), PUT/PATCH (actualizar), DELETE (eliminar)
JSONFormato de intercambio estándar
Códigos HTTP200 OK, 201 Created, 400 Bad Request, 404 Not Found, 500 Server Error

Si tu API sigue estos principios, cualquier cliente puede consumirla sin acoplamiento.


Lo que necesitas antes de empezar

  • Node.js instalado (versión 20 LTS o superior recomendada para 2026)
  • Un editor de código (VS Code o Cursor)
  • Conocimientos básicos de JavaScript
  • Terminal / línea de comandos

Verifica tu versión de Node:

node --version
# debe mostrar v20.x.x o superior

Si tienes todo listo, empezamos.


Paso 1: Inicializar el proyecto

Crea una carpeta para tu proyecto y ábrela en la terminal:

mkdir mi-api
cd mi-api
npm init -y

El flag -y acepta valores por defecto. Esto crea tu package.json.

Instala Express, el framework más usado para APIs en Node.js:

npm install express

Para desarrollo, instala nodemon (reinicia el servidor automáticamente al guardar cambios):

npm install --save-dev nodemon

Abre package.json y agrega los scripts:

"scripts": {
  "dev": "nodemon index.js",
  "start": "node index.js"
}

Paso 2: Crear el servidor base

Crea un archivo index.js en la raíz:

const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;

// Middleware: parsear JSON en el body de las requests
app.use(express.json());

// Ruta de prueba
app.get('/', (req, res) => {
  res.json({
    mensaje: 'API funcionando correctamente',
    version: '1.0.0',
    timestamp: new Date().toISOString()
  });
});

// Iniciar servidor
app.listen(PORT, () => {
  console.log(`✓ Servidor corriendo en http://localhost:${PORT}`);
});

Ejecuta:

npm run dev

Abre http://localhost:3000 y verás el JSON. Tu primera API REST está corriendo.


Paso 3: Organizar la estructura del proyecto (MVC)

Para que la API escale necesitas arquitectura ordenada desde el inicio. El patrón MVC (Model-View-Controller) adaptado para APIs:

mi-api/
├── index.js              # Entry point
├── routes/               # Definen las URLs
│   └── tareas.js
├── controllers/          # Lógica de cada endpoint
│   └── tareasController.js
├── data/                 # Datos (luego será base de datos)
│   └── tareas.js
├── middlewares/          # Validación, auth, logging
│   └── errorHandler.js
└── package.json

Crea las carpetas:

mkdir routes controllers data middlewares

Beneficio de MVC: cada archivo tiene una responsabilidad clara. Cuando tu API crezca, sabes exactamente dónde tocar.


Paso 4: Crear los datos de ejemplo

En data/tareas.js, simula una base de datos en memoria:

let tareas = [
  { id: 1, titulo: 'Aprender Node.js', completada: false, prioridad: 'alta' },
  { id: 2, titulo: 'Construir una API', completada: false, prioridad: 'media' },
  { id: 3, titulo: 'Publicar en producción', completada: false, prioridad: 'baja' }
];

module.exports = tareas;

En un proyecto real conectarías PostgreSQL o MongoDB aquí. Por ahora un array nos sirve para enfocarnos en la API.


Paso 5: Crear el controlador con operaciones CRUD

En controllers/tareasController.js:

let tareas = require('../data/tareas');

// GET /api/tareas — Obtener todas las tareas
const getTareas = (req, res) => {
  // Soporte de query params: ?completada=true&prioridad=alta
  const { completada, prioridad } = req.query;
  let resultado = [...tareas];

  if (completada !== undefined) {
    resultado = resultado.filter(t => t.completada === (completada === 'true'));
  }
  if (prioridad) {
    resultado = resultado.filter(t => t.prioridad === prioridad);
  }

  res.json({
    total: resultado.length,
    data: resultado
  });
};

// GET /api/tareas/:id — Obtener una tarea por ID
const getTareaById = (req, res) => {
  const id = parseInt(req.params.id);
  const tarea = tareas.find(t => t.id === id);

  if (!tarea) {
    return res.status(404).json({ error: 'Tarea no encontrada' });
  }
  res.json(tarea);
};

// POST /api/tareas — Crear una nueva tarea
const createTarea = (req, res) => {
  const { titulo, prioridad = 'media' } = req.body;

  // Validación básica
  if (!titulo || titulo.trim().length < 3) {
    return res.status(400).json({
      error: 'El título es obligatorio y debe tener al menos 3 caracteres'
    });
  }
  if (!['alta', 'media', 'baja'].includes(prioridad)) {
    return res.status(400).json({
      error: 'La prioridad debe ser: alta, media o baja'
    });
  }

  const nuevaTarea = {
    id: tareas.length > 0 ? Math.max(...tareas.map(t => t.id)) + 1 : 1,
    titulo: titulo.trim(),
    completada: false,
    prioridad,
    creadaEn: new Date().toISOString()
  };

  tareas.push(nuevaTarea);
  res.status(201).json(nuevaTarea);
};

// PUT /api/tareas/:id — Actualizar una tarea
const updateTarea = (req, res) => {
  const id = parseInt(req.params.id);
  const tarea = tareas.find(t => t.id === id);

  if (!tarea) {
    return res.status(404).json({ error: 'Tarea no encontrada' });
  }

  const { titulo, completada, prioridad } = req.body;

  if (titulo !== undefined) {
    if (titulo.trim().length < 3) {
      return res.status(400).json({ error: 'Título muy corto' });
    }
    tarea.titulo = titulo.trim();
  }
  if (completada !== undefined) tarea.completada = Boolean(completada);
  if (prioridad !== undefined) {
    if (!['alta', 'media', 'baja'].includes(prioridad)) {
      return res.status(400).json({ error: 'Prioridad inválida' });
    }
    tarea.prioridad = prioridad;
  }

  tarea.actualizadaEn = new Date().toISOString();
  res.json(tarea);
};

// DELETE /api/tareas/:id — Eliminar una tarea
const deleteTarea = (req, res) => {
  const id = parseInt(req.params.id);
  const index = tareas.findIndex(t => t.id === id);

  if (index === -1) {
    return res.status(404).json({ error: 'Tarea no encontrada' });
  }
  tareas.splice(index, 1);
  res.status(204).send(); // 204 No Content
};

module.exports = {
  getTareas,
  getTareaById,
  createTarea,
  updateTarea,
  deleteTarea
};

Aquí ya tienes validación básica, filtros por query params y respuestas estándar HTTP.


Paso 6: Definir las rutas

En routes/tareas.js:

const express = require('express');
const router = express.Router();
const {
  getTareas,
  getTareaById,
  createTarea,
  updateTarea,
  deleteTarea
} = require('../controllers/tareasController');

router.get('/', getTareas);
router.get('/:id', getTareaById);
router.post('/', createTarea);
router.put('/:id', updateTarea);
router.delete('/:id', deleteTarea);

module.exports = router;

Patrón clave: las rutas solo declaran el qué (URL + verbo HTTP). El cómo vive en el controller. Esto te permite cambiar lógica sin tocar rutas.


Paso 7: Middleware de manejo de errores global

En middlewares/errorHandler.js:

const errorHandler = (err, req, res, next) => {
  console.error('Error:', err.message);
  console.error(err.stack);

  res.status(err.status || 500).json({
    error: err.message || 'Error interno del servidor',
    ...(process.env.NODE_ENV !== 'production' && { stack: err.stack })
  });
};

const notFoundHandler = (req, res) => {
  res.status(404).json({
    error: 'Ruta no encontrada',
    path: req.originalUrl,
    method: req.method
  });
};

module.exports = { errorHandler, notFoundHandler };

Paso 8: Conectar todo en index.js

Actualiza index.js:

const express = require('express');
const tareasRoutes = require('./routes/tareas');
const { errorHandler, notFoundHandler } = require('./middlewares/errorHandler');

const app = express();
const PORT = process.env.PORT || 3000;

// ── Middlewares globales ──
app.use(express.json());

// ── Logging básico (sin librerías externas) ──
app.use((req, res, next) => {
  console.log(`${new Date().toISOString()} ${req.method} ${req.originalUrl}`);
  next();
});

// ── Rutas ──
app.get('/', (req, res) => {
  res.json({
    mensaje: 'API REST funcionando',
    endpoints: {
      tareas: '/api/tareas'
    }
  });
});

app.use('/api/tareas', tareasRoutes);

// ── 404 y error handlers (deben ir AL FINAL) ──
app.use(notFoundHandler);
app.use(errorHandler);

app.listen(PORT, () => {
  console.log(`✓ API REST corriendo en http://localhost:${PORT}`);
});

Paso 9: Probar la API

Con el servidor corriendo (npm run dev), prueba los endpoints. Puedes usar Postman, Insomnia, Bruno o curl directamente:

# Obtener todas las tareas
curl http://localhost:3000/api/tareas

# Filtrar por completada y prioridad
curl "http://localhost:3000/api/tareas?completada=false&prioridad=alta"

# Obtener una tarea por ID
curl http://localhost:3000/api/tareas/1

# Crear una tarea
curl -X POST http://localhost:3000/api/tareas \
  -H "Content-Type: application/json" \
  -d '{"titulo": "Aprender Express", "prioridad": "alta"}'

# Actualizar una tarea
curl -X PUT http://localhost:3000/api/tareas/1 \
  -H "Content-Type: application/json" \
  -d '{"completada": true}'

# Eliminar una tarea
curl -X DELETE http://localhost:3000/api/tareas/1

Si todo funcionó, acabas de construir tu primera API REST profesional.


Próximos pasos: de tutorial a producción

Lo que construiste es la base sólida. Para llevarlo a producción real necesitas:

1. Base de datos real

Reemplaza el array en memoria con PostgreSQL + Prisma o MongoDB + Mongoose:

npm install @prisma/client
npx prisma init

2. Validación robusta

Usa Zod o Joi para validación type-safe:

npm install zod
import { z } from 'zod';

const TareaSchema = z.object({
  titulo: z.string().min(3).max(100),
  prioridad: z.enum(['alta', 'media', 'baja']).default('media')
});

// En el controller:
const data = TareaSchema.parse(req.body); // valida o lanza error

3. Autenticación con JWT

npm install jsonwebtoken bcrypt

Implementa rutas /auth/register y /auth/login, protege endpoints con middleware que verifique el token.

4. Variables de entorno

npm install dotenv

Crea .env:

PORT=3000
DATABASE_URL=postgres://...
JWT_SECRET=tu-secreto-largo-y-aleatorio

5. Rate limiting (anti-abuso)

npm install express-rate-limit

6. CORS configurado

npm install cors

7. Helmet (security headers)

npm install helmet

8. Logging profesional con Pino o Winston

npm install pino pino-http

9. Tests automatizados con Vitest o Jest

npm install --save-dev vitest supertest

10. TypeScript (recomendado en 2026)

📖 Profundiza: 5 tendencias de desarrollo web en 2026 — TypeScript es la #3.


Errores comunes al construir APIs REST

He visto los mismos errores en muchos proyectos:

  • ❌ Usar 200 para todo (deberías usar 201, 204, 400, 404, 500 según corresponda)
  • ❌ No validar el body de las requests (security risk + bugs)
  • ❌ Devolver mensajes de error en español o inconsistentes
  • ❌ No paginar endpoints de listado (?page=1&limit=20)
  • ❌ Mezclar lógica de negocio en routes (debe vivir en services/controllers)
  • ❌ Sin manejo de errores global (cada controller try/catch repetido)
  • ❌ Hardcodear configuración en lugar de usar variables de entorno
  • ❌ No documentar la API (OpenAPI/Swagger es estándar)

Preguntas frecuentes sobre APIs REST con Node.js

¿Por qué Express y no otro framework?

Express tiene el ecosistema más maduro (15+ años), mejor documentación y comunidad gigante. Alternativas modernas: Fastify (más rápido), Hono (multi-runtime, edge), NestJS (estructurado, opinionated). Para empezar y para 90% de casos: Express.

¿Debo usar TypeScript en mis APIs?

En proyectos profesionales nuevos en 2026: sí, sin excepción. TypeScript previene 60-80% de bugs en producción. Para hobby projects o prototipos rápidos, JS puro está bien.

¿Cuánto cuesta hostear una API en producción?

  • Tier gratis (Vercel, Render, Railway): suficiente para hobby/MVP
  • $5-20/mes (Railway, Fly.io): producción ligera
  • $50-200/mes (DigitalOcean, AWS): apps con tráfico real
  • $500+/mes: scaling enterprise

¿REST vs GraphQL en 2026?

REST sigue siendo dominante (~80%+ del mercado). GraphQL es excelente cuando tienes muchos consumidores con necesidades distintas. Para una sola app frontend + backend: REST es más simple y suficiente.

¿Cuánto cuesta construir una API profesional con Dualsym en RD?

Depende del alcance:

  • API simple (CRUD + auth): RD$45,000-80,000
  • API media (multi-tenant, integraciones): RD$80,000-180,000
  • Plataforma SaaS completa: USD $8,000-30,000

Pídenos cotización con tu caso específico.

¿Cuándo migrar de array en memoria a base de datos?

Apenas vayas a deployar a producción. El array en memoria se pierde al reiniciar el servidor. Para desarrollo local + tests: array está bien. Para algo serio: PostgreSQL es la opción default segura.

¿Cómo documento mi API REST?

Estándar de la industria: OpenAPI/Swagger (swagger-jsdoc + swagger-ui-express). Para algo más moderno: Scalar o Redoc. Es el primer indicador de profesionalismo cuando un cliente revisa tu API.


Conclusión

Con esta base tienes una API REST funcional, bien estructurada y lista para crecer. Para llevarla a producción real solo necesitas añadir capa por capa: base de datos, autenticación, validación robusta, tests y deploy.

Cada uno de esos pasos merece su propio tutorial, pero lo importante es que ya tienes los fundamentos sólidos.

El mejor código es el que entiende quien lo escribe. Construir desde cero, aunque parezca más lento al inicio, produce ese entendimiento.

¿Necesitas una API profesional para tu negocio en Santo Domingo o RD? En Dualsym construimos APIs escalables con Node.js + TypeScript + PostgreSQL desde el primer día. Conversemos por WhatsApp.