From 7974c9f3121cee445f37ddbf87bcce145f2e364f Mon Sep 17 00:00:00 2001 From: Acer Date: Mon, 3 Nov 2025 11:08:41 +0530 Subject: [PATCH 01/11] set up databse connection and create mongodb schema --- src/models/taskModel.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/models/taskModel.js b/src/models/taskModel.js index 453025b..38bbc5d 100644 --- a/src/models/taskModel.js +++ b/src/models/taskModel.js @@ -21,6 +21,12 @@ const taskModel = mongoose.Schema({ enum : ["pending", "in-progress", "completed"], default : "pending", required : true - }} + }}, + + { + timestamps : true -}) \ No newline at end of file + } +); + +export default mongoose.model("Tasks", taskModel) \ No newline at end of file From a061c152419b888c60ad4c61777019077d5c6741 Mon Sep 17 00:00:00 2001 From: Acer Date: Mon, 3 Nov 2025 11:41:19 +0530 Subject: [PATCH 02/11] create taskDto and taskService --- src/controller.js | 5 ----- src/dto/taskDto.js | 10 ++++++++++ src/services/taskService.js | 19 +++++++++++++++++++ 3 files changed, 29 insertions(+), 5 deletions(-) delete mode 100644 src/controller.js create mode 100644 src/dto/taskDto.js create mode 100644 src/services/taskService.js diff --git a/src/controller.js b/src/controller.js deleted file mode 100644 index b45e9f8..0000000 --- a/src/controller.js +++ /dev/null @@ -1,5 +0,0 @@ -export const myName = async () =>{ - const a = "John Doe"; - return a; - -} \ No newline at end of file diff --git a/src/dto/taskDto.js b/src/dto/taskDto.js new file mode 100644 index 0000000..70d6ffb --- /dev/null +++ b/src/dto/taskDto.js @@ -0,0 +1,10 @@ +import { status } from "express/lib/response"; +import { title } from "process"; + +export const TaskDto ={ + id : String, + title : String, + description : String, + status : String, + +} \ No newline at end of file diff --git a/src/services/taskService.js b/src/services/taskService.js new file mode 100644 index 0000000..db90dee --- /dev/null +++ b/src/services/taskService.js @@ -0,0 +1,19 @@ +import { TaskDto } from "../dto/taskDto"; +import taskModel from "../models/taskModel" + +export const getAllTasks = async () =>{ + return await taskModel.find(); +} + +export const createTask = async (TaskDto) => { + return await taskModel.create(TaskDto) + +} + +export const deleteTaskById = async (id) => { + return await taskModel.findByIdAndDelete(id); +} + +export const updateTaskById = async (id, TaskDto) => { + return await taskModel.findByIdAndUpdate(id, TaskDto, {new : true}); +} From 303c881f48db966deb79c2294b69565612919c77 Mon Sep 17 00:00:00 2001 From: Acer Date: Mon, 3 Nov 2025 15:18:48 +0530 Subject: [PATCH 03/11] create taskController --- src/controllers/taskController.js | 56 +++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 src/controllers/taskController.js diff --git a/src/controllers/taskController.js b/src/controllers/taskController.js new file mode 100644 index 0000000..11d4ffa --- /dev/null +++ b/src/controllers/taskController.js @@ -0,0 +1,56 @@ +import { request , response } from "express" +import res from "express/lib/response"; +import * as taskService from "../services/taskService.js" + +export const getAllTask = async (request , response) =>{ + try { + const tasks = await taskService.getAllTasks(); + response.status(200).json({message : "Tasks fetched successfully", data : tasks}); + + }catch (error) { + response.status(500).json({message : "Error while fetching tasks", error : error.message}); + } +} + +export const createTask = async (request , response) => { + try { + const newTask = request.body; + const createdTask = await taskService.createTask(newTask) + response.status(201).json({message : "Task created successfully" , data : createdTask}); + + }catch (error) { + response.status(500).json({message : "Error while creating task", error : error.message}); + } +} + +export const deleteTask = async (request , response) => { + try { + const taskId = request.params.id; + const deleteTask = await taskService.deleteTaskById(taskId); + + if(!deleteTask) { + return response.status(404).json({message : "Task not found"}); + } + + return response.status(200).json({message : "Task deleted successfully", data : deleteTask}); + + }catch (error) { + return response.status(500).json({message : "Error while deleting task", error : error.message}); + } +} + +export const updateTask = async (request , response) => { + try { + const taskId = request.params.id; + const taskData = request.body; + const updateTask = await taskService.updateTaskById(taskId,taskData); + if (!updateTask) { + return response.status(400).json({message : "Task not found"}); + } + + return response.status(200).json({message : "Task updated successfully", data : updateTask}) + + }catch (error) { + return response.status(500).json({message : "Error while updating task", error : error.message}) + } +} \ No newline at end of file From 9ccff8e9d7654efa3bb4ad9ea4f9aee7e2fb3e35 Mon Sep 17 00:00:00 2001 From: Acer Date: Tue, 4 Nov 2025 10:06:54 +0530 Subject: [PATCH 04/11] create app.js and index.js and add auto-generated incremental ID for tasks --- package.json | 4 +++- src/app.js | 12 +++++++++++ src/controllers/taskController.js | 1 - src/index.js | 11 ++++++++++ src/models/taskModel.js | 3 ++- src/routes/taskRoute.js | 8 +++++++ src/services/taskService.js | 35 ++++++++++++++++++------------- 7 files changed, 57 insertions(+), 17 deletions(-) create mode 100644 src/app.js create mode 100644 src/index.js create mode 100644 src/routes/taskRoute.js diff --git a/package.json b/package.json index 1943d9f..276f24c 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,10 @@ "name": "task-api", "version": "1.0.0", "main": "index.js", + "type": "module", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "dev": "nodemon src/index.js" }, "keywords": [], "author": "", diff --git a/src/app.js b/src/app.js new file mode 100644 index 0000000..d840a07 --- /dev/null +++ b/src/app.js @@ -0,0 +1,12 @@ +import express from "express"; +import { taskRouter } from "./routes/taskRoute.js"; + +const app = express(); + +// middleware +app.use(express.json()); + +// routes +app.use("/api/v1/tasks" , taskRouter) + +export default app; \ No newline at end of file diff --git a/src/controllers/taskController.js b/src/controllers/taskController.js index 11d4ffa..ecba8ef 100644 --- a/src/controllers/taskController.js +++ b/src/controllers/taskController.js @@ -1,5 +1,4 @@ import { request , response } from "express" -import res from "express/lib/response"; import * as taskService from "../services/taskService.js" export const getAllTask = async (request , response) =>{ diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..01c89f3 --- /dev/null +++ b/src/index.js @@ -0,0 +1,11 @@ +import {DBConnection} from "./config/dbConnection.js"; +import app from "./app.js"; +import dotenv from "dotenv"; + +dotenv.config(); + + +const port = process.env.PORT || 3000; +DBConnection().then(result => console.log(result)); + +app.listen(port,() => console.log(`server is running on port ${port}`)); diff --git a/src/models/taskModel.js b/src/models/taskModel.js index 38bbc5d..79bf7d9 100644 --- a/src/models/taskModel.js +++ b/src/models/taskModel.js @@ -3,7 +3,8 @@ import mongoose from "mongoose"; const taskModel = mongoose.Schema({ id : { type : String, - required : true + required : true, + unique : true }, title : { diff --git a/src/routes/taskRoute.js b/src/routes/taskRoute.js new file mode 100644 index 0000000..2a6f0fd --- /dev/null +++ b/src/routes/taskRoute.js @@ -0,0 +1,8 @@ +import { Router } from "express"; +import { getAllTask,createTask,deleteTask,updateTask } from "../controllers/taskController.js"; + +export const taskRouter = Router(); +taskRouter.get("/getAll" , getAllTask); +taskRouter.post("/create" , createTask); +taskRouter.delete("/delete/:id" , deleteTask); +taskRouter.put("/update/:id" , updateTask); diff --git a/src/services/taskService.js b/src/services/taskService.js index db90dee..b32f7cb 100644 --- a/src/services/taskService.js +++ b/src/services/taskService.js @@ -1,19 +1,26 @@ -import { TaskDto } from "../dto/taskDto"; -import taskModel from "../models/taskModel" +import taskModel from "../models/taskModel.js"; -export const getAllTasks = async () =>{ - return await taskModel.find(); -} +export const getAllTasks = async () => { + return await taskModel.find(); +}; -export const createTask = async (TaskDto) => { - return await taskModel.create(TaskDto) - -} +export const createTask = async (task) => { + // Generate a unique ID for the new task + const lastTask = await taskModel.findOne().sort({ id: -1 }); + let newId = "T001"; + if (lastTask && lastTask.id) { + const lastNum = parseInt(lastTask.id.replace("T", "")); + const nextNum = lastNum + 1; + newId = "T" + nextNum.toString().padStart(3, "0"); + } + const newTask = { ...task, id: newId }; + return await taskModel.create(newTask); +}; export const deleteTaskById = async (id) => { - return await taskModel.findByIdAndDelete(id); -} + return await taskModel.findByIdAndDelete(id); +}; -export const updateTaskById = async (id, TaskDto) => { - return await taskModel.findByIdAndUpdate(id, TaskDto, {new : true}); -} +export const updateTaskById = async (id, task) => { + return await taskModel.findByIdAndUpdate(id, task, { new: true }); +}; From 3ecd70ff286a1ef8c700ae288141293728269ce9 Mon Sep 17 00:00:00 2001 From: Acer Date: Tue, 4 Nov 2025 10:27:37 +0530 Subject: [PATCH 05/11] define customer error class for handle 404 errors --- src/middleware/taskValidator.js | 0 src/utils/customError.js | 11 +++++++++++ 2 files changed, 11 insertions(+) create mode 100644 src/middleware/taskValidator.js create mode 100644 src/utils/customError.js diff --git a/src/middleware/taskValidator.js b/src/middleware/taskValidator.js new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/customError.js b/src/utils/customError.js new file mode 100644 index 0000000..6252677 --- /dev/null +++ b/src/utils/customError.js @@ -0,0 +1,11 @@ +// define custom error class to handle errors more effectively +class CustomError extends Error { + constructor(message, statusCode, data = null) { + super(message); + this.statusCode = statusCode; + this.data = data; // Useful for passing validation errors or other structured data + Error.captureStackTrace(this, this.constructor); + } +} + +module.exports = { CustomError }; \ No newline at end of file From a7978fa8522b52170d6629de549b200524b66266 Mon Sep 17 00:00:00 2001 From: Acer Date: Tue, 4 Nov 2025 12:19:33 +0530 Subject: [PATCH 06/11] implement centralized error handling middleware with detailed logging and custom error support --- src/app.js | 4 +++ src/controllers/taskController.js | 36 ++++++++++++----------- src/middleware/errorhandler.js | 47 +++++++++++++++++++++++++++++++ src/services/taskService.js | 4 +-- src/utils/customError.js | 11 +++----- 5 files changed, 77 insertions(+), 25 deletions(-) create mode 100644 src/middleware/errorhandler.js diff --git a/src/app.js b/src/app.js index d840a07..401d0de 100644 --- a/src/app.js +++ b/src/app.js @@ -1,5 +1,6 @@ import express from "express"; import { taskRouter } from "./routes/taskRoute.js"; +import { ErrorHandler } from "./middleware/errorhandler.js"; const app = express(); @@ -9,4 +10,7 @@ app.use(express.json()); // routes app.use("/api/v1/tasks" , taskRouter) +// Error handling middleware +app.use(ErrorHandler); + export default app; \ No newline at end of file diff --git a/src/controllers/taskController.js b/src/controllers/taskController.js index ecba8ef..ce297d8 100644 --- a/src/controllers/taskController.js +++ b/src/controllers/taskController.js @@ -1,55 +1,59 @@ import { request , response } from "express" import * as taskService from "../services/taskService.js" +import { CustomError } from "../utils/customError.js"; -export const getAllTask = async (request , response) =>{ +console.log("CustomError imported:", CustomError); + + +export const getAllTask = async (request , response , next) =>{ try { const tasks = await taskService.getAllTasks(); response.status(200).json({message : "Tasks fetched successfully", data : tasks}); }catch (error) { - response.status(500).json({message : "Error while fetching tasks", error : error.message}); + next(error) } } -export const createTask = async (request , response) => { +export const createTask = async (request , response , next) => { try { const newTask = request.body; const createdTask = await taskService.createTask(newTask) response.status(201).json({message : "Task created successfully" , data : createdTask}); }catch (error) { - response.status(500).json({message : "Error while creating task", error : error.message}); + next(error); } } -export const deleteTask = async (request , response) => { +export const deleteTask = async (request , response , next) => { try { const taskId = request.params.id; - const deleteTask = await taskService.deleteTaskById(taskId); + const deletedTask = await taskService.deleteTaskById(taskId); - if(!deleteTask) { - return response.status(404).json({message : "Task not found"}); + if(!deletedTask) { + return next(new CustomError(`Task not found with ID: ${taskId}`, 404)); } - return response.status(200).json({message : "Task deleted successfully", data : deleteTask}); + return response.status(200).json({message : "Task deleted successfully", data : deletedTask}); }catch (error) { - return response.status(500).json({message : "Error while deleting task", error : error.message}); + next(error); } } -export const updateTask = async (request , response) => { +export const updateTask = async (request , response , next) => { try { const taskId = request.params.id; const taskData = request.body; - const updateTask = await taskService.updateTaskById(taskId,taskData); - if (!updateTask) { - return response.status(400).json({message : "Task not found"}); + const updatedTask = await taskService.updateTaskById(taskId,taskData); + if (!updatedTask) { + return next(new CustomError(`Task not found with ID: ${taskId}`, 404)); } - return response.status(200).json({message : "Task updated successfully", data : updateTask}) + return response.status(200).json({message : "Task updated successfully", data : updatedTask}) }catch (error) { - return response.status(500).json({message : "Error while updating task", error : error.message}) + next(error); } } \ No newline at end of file diff --git a/src/middleware/errorhandler.js b/src/middleware/errorhandler.js new file mode 100644 index 0000000..c0713e4 --- /dev/null +++ b/src/middleware/errorhandler.js @@ -0,0 +1,47 @@ +export const ErrorHandler = (err, req, res, next) => { + // Default to a 500 Internal Server Error + let statusCode = err.statusCode || 500; + let message = err.message || 'Internal Server Error'; + let data = err.data || null; + + // Log error for debugging + console.log("Error caught in handler:"); + console.log("- Type:", err.constructor.name); + console.log("- Message:", err.message); + console.log("- StatusCode:", err.statusCode); + + // 1. Handle CustomError (HIGHEST PRIORITY) + if (err.name === 'CustomError' || err.statusCode) { + statusCode = err.statusCode; + message = err.message; + } + // 2. Handle Mongoose CastError (e.g., invalid ID format) + else if (err.name === 'CastError' && err.kind === 'ObjectId') { + statusCode = 404; + message = `Resource not found with ID of ${err.value}`; + } + // 3. Handle Mongoose Duplicate Key Error + else if (err.code === 11000) { + statusCode = 400; + message = `Duplicate field value entered: ${Object.keys(err.keyValue)}`; + } + // 4. Handle Mongoose Validation Error + else if (err.name === 'ValidationError') { + statusCode = 400; + message = 'Mongoose validation failed'; + data = Object.values(err.errors).map(val => ({ [val.path]: val.message })); + } + + // Send the standardized error response + res.status(statusCode).json({ + success: false, + error: { + message: message, + code: statusCode, + // Only include 'data' if it exists + ...(data && { details: data }), + // Optional: Include stack trace ONLY in development + ...(process.env.NODE_ENV === 'development' && { stack: err.stack }), + }, + }); +}; \ No newline at end of file diff --git a/src/services/taskService.js b/src/services/taskService.js index b32f7cb..ec29f1f 100644 --- a/src/services/taskService.js +++ b/src/services/taskService.js @@ -18,9 +18,9 @@ export const createTask = async (task) => { }; export const deleteTaskById = async (id) => { - return await taskModel.findByIdAndDelete(id); + return await taskModel.findOneAndDelete({id : id}); }; export const updateTaskById = async (id, task) => { - return await taskModel.findByIdAndUpdate(id, task, { new: true }); + return await taskModel.findOneAndUpdate({id:id},task,{new : true}) }; diff --git a/src/utils/customError.js b/src/utils/customError.js index 6252677..7f14d56 100644 --- a/src/utils/customError.js +++ b/src/utils/customError.js @@ -1,11 +1,8 @@ -// define custom error class to handle errors more effectively -class CustomError extends Error { - constructor(message, statusCode, data = null) { +export class CustomError extends Error { + constructor(message, statusCode) { super(message); this.statusCode = statusCode; - this.data = data; // Useful for passing validation errors or other structured data + this.name = this.constructor.name; Error.captureStackTrace(this, this.constructor); } -} - -module.exports = { CustomError }; \ No newline at end of file +} \ No newline at end of file From 571b2514252375a43ed28252e1c41ad769fec278 Mon Sep 17 00:00:00 2001 From: Acer Date: Tue, 4 Nov 2025 14:05:04 +0530 Subject: [PATCH 07/11] implement getTaskById service and controller with proper error handling --- src/app.js | 1 + src/controllers/taskController.js | 14 ++++ src/middleware/taskValidator.js | 106 ++++++++++++++++++++++++++++++ src/models/taskModel.js | 10 ++- src/routes/taskRoute.js | 10 +-- src/services/taskService.js | 4 ++ 6 files changed, 139 insertions(+), 6 deletions(-) diff --git a/src/app.js b/src/app.js index 401d0de..6309676 100644 --- a/src/app.js +++ b/src/app.js @@ -13,4 +13,5 @@ app.use("/api/v1/tasks" , taskRouter) // Error handling middleware app.use(ErrorHandler); + export default app; \ No newline at end of file diff --git a/src/controllers/taskController.js b/src/controllers/taskController.js index ce297d8..a00dcb2 100644 --- a/src/controllers/taskController.js +++ b/src/controllers/taskController.js @@ -56,4 +56,18 @@ export const updateTask = async (request , response , next) => { }catch (error) { next(error); } +} + +export const getTaskById = async (request , response , next) => { + try { + const taskId = request.params.id; + const task = await taskService.getTaskById(taskId); + + if(!task){ + return next (new CustomError(`Task not found with ID: ${taskId}` , 404)); + } + return response.status(200).json({message : "Task fetched successfully" , data : task}); + }catch (error) { + next(error); + } } \ No newline at end of file diff --git a/src/middleware/taskValidator.js b/src/middleware/taskValidator.js index e69de29..b391842 100644 --- a/src/middleware/taskValidator.js +++ b/src/middleware/taskValidator.js @@ -0,0 +1,106 @@ +import { CustomError } from "../utils/customError.js"; + +// Validate Create Task +export const validateCreateTask = (req, res, next) => { + const { title, description, status } = req.body; + const errors = []; + + // Check required fields + if (!title || title.trim() === "") { + errors.push("Title is required"); + } + + // Validate title length + if (title && title.trim().length < 3) { + errors.push("Title must be at least 3 characters long"); + } + + if (title && title.length > 100) { + errors.push("Title must not exceed 100 characters"); + } + + // Validate description (optional but if provided, check length) + if (description && description.length > 500) { + errors.push("Description must not exceed 500 characters"); + } + + // Validate status (if provided) - matches model enum + const validStatuses = ["pending", "in-progress", "completed"]; + if (status && !validStatuses.includes(status)) { + errors.push(`Status must be one of: ${validStatuses.join(", ")}`); + } + + // If there are validation errors, throw CustomError + if (errors.length > 0) { + return next(new CustomError(errors.join("; "), 400)); + } + + next(); +}; + +// Validate Update Task +export const validateUpdateTask = (req, res, next) => { + const { title, description, status } = req.body; + const errors = []; + + // Check if at least one field is provided for update + if (title === undefined && description === undefined && status === undefined) { + return next(new CustomError("At least one field (title, description, status) is required for update", 400)); + } + + // Validate title if provided + if (title !== undefined) { + if (typeof title !== "string" || title.trim() === "") { + errors.push("Title cannot be empty"); + } else { + if (title.trim().length < 3) { + errors.push("Title must be at least 3 characters long"); + } + if (title.length > 100) { + errors.push("Title must not exceed 100 characters"); + } + } + } + + // Validate description if provided + if (description !== undefined) { + if (typeof description !== "string") { + errors.push("Description must be a string"); + } else if (description.length > 500) { + errors.push("Description must not exceed 500 characters"); + } + } + + // Validate status if provided - matches model enum + const validStatuses = ["pending", "in-progress", "completed"]; + if (status !== undefined && !validStatuses.includes(status)) { + errors.push(`Status must be one of: ${validStatuses.join(", ")}`); + } + + // If there are validation errors, throw CustomError + if (errors.length > 0) { + return next(new CustomError(errors.join("; "), 400)); + } + + next(); +}; + +// Validate Task ID parameter +export const validateTaskId = (req, res, next) => { + const { id } = req.params; + + if (!id) { + return next(new CustomError("Task ID is required", 400)); + } + + // Check if it's a valid custom ID format (T001, T002, etc.) or MongoDB ObjectId + const customIdPattern = /^T\d{3,}$/; + const isValidCustomId = customIdPattern.test(id); + const isValidObjectId = /^[a-f\d]{24}$/i.test(id); + + if (!isValidCustomId && !isValidObjectId) { + return next(new CustomError("Invalid Task ID format. Expected: T001 or valid MongoDB ObjectId", 400)); + } + + next(); +}; \ No newline at end of file diff --git a/src/models/taskModel.js b/src/models/taskModel.js index 79bf7d9..654ec1b 100644 --- a/src/models/taskModel.js +++ b/src/models/taskModel.js @@ -9,11 +9,17 @@ const taskModel = mongoose.Schema({ title : { type : String, - required : true + trim : true, + required : [true , "title is required"], + minlength : [3 , "title must be at least 3 characters long"], + maxlength : [100 , "title must be at most 100 characters long"] }, description : { type : String, + trim : true, + maxlength : [500 , "description must be at most 500 characters long"], + default : "" }, @@ -21,7 +27,7 @@ const taskModel = mongoose.Schema({ type : String, enum : ["pending", "in-progress", "completed"], default : "pending", - required : true + required : [true , "status is required"] }}, { diff --git a/src/routes/taskRoute.js b/src/routes/taskRoute.js index 2a6f0fd..e400e96 100644 --- a/src/routes/taskRoute.js +++ b/src/routes/taskRoute.js @@ -1,8 +1,10 @@ import { Router } from "express"; -import { getAllTask,createTask,deleteTask,updateTask } from "../controllers/taskController.js"; +import { getAllTask,createTask,deleteTask,updateTask, getTaskById } from "../controllers/taskController.js"; +import { validateCreateTask, validateTaskId, validateUpdateTask } from "../middleware/taskValidator.js"; export const taskRouter = Router(); taskRouter.get("/getAll" , getAllTask); -taskRouter.post("/create" , createTask); -taskRouter.delete("/delete/:id" , deleteTask); -taskRouter.put("/update/:id" , updateTask); +taskRouter.post("/create" , validateCreateTask,createTask); +taskRouter.delete("/delete/:id" , validateTaskId , deleteTask); +taskRouter.put("/update/:id" , validateTaskId , validateUpdateTask , updateTask); +taskRouter.get("/get/:id" ,validateTaskId , getTaskById); \ No newline at end of file diff --git a/src/services/taskService.js b/src/services/taskService.js index ec29f1f..3ea456b 100644 --- a/src/services/taskService.js +++ b/src/services/taskService.js @@ -24,3 +24,7 @@ export const deleteTaskById = async (id) => { export const updateTaskById = async (id, task) => { return await taskModel.findOneAndUpdate({id:id},task,{new : true}) }; + +export const getTaskById = async (id) => { + return await taskModel.findOne({id : id}) +} \ No newline at end of file From 1418adacbab11b4e9ef8e2c04dea735735eb747a Mon Sep 17 00:00:00 2001 From: Acer Date: Tue, 4 Nov 2025 15:48:06 +0530 Subject: [PATCH 08/11] create README file and added Postman collection for API testing --- .vscode/settings.json | 3 + README.md | 209 ++++++++++++++++++ .../Task Management.postman_collection.json | 119 ++++++++++ src/services/taskService.js | 2 +- 4 files changed, 332 insertions(+), 1 deletion(-) create mode 100644 .vscode/settings.json create mode 100644 README.md create mode 100644 postman/Task Management.postman_collection.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..77b93f8 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "cSpell.words": ["manushamadubhashini"] +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..7cc1671 --- /dev/null +++ b/README.md @@ -0,0 +1,209 @@ +# Task Management API + +A simple RESTful API for managing tasks built with Node.js, Express.js, and MongoDB. + +## Features + +- Create, read, update and delete tasks +- Input validation with express-validator +- MongoDB database integration +- Proper error handling and status codes +- Postman collection included + +## Tech Stack + +- **Backend**: Node.js, Express.js +- **Database**: MongoDB, Mongoose +- **Validation**: Express Validator +- **Environment**: dotenv + +## Prerequisites + +- Node.js (v14+) +- MongoDB +- Postman + +## Installation + +1. **Clone the repository** +```bash +git clone +cd task-api +``` + +2. **Install dependencies** +```bash +npm install express mongoose express-validator dotenv +``` + +3. **Create `.env` file** +```env +PORT=3000 +MONGODB_URI=mongodb://localhost:27017/taskmanagement +NODE_ENV=development +``` + + +5. **Run the application** +```bash +npm run dev +``` + +Server will run at `http://localhost:3000` + +## Project Structure + +``` +task-api/ +├── postman/ +│ └── Task Management.postman_collection.json +├── src/ +│ ├── config/ +│ │ └── dbConnection.js +│ ├── controllers/ +│ │ └── taskController.js +│ ├── dto/ +│ │ └── taskDTO.js +│ ├── middleware/ +│ │ ├── errorHandler.js +│ │ └── taskValidator.js +│ ├── models/ +│ │ └── taskModel.js +│ ├── routes/ +│ │ └── taskRoute.js +│ ├── services/ +│ │ └── taskService.js +│ ├── utils/ +│ │ └── customError.js +│ ├── app.js +│ └── index.js +├── .env +├── .gitignore +├── package.json +└── README.md +``` + +## API Endpoints + +Base URL: `http://localhost:3000/api/v1/tasks` + +### 1. Create Task +**POST** `/create` + +Request: +```json +{ + "title" : "Task 5", + "description" : "This is task 5", + "status" : "pending" +} +``` + +Response (201): +```json +{ + "message": "Task created successfully", + "data": { + "id": "T005", + "title": "Task 5", + "description": "This is task 5", + "status": "pending", + "_id": "6909c995b7a10bcb07015c3f", + "createdAt": "2025-11-04T09:38:29.632Z", + "updatedAt": "2025-11-04T09:38:29.632Z", + "__v": 0 + } +} +``` + +### 2. Get All Tasks +**GET** `/getAll` + +Response (200): +```json +{ + "message": "Tasks fetched successfully", + "data": [ + { + "_id": "6909c995b7a10bcb07015c3f", + "id": "T005", + "title": "Task 5", + "description": "This is task 5", + "status": "pending", + "createdAt": "2025-11-04T09:38:29.632Z", + "updatedAt": "2025-11-04T09:38:29.632Z", + "__v": 0 + } + ] +} +``` + +### 3. Get Task by ID +**GET** `/get/:id` + +Response (200): +```json +{ + "message": "Task fetched successfully", + "data": { + "_id": "6909c995b7a10bcb07015c3f", + "id": "T005", + "title": "Task 5", + "description": "This is task 5", + "status": "pending", + "createdAt": "2025-11-04T09:38:29.632Z", + "updatedAt": "2025-11-04T09:38:29.632Z", + "__v": 0 + } +} +``` + +### 4. Delete Task +**DELETE** `/delete/:id` + +Response (200): +```json +{ + "message": "Task deleted successfully", + "data": { + "_id": "69098178f4cdd31945052ef5", + "id": "T002", + "title": "task3", + "description": "this is a task 3", + "status": "pending", + "createdAt": "2025-11-04T04:30:48.107Z", + "updatedAt": "2025-11-04T06:45:04.058Z", + "__v": 0 + } +} +```{ + "message": "Task fetched successfully", + "data": { + "_id": "6909c995b7a10bcb07015c3f", + "id": "T005", + "title": "Task 5", + "description": "This is task 5", + "status": "pending", + "createdAt": "2025-11-04T09:38:29.632Z", + "updatedAt": "2025-11-04T09:38:29.632Z", + "__v": 0 + } +} + +## Testing with Postman + +1. Open Postman +2. Click **Import** +3. Select `postman/Task Management.postman_collection.json` +4. Run the requests + +## Troubleshooting + +**MongoDB Connection Error** +- Ensure MongoDB is running: `mongod` +- Check `MONGODB_URI` in `.env` + +`` + +## Author +Manusha Madubhashini diff --git a/postman/Task Management.postman_collection.json b/postman/Task Management.postman_collection.json new file mode 100644 index 0000000..82089e9 --- /dev/null +++ b/postman/Task Management.postman_collection.json @@ -0,0 +1,119 @@ +{ + "info": { + "_postman_id": "4560299f-17ae-44f4-9a79-3f0975ee760e", + "name": "Task Management", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "40188628" + }, + "item": [ + { + "name": "New Request", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"title\" : \"task3\",\r\n \"description\" : \"this is task 3\",\r\n \"status\" : \"pending\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:3000/api/v1/tasks/create", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "v1", + "tasks", + "create" + ] + } + }, + "response": [] + }, + { + "name": "New Request", + "request": { + "auth": { + "type": "bearer" + }, + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "http://localhost:3000/api/v1/tasks/delete/T001", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "v1", + "tasks", + "delete", + "T001" + ] + } + }, + "response": [] + }, + { + "name": "New Request", + "request": { + "method": "GET", + "header": [] + }, + "response": [] + }, + { + "name": "New Request", + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"title\" : \"task3\",\r\n \"description\" : \"this is a task 3\",\r\n \"status\" : \"pending\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:3000/api/v1/tasks/update/T002", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "v1", + "tasks", + "update", + "T002" + ] + } + }, + "response": [] + }, + { + "name": "New Request", + "request": { + "method": "GET", + "header": [] + }, + "response": [] + } + ] +} \ No newline at end of file diff --git a/src/services/taskService.js b/src/services/taskService.js index 3ea456b..933b21e 100644 --- a/src/services/taskService.js +++ b/src/services/taskService.js @@ -26,5 +26,5 @@ export const updateTaskById = async (id, task) => { }; export const getTaskById = async (id) => { - return await taskModel.findOne({id : id}) + return await taskModel.findOne({id : id}); } \ No newline at end of file From 905f812cc379e7552cf54574784f7a2968026828 Mon Sep 17 00:00:00 2001 From: manushamadubhashini <157113946+manushamadubhashini@users.noreply.github.com> Date: Tue, 4 Nov 2025 15:56:42 +0530 Subject: [PATCH 09/11] Remove example JSON response Removed example JSON response from README. --- README.md | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 7cc1671..40fb93a 100644 --- a/README.md +++ b/README.md @@ -176,19 +176,7 @@ Response (200): "__v": 0 } } -```{ - "message": "Task fetched successfully", - "data": { - "_id": "6909c995b7a10bcb07015c3f", - "id": "T005", - "title": "Task 5", - "description": "This is task 5", - "status": "pending", - "createdAt": "2025-11-04T09:38:29.632Z", - "updatedAt": "2025-11-04T09:38:29.632Z", - "__v": 0 - } -} +``` ## Testing with Postman @@ -203,7 +191,6 @@ Response (200): - Ensure MongoDB is running: `mongod` - Check `MONGODB_URI` in `.env` -`` - ## Author + Manusha Madubhashini From cced064d9bd291e86a14cbf358f9cf6dda7b9cba Mon Sep 17 00:00:00 2001 From: manushamadubhashini <157113946+manushamadubhashini@users.noreply.github.com> Date: Tue, 4 Nov 2025 15:57:45 +0530 Subject: [PATCH 10/11] Update project title in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 40fb93a..6ded878 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Task Management API +# Build a Simple Task Management API A simple RESTful API for managing tasks built with Node.js, Express.js, and MongoDB. From 242158849a68b40842f685b6c7bb0ad2ace19d28 Mon Sep 17 00:00:00 2001 From: manushamadubhashini <157113946+manushamadubhashini@users.noreply.github.com> Date: Tue, 4 Nov 2025 15:59:55 +0530 Subject: [PATCH 11/11] Update clone command in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6ded878..9d466f5 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ A simple RESTful API for managing tasks built with Node.js, Express.js, and Mong 1. **Clone the repository** ```bash -git clone +git clone git@github.com:manushamadubhashini/task-api.git cd task-api ```