1. Inicializar el proyecto con TypeScript1. Initialize the Project with TypeScript
Empezamos creando el proyecto desde cero con npm. La clave es configurar TypeScript correctamente desde el principio para no tener deuda técnica después. We start by creating the project from scratch with npm. The key is to configure TypeScript correctly from the start to avoid technical debt later.
mkdir api-rest-ts && cd api-rest-ts
npm init -y
npm install express
npm install -D typescript @types/node @types/express ts-node-dev
npx tsc --init Actualiza tsconfig.json con estas opciones importantes: Update tsconfig.json with these important options:
{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"resolveJsonModule": true
}
} 2. Estructura del proyecto2. Project Structure
Una API bien organizada sigue una arquitectura en capas que separa responsabilidades. Esta es la estructura recomendada para proyectos escalables: A well-organized API follows a layered architecture that separates concerns. This is the recommended structure for scalable projects:
src/
├── controllers/ # lógica de peticiones HTTP
├── services/ # lógica de negocio
├── repositories/ # acceso a datos
├── middlewares/ # auth, validación, errores
├── routes/ # definición de rutas
├── models/ # tipos e interfaces
└── index.ts # entry point Esta separación es fundamental: los controllers solo manejan HTTP (request/response), los services contienen la lógica de negocio (completamente testeable sin HTTP), y los repositories se comunican con la base de datos. This separation is key: controllers only handle HTTP (request/response), services hold the business logic (fully testable without HTTP), and repositories communicate with the database.
3. Crear el servidor Express con TypeScript3. Create the Express Server with TypeScript
// src/index.ts
import express, { Application } from 'express';
import cors from 'cors';
import helmet from 'helmet';
import { authRouter } from './routes/auth.routes';
import { userRouter } from './routes/user.routes';
const app: Application = express();
// Middlewares globales
app.use(helmet()); // cabeceras de seguridad
app.use(cors({ origin: process.env.ALLOWED_ORIGIN }));
app.use(express.json({ limit: '10mb' }));
// Rutas
app.use('/api/v1/auth', authRouter);
app.use('/api/v1/users', userRouter);
// Health check
app.get('/health', (_, res) => res.json({ status: 'ok' }));
const PORT = process.env.PORT ?? 3000;
app.listen(PORT, () => console.log(`API corriendo en :${PORT}`)); Nota el uso de helmet() para cabeceras de seguridad HTTP y cors con origin explícito — nunca uses cors() sin restricciones en producción. Note the use of helmet() for HTTP security headers and cors with an explicit origin — never use an unrestricted cors() in production.
4. Autenticación con JWT4. Authentication with JWT
JWT (JSON Web Token) es el estándar para autenticación sin estado en APIs REST. La implementación segura requiere tokens de acceso de corta duración más un refresh token: JWT (JSON Web Token) is the standard for stateless authentication in REST APIs. A secure implementation requires short-lived access tokens plus a refresh token:
// src/services/auth.service.ts
import jwt from 'jsonwebtoken';
import bcrypt from 'bcryptjs';
const ACCESS_SECRET = process.env.JWT_ACCESS_SECRET!;
const REFRESH_SECRET = process.env.JWT_REFRESH_SECRET!;
export function generateTokens(userId: string) {
const accessToken = jwt.sign({ sub: userId }, ACCESS_SECRET, { expiresIn: '15m' });
const refreshToken = jwt.sign({ sub: userId }, REFRESH_SECRET, { expiresIn: '7d' });
return { accessToken, refreshToken };
}
export async function hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, 12); // cost factor 12 para 2024+
}
export async function verifyPassword(plain: string, hashed: string): Promise<boolean> {
return bcrypt.compare(plain, hashed);
} 5. Middleware de autenticación5. Authentication Middleware
// src/middlewares/auth.middleware.ts
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
interface JwtPayload { sub: string; }
export function requireAuth(req: Request, res: Response, next: NextFunction): void {
const header = req.headers.authorization;
if (!header?.startsWith('Bearer ')) {
res.status(401).json({ error: 'Token requerido' });
return;
}
try {
const token = header.slice(7);
const payload = jwt.verify(token, process.env.JWT_ACCESS_SECRET!) as JwtPayload;
(req as any).userId = payload.sub;
next();
} catch {
res.status(401).json({ error: 'Token inválido o expirado' });
}
} 6. Validación de datos con Zod6. Data Validation with Zod
Zod es la librería de validación más popular en el ecosistema TypeScript porque sus esquemas son simultáneamente validadores en runtime y generadores de tipos en compile-time: Zod is the most popular validation library in the TypeScript ecosystem because its schemas are simultaneously runtime validators and compile-time type generators:
import { z } from 'zod';
export const CreateUserSchema = z.object({
name: z.string().min(2).max(100).trim(),
email: z.string().email().toLowerCase(),
password: z.string().min(8).regex(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/,
'Debe tener mayúsculas, minúsculas y números'
),
});
export type CreateUserDTO = z.infer<typeof CreateUserSchema>; 7. Manejo centralizado de errores7. Centralized Error Handling
Un error handler global en Express evita que errores no capturados expongan detalles del servidor al cliente: A global error handler in Express prevents uncaught errors from leaking server details to the client:
// src/middlewares/error.middleware.ts
import { Request, Response, NextFunction } from 'express';
export function errorHandler(err: Error, _req: Request, res: Response, _next: NextFunction) {
console.error(`[ERROR] ${err.message}`);
if (err.name === 'ValidationError') {
return res.status(400).json({ error: err.message });
}
if (err.name === 'UnauthorizedError') {
return res.status(401).json({ error: 'No autorizado' });
}
// En producción, nunca expongas el stack trace
res.status(500).json({ error: 'Error interno del servidor' });
} ConclusiónConclusion
Una API REST con Node.js, Express y TypeScript bien construida es la base de cualquier aplicación web moderna. Los puntos clave son: arquitectura en capas, JWT con tokens de corta duración, validación con Zod y un error handler que no filtre información sensible. A well-built REST API with Node.js, Express and TypeScript is the foundation of any modern web application. The key points are: layered architecture, short-lived JWT tokens, Zod validation and an error handler that never leaks sensitive information.
Si necesitas una API REST, microservicio o backend completo, contáctame. Construyo backends escalables con Node.js, TypeScript, PostgreSQL y también con Java Spring Boot para requisitos enterprise. If you need a REST API, microservice or full backend, contact me. I build scalable backends with Node.js, TypeScript, PostgreSQL and Java Spring Boot for enterprise requirements.