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 ;
}
}
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:
Validate Task Exists
Check if the task exists before attempting updates
Clean Input Data
Remove undefined fields to prevent overwriting existing data
Update Timestamp
Automatically set updatedAt to current time
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 });
}
Controller - Get All
Controller - Get by ID
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