Guía para Construir un API RESTful
Flujo paso a paso para exponer un API REST consistente con Fox Framework.
Contenido
- Estructura del Proyecto
- Modelos y DTOs
- Servicios (Business Logic)
- Controladores
- Rutas y Middlewares
- Validación
- Errores y Respuestas
- Versionado de API
Estructura del Proyecto
src/
controllers/
user.controller.ts
services/
user.service.ts
routes/
user.routes.ts
middleware/
auth.middleware.ts
validation/
user.schema.tsModelos y DTOs
// src/dto/user.dto.ts
export interface CreateUserDTO { name:string; email:string; password:string; }
export interface UserResponseDTO { id:string; name:string; email:string; createdAt:string; }Servicios (Business Logic)
// src/services/user.service.ts
export class UserService {
constructor(private users: UserRepository) {}
async create(data: CreateUserDTO): Promise<UserResponseDTO> {
// hash password, validaciones extra
const entity = await this.users.create({ ...data, passwordHash: 'hashed' });
return this.toDTO(entity);
}
async find(id:string){ const u = await this.users.findById(id); return u? this.toDTO(u): null; }
private toDTO(u:any): UserResponseDTO { return { id:u.id, name:u.name, email:u.email, createdAt:u.created_at }; }
}Controladores
// src/controllers/user.controller.ts
import { Request, Response } from 'fox-framework';
export class UserController {
constructor(private service: UserService) {}
create = async (req:Request,res:Response) => {
const dto = await this.service.create(req.body);
res.status(201).json({ success:true, data:dto });
};
getById = async (req:Request,res:Response) => {
const user = await this.service.find(req.params.id);
if(!user) return res.status(404).json({ success:false, message:'User not found' });
res.json({ success:true, data:user });
};
}Rutas y Middlewares
// src/routes/user.routes.ts
import { Router } from 'fox-framework';
const router = Router.create();
const controller = new UserController(userService);
router.post('/users', validate(createUserSchema), controller.create);
router.get('/users/:id', controller.getById);
export default router;Registro en servidor:
app.use('/api/v1', userRoutes);Validación
// src/validation/user.schema.ts
import { z } from 'zod';
export const createUserSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
password: z.string().min(8)
});
export function validate(schema){
return (req,res,next)=> {
const result = schema.safeParse(req.body);
if(!result.success){
return res.status(422).json({ success:false, errors: result.error.issues });
}
req.body = result.data; next();
};
}Errores y Respuestas
Middleware de error uniforme:
app.use((err,req,res,next)=> {
const status = err.status || 500;
res.status(status).json({ success:false, message: err.message, code: err.code });
});Formato estándar de respuesta:
{ "success": true, "data": { ... } }
{ "success": false, "message": "..." }Versionado de API
Estrategias:
- Prefijo path (
/api/v1) recomendado - Cambios breaking -> nueva versión (
/api/v2) - Mantener al menos dos versiones activas durante migración
Routing multi-versión:
app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);Mejores Prácticas
- Stateless (no almacenar sesión server-side salvo tokens revocados)
- Idempotencia en PUT/PATCH
- Usar códigos HTTP correctos (201 en creaciones)
- Paginación consistente (
?cursor=o?page=) - Documentar (OpenAPI) y validar contratos