Crea endpoints Express siguiendo el patron Controller → Service → Prisma del proyecto Ocralis. Usar cuando se necesita un nuevo endpoint REST con validacion Zod, middleware de autenticacion y servicio.
Crea un nuevo endpoint Express siguiendo el patron del proyecto Ocralis.
controllers/{modulo}.controller.js)import { prisma } from "../prismaClient.js";
import logger from "../utils/logger.js";
import { z } from "zod";
// Schema de validacion Zod
const createSchema = z.object({
nombre: z.string().min(1, "Nombre requerido"),
email: z.string().email("Email invalido"),
// campos con tipos y validaciones segun el recurso
});
export const create = async (req, res) => {
try {
// 1. Validar input con Zod
const validated = createSchema.parse(req.body);
// 2. Obtener usuario autenticado
const usuario = req.user;
// 3. Logica de negocio (llamar servicio si es externo)
const result = await prisma.model.create({
data: { ...validated, usuarioId: usuario.id },
});
// 4. Respuesta consistente
res.status(201).json({ success: true, data: result });
} catch (error) {
if (error instanceof z.ZodError) {
return res.status(400).json({
success: false,
error: "Datos invalidos",
details: error.errors,
});
}
logger.error(error, "Error en create");
res.status(500).json({ success: false, error: "Error interno del servidor" });
}
};
export const list = async (req, res) => {
try {
const usuario = req.user;
const items = await prisma.model.findMany({
where: { usuarioId: usuario.id },
orderBy: { createdAt: "desc" },
});
res.json({ success: true, data: items });
} catch (error) {
logger.error(error, "Error en list");
res.status(500).json({ success: false, error: "Error interno del servidor" });
}
};
export const getById = async (req, res) => {
try {
const { id } = req.params;
const item = await prisma.model.findUnique({
where: { id: parseInt(id) },
});
if (!item) {
return res.status(404).json({ success: false, error: "No encontrado" });
}
res.json({ success: true, data: item });
} catch (error) {
logger.error(error, "Error en getById");
res.status(500).json({ success: false, error: "Error interno del servidor" });
}
};
export const update = async (req, res) => {
try {
const { id } = req.params;
const validated = updateSchema.parse(req.body);
const item = await prisma.model.update({
where: { id: parseInt(id) },
data: validated,
});
res.json({ success: true, data: item });
} catch (error) {
if (error instanceof z.ZodError) {
return res.status(400).json({
success: false,
error: "Datos invalidos",
details: error.errors,
});
}
logger.error(error, "Error en update");
res.status(500).json({ success: false, error: "Error interno del servidor" });
}
};
export const remove = async (req, res) => {
try {
const { id } = req.params;
await prisma.model.delete({ where: { id: parseInt(id) } });
res.json({ success: true, data: { message: "Eliminado correctamente" } });
} catch (error) {
logger.error(error, "Error en remove");
res.status(500).json({ success: false, error: "Error interno del servidor" });
}
};
routes/{modulo}.routes.js)import express from "express";
import { create, list, getById, update, remove } from "../controllers/{modulo}.controller.js";
import { authenticateToken } from "../middleware/auth.middleware.js";
const router = express.Router();
// Aplicar auth a todas las rutas del modulo
router.use(authenticateToken);
router.post("/", create);
router.get("/", list);
router.get("/:id", getById);
router.put("/:id", update);
router.delete("/:id", remove);
export default router;
app.jsimport moduloRoutes from "./routes/{modulo}.routes.js";
app.use("/api/{modulo}", moduloRoutes);
services/{modulo}.service.js)Solo crear si el endpoint necesita integrar con APIs externas (SALTRA, GCS, Gemini, etc.). Para operaciones CRUD puras con Prisma, la logica va directamente en el controller.
| Concepto | Convencion |
|---|---|
| Modulos | ES Modules (import/export, no require) |
| Prisma | import { prisma } from "../prismaClient.js" |
| Auth | req.user contiene el usuario autenticado (set por authenticateToken) |
| Respuestas | { success: true/false, data: ..., error: "mensaje" } |
| Logging | Pino: logger.info, logger.error, logger.debug |
| Validacion | Zod inline en el controller |
| IDs | Integer autoincrement en Prisma |
| Errores Zod | Status 400 con details: error.errors |
| Errores 404 | { success: false, error: "No encontrado" } |
| Errores 500 | { success: false, error: "Error interno del servidor" } |
authenticateToken middlewareapp.js con prefijo /api/{modulo}success/data/error)parseInt() en params