Skip to main content

Overview

The Task Manager application provides robust task management capabilities with automatic timestamp tracking, completion status management, and comprehensive validation. Tasks support full CRUD operations with proper state management and data integrity.

Task Data Model

Each task contains the following properties:
  • _id: Unique identifier (UUID)
  • title: Task title (3-25 characters, required)
  • description: Task description (max 25 characters, optional)
  • categoryId: Associated category UUID (optional)
  • completed: Boolean completion status
  • createdAt: ISO 8601 timestamp when task was created
  • updatedAt: ISO 8601 timestamp of last modification
  • finishedAt: ISO 8601 timestamp when task was marked as completed

Creating Tasks

Backend Implementation

Tasks are created with automatic UUID generation and timestamp initialization:
~/workspace/source/src/models/task.js
import { randomUUID } from "node:crypto";
import { Tasks } from "../DB/DB_schemas.js";

export class TaskModel {
  static create({ input }) {
    const newTask = {
      _id: randomUUID(),
      title: input.title,
      description: input.description,
      categoryId: input.categoryId,
      createdAt: new Date().toISOString(),
      completed: input.completed,
    };
    Tasks.create(newTask).save();
    return newTask;
  }
}

Input Validation

All task data is validated using Zod schemas before processing:
~/workspace/source/src/schemas/task.js
import z from 'zod';

const taskSchema = z.object({
  title: z.string().min(3).max(25),
  description: z.string().max(25).optional(),
  completed: z.boolean().optional(),
  categoryId: z.string().optional(),
  createdAt: z.string().optional().refine(v => !v || !isNaN(Date.parse(v)), {
    message: "Invalid date format createAt"
  }),
  updatedAt: z.string().optional().refine(v => !v || !isNaN(Date.parse(v)), {
    message: "Invalid date format updateAt"
  }),
  finishedAt: z.string().optional().refine(v => !v || !isNaN(Date.parse(v)), {
    message: "Invalid date format finishedAt"
  })
});

export function validateTask(input) {
  return taskSchema.safeParse(input);
}

export function validatePartialTask(input) {
  return taskSchema.partial().safeParse(input);
}
The validation ensures title length between 3-25 characters and validates all timestamp fields as valid ISO 8601 date strings.

API Endpoint

The controller handles validation and responds with appropriate status codes:
~/workspace/source/src/controllers/task.js
import { TaskModel } from "../models/task.js";
import { validateTask } from "../schemas/task.js";

export class TaskController {
  static async create(req, res) {
    const validate = validateTask(req.body);
    if (validate.error) {
      res.status(400).json(validate.error);
    }
    const newTask = TaskModel.create({ input: validate.data });
    res.status(201).json(newTask);
  }
}

Updating Tasks

Automatic Timestamp Management

The update operation intelligently manages timestamps:
1

Validate Task Exists

Check if the task exists before attempting updates
2

Clean Input Data

Remove undefined fields to prevent overwriting existing data
3

Update Timestamp

Automatically set updatedAt to current time
4

Manage Completion Status

Handle finishedAt timestamp based on completion state changes

Smart Completion Tracking

~/workspace/source/src/models/task.js
static async update(taskId, input) {
  const taskExists = await this.getById(taskId);
  if (!taskExists) {
    return null;
  }

  // Extract and filter undefined values
  const { title, description, categoryId, completed, createdAt, updatedAt, finishedAt } = input;
  const inputData = { title, description, categoryId, completed, createdAt, updatedAt, finishedAt };
  
  const cleanInputData = Object.fromEntries(
    Object.entries(inputData).filter(([_, value]) => value !== undefined)
  );
  
  // Always add updatedAt
  cleanInputData.updatedAt = new Date().toISOString();

  // Handle completion status changes
  if ("completed" in cleanInputData) {
    const newCompleted = cleanInputData.completed;
    const oldCompleted = !!taskExists.completed;

    if (oldCompleted === false && newCompleted === true) {
      // Task just completed - set finishedAt
      cleanInputData.finishedAt = new Date().toISOString();
    }
    if (oldCompleted === true && newCompleted === false) {
      // Task uncompleted - remove finishedAt
      delete taskExists.finishedAt;
    }
  }

  await taskExists.update(cleanInputData).save();
  const fullTask = await this.getById(taskId);
  return fullTask;
}
The finishedAt timestamp is automatically set when a task transitions from incomplete to complete, and removed when uncompleted.

Partial Updates

The API supports PATCH requests for partial updates:
~/workspace/source/src/controllers/task.js
static async update(req, res) {
  const validate = validatePartialTask(req.body);
  if (validate.error) {
    res.status(400).json({ error: "Datos no validos o incompletos" });
  }

  const { id } = req.params;
  const updatedTask = await TaskModel.update(id, validate.data);
  if (!updatedTask) {
    res.status(404).json({ message: "Task not found" });
  }
  res.status(200).json(updatedTask);
}

Reading Tasks

Get All Tasks

Retrieve all tasks with optional filtering:
~/workspace/source/src/models/task.js
static async getAll(filters = {}) {
  const query = {};
  if (filters.categoryId) {
    query.categoryId = filters.categoryId;
  }
  return await Tasks.find(filters);
}

Get Task by ID

~/workspace/source/src/models/task.js
static async getById(taskId) {
  return await Tasks.findOne({ _id: taskId });
}
static async getAll(req, res) {
  const tasks = await TaskModel.getAll();
  res.status(200).json(tasks);
}

Deleting Tasks

~/workspace/source/src/models/task.js
static async delete(taskId) {
  const taskExists = await this.getById(taskId);
  if (!taskExists) {
    return false;
  }
  Tasks.remove(taskId);
  return taskId;
}
The controller ensures proper status codes:
~/workspace/source/src/controllers/task.js
static async delete(req, res) {
  const { id } = req.params;
  const result = await TaskModel.delete(id);
  if (result === false) {
    res.status(404).json({ message: "Task not found" });
  }
  res.status(204).end();
}

Bulk Operations

Update Many Tasks

The model supports bulk updates for operations like category reassignment:
~/workspace/source/src/models/task.js
static async updateMany(propiedad, values, newValue) {
  const filterQuery = { [propiedad]: { $in: values } };
  const tasksToUpdate = await Tasks.find(filterQuery);
  if (tasksToUpdate.length === 0) return 0;

  tasksToUpdate.forEach((task) => {
    task
      .update({
        [propiedad]: newValue,
        updatedAt: new Date().toISOString(),
      })
      .save();
  });
  return tasksToUpdate.length;
}
When using updateMany, the updatedAt timestamp is automatically set for each task, but finishedAt logic is not applied. Use individual updates if completion status changes.

API Routes

All task endpoints are defined in the router:
~/workspace/source/src/routes/tasks.js
import { Router } from "express";
import { TaskController } from "../controllers/task.js";

export const tasksRouter = Router();

tasksRouter.post("/", TaskController.create);
tasksRouter.delete("/:id", TaskController.delete);
tasksRouter.get("/:id", TaskController.getById);
tasksRouter.get("/", TaskController.getAll);
tasksRouter.patch("/:id", TaskController.update);

Error Handling

400 Bad Request

Invalid input data that fails Zod validation

404 Not Found

Task ID does not exist in database

200 OK

Successful read or update operation

201 Created

Task successfully created

204 No Content

Task successfully deleted

Best Practices

Timestamp Management: The application automatically handles all timestamp fields. Never manually set createdAt, updatedAt, or finishedAt in your API calls.
Validation: Always validate input on both frontend and backend. The Zod schemas provide type-safe validation with clear error messages.
Partial Updates: Use PATCH with validatePartialTask() to update only specific fields without requiring all task properties.

Next Steps

Categories

Learn how to organize tasks with categories

Frontend UI

Explore the vanilla JavaScript frontend implementation

Build docs developers (and LLMs) love