Tutorial Node.js y Express: crea tu primera API con MongoDB paso a paso
¿Quieres dar tus primeros pasos en el backend con Node.js y Express? En este tutorial Node.js Express aprenderás a crear un servidor Node.js desde cero, manejar rutas y middleware, y conectar tu API a MongoDB usando Mongoose. Está pensado para desarrolladores principiantes e intermedios que buscan construir aplicaciones web con Express de manera clara y profesional.
Al finalizar tendrás una API REST funcional con operaciones CRUD completa, lista para seguir creciendo.
Objetivos del tutorial
- Instalar y preparar el entorno de desarrollo.
- Crear servidor Node.js básico con Express.
- Manejar rutas y controladores.
- Aplicar middleware esencial (JSON, CORS, logs, errores).
- Conectar a una base de datos MongoDB con Mongoose.
- Implementar un CRUD de ejemplo (tareas).
Requisitos previos
- Conocimientos básicos de JavaScript.
- Node.js instalado (v18+ recomendado).
- Cuenta de MongoDB Atlas o una instancia local de MongoDB.
1) Instalación y preparación del entorno
1.1 Verifica tu versión de Node.js y npm
Asegúrate de tener Node.js y npm instalados.
1 2 3 |
node -v npm -v |
Si no los tienes o usas una versión muy antigua, instala la última LTS desde nodejs.org o administra versiones con nvm.
1.2 Crea el proyecto base
Elige una carpeta y ejecuta:
1 2 3 |
mkdir api-tareas-express && cd api-tareas-express npm init -y |
Instala dependencias necesarias:
1 2 3 |
npm install express mongoose dotenv cors morgan npm install -D nodemon |
- express: framework minimalista para backend con Node.js.
- mongoose: ODM para conectarse y modelar datos en MongoDB.
- dotenv: gestión de variables de entorno.
- cors: habilitar CORS para clientes web.
- morgan: logger HTTP en desarrollo.
- nodemon: reinicio automático del servidor durante el desarrollo.
1.3 Estructura de carpetas recomendada
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
api-tareas-express/ ├─ package.json ├─ .env ├─ .gitignore ├─ src/ │ ├─ index.js │ ├─ config/ │ │ └─ db.js │ ├─ models/ │ │ └─ task.model.js │ ├─ controllers/ │ │ └─ task.controller.js │ ├─ routes/ │ │ └─ task.routes.js │ └─ middlewares/ │ └─ error.middleware.js |
Crea los directorios vacíos para ir añadiendo archivos.
2) Configura scripts de arranque
Abre package.json y agrega los scripts para desarrollo y producción.
1 2 3 4 5 6 7 8 9 10 11 |
{ "name": "api-tareas-express", "version": "1.0.0", "main": "src/index.js", "type": "commonjs", "scripts": { "dev": "nodemon src/index.js", "start": "node src/index.js" } } |
Nota: Si tu package.json ya tiene campos por defecto, solo añade o ajusta la sección de scripts.
3) Variables de entorno (.env)
Crea un archivo .env en la raíz del proyecto para gestionar datos sensibles:
1 2 3 4 5 |
PORT=4000 MONGODB_URI=mongodb://localhost:27017 MONGODB_DB=tutorial_express NODE_ENV=development |
Si usas MongoDB Atlas, tu MONGODB_URI se verá similar a:
1 2 |
MONGODB_URI=mongodb+srv://usuario:password@cluster0.xxxxxx.mongodb.net |
Asegúrate de no commitear tu .env a repositorios públicos. Añádelo a .gitignore.
4) Conexión a MongoDB con Mongoose
Crea src/config/db.js para centralizar la conexión a la base de datos.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// src/config/db.js const mongoose = require('mongoose'); async function connectDB() { const uri = process.env.MONGODB_URI; const dbName = process.env.MONGODB_DB || 'tutorial_express'; if (!uri) { throw new Error('MONGODB_URI no está definida en el .env'); } mongoose.set('strictQuery', true); await mongoose.connect(uri, { dbName }); console.log(`✅ Conectado a MongoDB: ${dbName}`); } module.exports = { connectDB }; |
5) Crea el servidor básico con Express
Implementa un servidor mínimo que cargue variables de entorno, conecte a MongoDB y exponga un endpoint de estado.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
// src/index.js require('dotenv').config(); const express = require('express'); const cors = require('cors'); const morgan = require('morgan'); const { connectDB } = require('./config/db'); const taskRoutes = require('./routes/task.routes'); const { errorHandler, notFoundHandler } = require('./middlewares/error.middleware'); const app = express(); // Middlewares base app.use(cors()); app.use(express.json()); // parsea JSON del body if (process.env.NODE_ENV !== 'test') { app.use(morgan('dev')); } // Rutas de salud app.get('/health', (req, res) => { res.json({ status: 'ok', uptime: process.uptime() }); }); // Rutas de la API app.use('/api/tasks', taskRoutes); // 404 y errores app.use(notFoundHandler); app.use(errorHandler); const PORT = process.env.PORT || 4000; // Arranque: conecta a DB y levanta servidor connectDB() .then(() => { app.listen(PORT, () => { console.log(`🚀 Servidor escuchando en http://localhost:${PORT}`); }); }) .catch((err) => { console.error('❌ Error al conectar con MongoDB:', err.message); process.exit(1); }); |
Con esto ya tienes un backend con Node.js levantando un servidor. Probemos la ruta de salud:
1 2 |
curl http://localhost:4000/health |
6) Modelo de datos con Mongoose
Definamos un modelo sencillo de tarea con título, descripción y estado.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// src/models/task.model.js const { Schema, model } = require('mongoose'); const TaskSchema = new Schema( { title: { type: String, required: true, trim: true }, description: { type: String, default: '', trim: true }, completed: { type: Boolean, default: false } }, { timestamps: true } ); module.exports = model('Task', TaskSchema); |
7) Controladores: lógica de negocio
Creemos controladores con operaciones CRUD y paginación simple.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
// src/controllers/task.controller.js const Task = require('../models/task.model'); // Crear tarea async function createTask(req, res, next) { try { const { title, description, completed } = req.body; if (!title) return res.status(400).json({ message: 'title es requerido' }); const task = await Task.create({ title, description, completed }); return res.status(201).json(task); } catch (err) { next(err); } } // Listar tareas con paginación y búsqueda por título async function listTasks(req, res, next) { try { const page = Math.max(parseInt(req.query.page || '1', 10), 1); const limit = Math.min(Math.max(parseInt(req.query.limit || '10', 10), 1), 100); const q = (req.query.q || '').trim(); const filter = q ? { title: { $regex: q, $options: 'i' } } : {}; const [items, total] = await Promise.all([ Task.find(filter) .sort({ createdAt: -1 }) .skip((page - 1) * limit) .limit(limit), Task.countDocuments(filter) ]); return res.json({ page, limit, total, pages: Math.ceil(total / limit), items }); } catch (err) { next(err); } } // Obtener una tarea por ID async function getTaskById(req, res, next) { try { const { id } = req.params; const task = await Task.findById(id); if (!task) return res.status(404).json({ message: 'Tarea no encontrada' }); return res.json(task); } catch (err) { next(err); } } // Actualizar una tarea async function updateTask(req, res, next) { try { const { id } = req.params; const update = req.body; const task = await Task.findByIdAndUpdate(id, update, { new: true, runValidators: true }); if (!task) return res.status(404).json({ message: 'Tarea no encontrada' }); return res.json(task); } catch (err) { next(err); } } // Eliminar una tarea async function deleteTask(req, res, next) { try { const { id } = req.params; const task = await Task.findByIdAndDelete(id); if (!task) return res.status(404).json({ message: 'Tarea no encontrada' }); return res.status(204).send(); } catch (err) { next(err); } } module.exports = { createTask, listTasks, getTaskById, updateTask, deleteTask }; |
8) Rutas con Express Router
Define las rutas REST para interactuar con las tareas.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// src/routes/task.routes.js const { Router } = require('express'); const { createTask, listTasks, getTaskById, updateTask, deleteTask } = require('../controllers/task.controller'); const router = Router(); router.get('/', listTasks); router.post('/', createTask); router.get('/:id', getTaskById); router.patch('/:id', updateTask); router.delete('/:id', deleteTask); module.exports = router; |
9) Middleware de errores y 404
Un backend con Node.js robusto debe gestionar errores y rutas no encontradas de forma consistente.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// src/middlewares/error.middleware.js function notFoundHandler(req, res, next) { res.status(404).json({ message: 'Ruta no encontrada' }); } function errorHandler(err, req, res, next) { // eslint-disable-line no-unused-vars console.error('Error:', err); // Errores de validación Mongoose if (err.name === 'ValidationError') { return res.status(400).json({ message: 'Error de validación', errors: err.errors }); } // ID mal formateado if (err.name === 'CastError') { return res.status(400).json({ message: 'ID inválido' }); } return res.status(500).json({ message: 'Error interno del servidor' }); } module.exports = { errorHandler, notFoundHandler }; |
10) Arranca el servidor
Inicia el modo desarrollo con nodemon:
1 2 |
npm run dev |
Deberías ver en consola algo como:
1 2 3 |
✅ Conectado a MongoDB: tutorial_express 🚀 Servidor escuchando en http://localhost:4000 |
11) Probar la API: ejemplos con curl
11.1 Crear una tarea
1 2 3 4 |
curl -X POST http://localhost:4000/api/tasks \ -H 'Content-Type: application/json' \ -d '{"title":"Aprender Express","description":"Seguir un tutorial Node.js Express"}' |
Respuesta (201):
1 2 3 4 5 6 7 8 9 10 |
{ "_id": "6650a...", "title": "Aprender Express", "description": "Seguir un tutorial Node.js Express", "completed": false, "createdAt": "2025-05-01T10:00:00.000Z", "updatedAt": "2025-05-01T10:00:00.000Z", "__v": 0 } |
11.2 Listar tareas con paginación y búsqueda
1 2 |
curl 'http://localhost:4000/api/tasks?page=1&limit=5&q=express' |
11.3 Obtener, actualizar y eliminar por ID
1 2 3 4 5 6 7 8 9 10 11 |
# Obtener curl http://localhost:4000/api/tasks/ID_AQUI # Actualizar (parcial) curl -X PATCH http://localhost:4000/api/tasks/ID_AQUI \ -H 'Content-Type: application/json' \ -d '{"completed":true}' # Eliminar curl -X DELETE http://localhost:4000/api/tasks/ID_AQUI -i |
Consejo: También puedes usar Postman o Insomnia para probar la API con una interfaz visual.
12) Explicación de los conceptos clave
12.1 Crear servidor Node.js con Express
- express() crea la aplicación.
- app.use() registra middleware global.
- app.get(), app.post(), etc., definen rutas.
- app.listen() abre el puerto y deja el servidor a la espera de peticiones.
12.2 Middleware en Express
Un middleware es una función que recibe req, res y next. Sirve para:
- Analizar el body (express.json()).
- Habilitar CORS (cors()).
- Registrar peticiones (morgan).
- Manejar errores de forma centralizada.
Se ejecutan en orden de registro, lo que facilita la composición de funcionalidades.
12.3 Rutas y controladores
Separar rutas de controladores mejora la mantenibilidad:
- Rutas: definen endpoints y métodos HTTP.
- Controladores: implementan la lógica de negocio (leer/parcial/validar datos, llamar a la capa de datos, formatear respuesta).
12.4 Mongoose y modelos
- Un Schema define la forma de los documentos.
- Un Model aplica el schema para crear/leer/actualizar/eliminar en la colección.
- Mongoose también ofrece validaciones y middlewares de documento.
13) Buenas prácticas para aplicaciones web con Express
- Variables de entorno: nunca subas .env al repo; usa dotenv y valores por entorno.
- Validación de entrada: usa Joi, Zod o Yup para validar req.body, req.params y req.query.
- Manejo de errores: un único errorHandler reduce repetición y respuestas inconsistentes.
- Seguridad: añade helmet, limitador de tasa (express-rate-limit) y saneamiento de entrada.
- Logs: usa morgan en desarrollo y un logger estructurado (pino/winston) en producción.
- CORS: restrínge orígenes permitidos en producción.
- Testing: Jest + Supertest para tests de integración en endpoints.
- Linting y formato: ESLint y Prettier para mantener código consistente.
- Versionado de la API: prefija rutas con /api/v1 si prevés cambios futuros.
- Control de errores en promesas: siempre try/catch o captura en then/catch.
- Índices en MongoDB: define índices para campos consultados con frecuencia (por ejemplo, title si buscas por él).
- Desacopla configuración: lecturas de process.env en una capa de config central.
Ejemplo de validación rápida con Zod para createTask:
1 2 |
npm install zod |
1 2 3 4 5 6 7 8 9 10 11 |
// src/validators/task.schema.js const { z } = require('zod'); const createTaskSchema = z.object({ title: z.string().min(1, 'title es requerido'), description: z.string().optional(), completed: z.boolean().optional() }); module.exports = { createTaskSchema }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// src/middlewares/validate.middleware.js function validate(schema) { return (req, res, next) => { const parseResult = schema.safeParse(req.body); if (!parseResult.success) { return res.status(400).json({ message: 'Error de validación', errors: parseResult.error.flatten() }); } req.body = parseResult.data; next(); }; } module.exports = { validate }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// src/routes/task.routes.js (ejemplo utilizando el validador) const { Router } = require('express'); const { createTask, listTasks, getTaskById, updateTask, deleteTask } = require('../controllers/task.controller'); const { validate } = require('../middlewares/validate.middleware'); const { createTaskSchema } = require('../validators/task.schema'); const router = Router(); router.get('/', listTasks); router.post('/', validate(createTaskSchema), createTask); router.get('/:id', getTaskById); router.patch('/:id', updateTask); router.delete('/:id', deleteTask); module.exports = router; |
14) Despliegue básico
- Variables de entorno: configura PORT, MONGODBURI y MONGODBDB en tu plataforma (Railway, Render, Fly.io, etc.).
- Start script: usa npm start.
- Logs y salud: conserva /health para chequeos.
- CORS: configura orígenes permitidos a tu dominio del frontend.
15) Solución de problemas comunes
- Error ECONNREFUSED a MongoDB: verifica que el demonio de MongoDB está corriendo localmente o que tu cadena de conexión de Atlas es correcta y tu IP está permitida.
- CastError al consultar por ID: asegúrate de usar un ObjectId válido de MongoDB.
- CORS bloqueando tu frontend: ajusta cors({ origin: ‘https://tu-dominio.com’ }).
- No se recarga el servidor: ejecuta npm run dev para usar nodemon.
Conclusión: tu primer backend con Node.js está listo
Has construido paso a paso un backend con Node.js y Express, implementando rutas, middleware y conexión a MongoDB con Mongoose. Este tutorial Node.js Express te deja la base perfecta para crear aplicaciones web con Express más complejas: autenticación, autorización por roles, subida de archivos, colas de trabajo y más.
Siguiente reto: añade autenticación JWT, tests de integración y validaciones exhaustivas. Si este tutorial te fue útil, compártelo con tu equipo y guarda la guía como referencia para tu próximo proyecto.
¡Ahora sí, a construir!