Node.js y ExpressTesting y ProducciónLección 20 de 20
Proyecto: API Tareas con Auth
En este proyecto final construirás una API REST completa de gestión de tareas. Tendrá registro e inicio de sesión con JWT, contraseñas hasheadas con bcrypt, validación de datos con Zod, base de datos PostgreSQL con Prisma, y tests automatizados con Supertest y Jest.
Esta API es exactamente el tipo de backend que se construye en proyectos reales. Al terminarla entenderás el flujo completo de una petición HTTP: desde que llega al servidor, pasa por middlewares de autenticación y validación, ejecuta lógica de negocio, consulta la base de datos, y devuelve una respuesta JSON estructurada.
// src/index.js — Entry point
const app = require('./app');
const config = require('./config/env');
const { PrismaClient } = require('@prisma/client');
const db = new PrismaClient();
async function main() {
// Verificar conexión a BD antes de arrancar
await db.$connect();
console.log('✅ PostgreSQL conectado');
app.listen(config.port, () => {
console.log(`🚀 API corriendo en http://localhost:${config.port}`);
});
}
main().catch((err) => {
console.error('❌ Error al iniciar:', err);
process.exit(1);
});
// -------------------------------------------------
// src/app.js
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const authRouter = require('./routes/auth.routes');
const tareasRouter = require('./routes/tareas.routes');
const { errorHandler } = require('./middlewares/error.middleware');
const app = express();
app.use(helmet());
app.use(cors({ origin: process.env.CORS_ORIGIN }));
app.use(express.json({ limit: '10kb' }));
app.get('/health', (req, res) => res.json({ status: 'ok' }));
app.use('/api/v1/auth', authRouter);
app.use('/api/v1/tareas', tareasRouter);
app.use(errorHandler);
module.exports = app;
// -------------------------------------------------
// src/routes/tareas.routes.js
const { Router } = require('express');
const tareasController = require('../controllers/tareas.controller');
const { authMiddleware } = require('../middlewares/auth.middleware');
const { validar } = require('../middlewares/validar.middleware');
const { crearTareaSchema, actualizarTareaSchema } = require('../schemas/tarea.schema');
const router = Router();
// Todas las rutas de tareas requieren autenticación
router.use(authMiddleware);
router.get('/', tareasController.listar);
router.post('/', validar(crearTareaSchema), tareasController.crear);
router.get('/:id', tareasController.obtener);
router.patch('/:id', validar(actualizarTareaSchema), tareasController.actualizar);
router.delete('/:id', tareasController.eliminar);
module.exports = router;
// -------------------------------------------------
// src/services/tareas.service.js
const db = require('../db'); // singleton PrismaClient
const listar = async ({ usuarioId, page, limit, q }) => {
const skip = (page - 1) * limit;
const where = {
usuarioId,
...(q && { titulo: { contains: q, mode: 'insensitive' } })
};
const [tareas, total] = await Promise.all([
db.tarea.findMany({ where, skip, take: limit, orderBy: { creadoEn: 'desc' } }),
db.tarea.count({ where }),
]);
return { data: tareas, meta: { total, page, limit, totalPages: Math.ceil(total / limit) } };
};
const crear = async (datos) => db.tarea.create({ data: datos });
const obtenerPorId = async (id, usuarioId) =>
db.tarea.findFirst({ where: { id, usuarioId } });
const actualizar = async (id, usuarioId, datos) =>
db.tarea.updateMany({ where: { id, usuarioId }, data: datos });
const eliminar = async (id, usuarioId) =>
db.tarea.deleteMany({ where: { id, usuarioId } });
module.exports = { listar, crear, obtenerPorId, actualizar, eliminar };
ConsejoEl patrón deleteMany/updateMany con { id, usuarioId } en el where es una técnica de seguridad: garantiza que un usuario solo puede modificar SUS propias tareas, sin necesidad de un check separado.
Practica esta lección
Resuelve los ejercicios del curso de Node.js y Express en la app, gratis y con corrección automática.
Empezar a practicar