En el desarrollo backend, combinar Node.js con TypeScript ofrece un entorno robusto y tipado, mientras que JWT permite gestionar la autenticación de forma stateless. En este tutorial crearemos una API RESTful completa, desde la configuración del proyecto hasta su despliegue con Docker.

Tabla de contenidos

guía paso a paso: Construye una API RESTful con Node.js, TypeScript y JWT: guía paso a paso software development concept - imagen ilustrativa
Foto por Branko Stancevic en Unsplash

Arquitectura de la API

Configuración del proyecto Construye API RESTful - imagen ilustrativa
Foto por Bernd 📷 Dittrich en Unsplash

Una arquitectura limpia separa responsabilidades en capas: router, controller, service y repository. Esta separación facilita pruebas unitarias y el mantenimiento a largo plazo.

En nuestro caso utilizaremos la siguiente estructura de carpetas:

src/
│   index.ts               # punto de entrada
│   app.ts                 # configuración de Express
│
├─ routes/
│   user.routes.ts        # definición de endpoints
│
├─ controllers/
│   user.controller.ts    # lógica de petición
│
├─ services/
│   user.service.ts       # reglas de negocio
│
└─ models/
    user.model.ts         # definición del modelo (ejemplo con Prisma)

Esta disposición sigue el patrón Node.js y es compatible con cualquier ORM o base de datos que prefieras.

Configuración del proyecto

Implementación de rutas y controladores Construye API RESTful - imagen ilustrativa
Foto por Bernd 📷 Dittrich en Unsplash

Inicializamos un proyecto npm y añadimos dependencias esenciales:

npm init -y
npm install express cors helmet jsonwebtoken dotenv
npm install -D typescript ts-node-dev @types/express @types/node @types/jsonwebtoken

Crearemos el archivo tsconfig.json con una configuración mínima:

{
  "compilerOptions": {
    "target": "es2020",
    "module": "commonjs",
    "outDir": "dist",
    "rootDir": "src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  }
}

El archivo principal src/index.ts levanta el servidor:

import express from 'express';
import helmet from 'helmet';
import cors from 'cors';
import dotenv from 'dotenv';
import userRouter from './routes/user.routes';

dotenv.config();

const app = express();
app.use(helmet());
app.use(cors());
app.use(express.json());

app.use('/api/users', userRouter);

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Servidor corriendo en puerto ${PORT}`));

Implementación de rutas y controladores

Definimos un router básico en src/routes/user.routes.ts:

import { Router } from 'express';
import { getAllUsers, registerUser, loginUser } from '../controllers/user.controller';

const router = Router();

router.get('/', getAllUsers);
router.post('/register', registerUser);
router.post('/login', loginUser);

export default router;

Los controladores gestionan la lógica de cada endpoint:

import { Request, Response } from 'express';
import { createUser, findUserByEmail, getUsers } from '../services/user.service';
import jwt from 'jsonwebtoken';

export const getAllUsers = async (req: Request, res: Response) => {
  const users = await getUsers();
  res.json(users);
};

export const registerUser = async (req: Request, res: Response) => {
  const { email, password } = req.body;
  const user = await createUser(email, password);
  res.status(201).json(user);
};

export const loginUser = async (req: Request, res: Response) => {
  const { email, password } = req.body;
  const user = await findUserByEmail(email);
  if (!user || user.password !== password) {
    return res.status(401).json({ message: 'Credenciales inválidas' });
  }
  const token = jwt.sign({ sub: user.id }, process.env.JWT_SECRET as string, { expiresIn: '1h' });
  res.json({ token });
};

Seguridad con JWT

Para proteger rutas privadas crearemos un middleware de verificación:

import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';

export const verifyToken = (req: Request, res: Response, next: NextFunction) => {
  const authHeader = req.headers.authorization;
  if (!authHeader) {
    return res.status(401).json({ message: 'Token ausente' });
  }
  const token = authHeader.split(' ')[1];
  try {
    const payload = jwt.verify(token, process.env.JWT_SECRET as string);
    // @ts-ignore
    req.user = payload;
    next();
  } catch (error) {
    res.status(403).json({ message: 'Token inválido' });
  }
};

Aplicamos el middleware a cualquier ruta que requiera autenticación:

router.get('/profile', verifyToken, getUserProfile);

El secreto se define en un archivo .env:

JWT_SECRET=mi_secreto_super_seguro
PORT=4000

Contenerización con Docker

Para garantizar que la API se ejecuta de forma idéntica en cualquier entorno, crearemos una imagen Docker:

# Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["node", "dist/index.js"]

El docker-compose.yml permite levantar la API junto a una base de datos (ejemplo PostgreSQL):

version: '3.8'
services:
  api:
    build: .
    ports:
      - "3000:3000"
    env_file:
      - .env
    depends_on:
      - db
  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      POSTGRES_DB: mydb
    volumes:
      - pgdata:/var/lib/postgresql/data
volumes:
  pgdata:

Con docker compose up --build tendrás la API disponible en http://localhost:3000.

Conclusión

Hemos cubierto todo el proceso para crear una API RESTful con Node.js, TypeScript, autenticación basada en JWT y despliegue mediante Docker. Aplicar una arquitectura modular y seguir las mejores prácticas de seguridad garantiza un backend mantenible y listo para escalar.

Referencias