The Task Manager backend is built with Express.js 5.1.0, following a layered architecture pattern that separates concerns into distinct components. The application uses ES6 modules and implements RESTful API endpoints for task and category management.
import { Router } from "express";import { TaskCategoryController } from "../controllers/taskCategory.js";export const taskCategoriesRouter = Router();taskCategoriesRouter.post("/", TaskCategoryController.create);// Delete uses POST because it expects an array in req.bodytaskCategoriesRouter.post("/delete", TaskCategoryController.delete);taskCategoriesRouter.get("/", TaskCategoryController.getAll);taskCategoriesRouter.get("/:id", TaskCategoryController.getById);taskCategoriesRouter.patch("/:id", TaskCategoryController.update);
The category delete endpoint uses POST instead of DELETE because it accepts an array of category IDs in the request body for batch deletion.
import { TaskCategoryModel } from "../models/taskCategory.js";import { validateTaskCategory } from "../schemas/taskCategory.js";import { TaskCategoryService } from "../services/taskCategory.js";export class TaskCategoryController { static async create(req, res) { const validate = validateTaskCategory(req.body); if (validate.error) { res.status(400).json(validate.error); return; } try { const newCategory = TaskCategoryModel.create({ input: validate.data }); res.status(201).json(newCategory); } catch (error) { console.error("[CategoryController Error]:", error); return res.status(500).json({ error: "No se pudo crear la categoría en el servidor" }); } } static async delete(req, res) { const { categoriesIds } = req.body; if (!Array.isArray(categoriesIds)) { return res.status(400).json({ error: "Datos no validos, se esperaba un array" }); } try { const result = await TaskCategoryService.deleteCategories(categoriesIds); if (result === false) { return res.status(404).json({ message: "Categories not found" }); } res.status(204).end(); } catch (error) { console.error("Error en CategoryController.delete:", error); return res.status(500).json({ message: "Error interno al procesar el borrado masivo" }); } }}
The category delete operation uses a service layer (TaskCategoryService) because it involves complex business logic: updating all tasks in the deleted categories before removing the categories themselves.
import z from 'zod';const taskCategorySchema = z.object({ name: z.string().min(2).max(25)});export function validateTaskCategory(input) { return taskCategorySchema.safeParse(input);}
Services contain complex business logic that spans multiple models.
import { TaskModel } from "../models/task.js";import { TaskCategoryModel } from "../models/taskCategory.js";export class TaskCategoryService { static async deleteCategories(categoriesId) { // Update tasks with deleted categories to "uncategorized" await TaskModel.updateMany("categoryId", categoriesId, "uncategorized"); // Delete the categories const result = await TaskCategoryModel.delete(categoriesId); return result !== false; }}
The deleteCategories service method demonstrates a transactional operation: before deleting categories, it updates all associated tasks to prevent orphaned references.
import cors from 'cors';const ACCEPTED_ORIGINS = [ 'http://localhost:3000', 'http://127.0.0.1:3000',];export const corsMiddleware = ({acceptedOrigins = ACCEPTED_ORIGINS} = {}) => cors({ origin: (origin, callback) => { if (acceptedOrigins.includes(origin)) { return callback(null, true); } // Allow requests without origin (same-site requests) if (!origin) { return callback(null, true); } return callback(new Error('No permitido por CORS')); }});
The CORS middleware allows requests from localhost and same-origin requests (server-rendered pages). This is configurable through the acceptedOrigins parameter.