Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions backend/controllers/todoController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import express from "express";
import todoModel from "../models/todo.js";


//List Todo
export const listTodos = async (req, res) => {
try {
const todos = await todoModel.getAll();
res.json({
success: true,
data: todos,
count: todos.length
});

} catch (error) {
res.status(500).json({
success: false,
message: "Error fetching todos",
error: error.message
})
}
};

//list by id
export const listTodoById = async (req, res) => {
try {
const todo = await todoModel.getById(req.params.id);

//todo does not exsist
if(!todo){
return res.status(404).json({
success: false,
message: "Todo not found"
});
}
res.json({
success: true,
data: todo
});


} catch (error) {
res.status(500).json({
success: false,
message: "Error fetching todo",
error: error.message })
}
};

//create
export const createTodo = async (req, res) => {
try {
const {title} = req.body;

//null validation
if(!title || title.trim() === ' ')
return res.status(400).json({
success: false,
message: "Title is required"
});

const newTodo = await todoModel.create({
title: title.trim()
});


res.status(201).json({
success: true,
message: 'Todo created successfully',
data: newTodo
});

} catch (error) {
res.status(500).json({
success: false,
message: "Error creating todo",
error: error.message
})
}
};

//update
export const updateTodo = async (req, res) => {
try {
const update = {}
if(req.body.title) update.title = req.body.title.trim();
if(req.body.completed !== undefined) update.completed = req.body.completed;

const updatedTodo = await todoModel.update(req.params.id, update);

if (!updatedTodo) {
return res.status(400).json({
success: false,
message: "Todo not found"

})
}
res.json({
success: true,
message: "Todo updated successfully",
data: updatedTodo
});

} catch (error) {
res.status(500).json({
success: false,
message: "Error updating todo",
error: error.message
})

}
};

//delete
export const deleteTodo = async (req, res) => {
try {
const deleted = await todoModel.delete(req.params.id);

if(!deleted) {
return res.status(404).json({
success: false,
message: "Todo not found"
});
}
res.json({
success: true,
message: "Todo deleted successfully"
});


} catch (error) {
res.status(500).json({
success: false,
message: "Error deleting todo",
error: error.message
})
}
};
27 changes: 26 additions & 1 deletion backend/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,38 @@
const express = require("express");
import express from "express";
import dotenv from "dotenv";

import todoRoutes from "./routes/todoRoutes.js";


dotenv.config();


const app = express();
const PORT = process.env.PORT || 4000;

//middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Basic route
app.get("/", (req, res) => {
res.send("Hello from Express!");
});

//todo API route
app.use("/api/todos", todoRoutes);

app.use((err, req, res, next) => {
console.error(err.stacks);
res.status(500).json({
success: false,
message: "Something went wrong",
error: err.message
});
})

// Start server
app.listen(PORT, () => {
console.log(`Backend is running on http://localhost:${PORT}`);
console.log(`Todo API available at http://localhost:${PORT}/api/todos`)
});
116 changes: 116 additions & 0 deletions backend/models/todo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { create } from 'domain';
import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

class Todo {
constructor() {
this.filePath = path.join(__dirname, 'todos.json');
this.init();
}

async init() {
try {
await fs.access(this.filePath);
} catch (error) {
await this.saveToFile([]);
}


}

async readFromFile () {
try {
const data = await fs.readFile(this.filePath, 'utf-8');
return JSON.parse(data);
} catch (error) {
console.error('Error reading file:', error);
return [];

}
}


async saveToFile(todos) {
try {
await fs.writeFile(this.filePath, JSON.stringify(todos, null, 2));
return true;
} catch (error) {
console.error('Error saving file:', error);
return false;
}
}

async getAll() {
return await this.readFromFile();
}
// Get next available ID
async getNextId() {
const todos = await this.readFromFile();
if (todos.length === 0) return 1;
const maxId = Math.max(...todos.map(todo => todo.id));
return maxId + 1;
}

async getById(id) {
const todos =await this.readFromFile();
return todos.find(todo => todo.id === parseInt (id));
}

async create(todoData) {
const todos = await this.readFromFile();
const nextId = await this.getNextId();
const newTodo = {
id: nextId,
title: todoData.title,
completed: false,
createdAt: new Date().toISOString()

};
todos.push(newTodo);
await this.saveToFile(todos);
return newTodo;
}

async update(id, updatedData) {
const todos = await this.readFromFile();
const index = todos.findIndex(todo => todo.id === parseInt(id));
if (index === -1) {
return null;
}
todos[index] = {
...todos[index],
...updatedData,
updatedAt: new Date().toISOString(),
};
await this.saveToFile(todos);
return todos[index];


}

async delete(id) {
const todos = await this.readFromFile();
const filteredTodos = todos.filter(todo => todo.id !== parseInt(id));
await this.saveToFile(filteredTodos);
return true;
}

async getStats() {
const todos = await this.readFromFile();
const completed = todos.filter(todo => todo.completed).length;
return {
total: todos.length,
completed,
pending: todos.length - completed,

};
}

}

const todoModel = new Todo();
export default todoModel;
15 changes: 15 additions & 0 deletions backend/models/todos.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[
{
"id": 2,
"title": "Debug your code",
"completed": true,
"createdAt": "2026-04-10T14:04:53.215Z",
"updatedAt": "2026-04-10T14:49:34.898Z"
},
{
"id": 3,
"title": "Remind your gf to drink water",
"completed": false,
"createdAt": "2026-04-10T14:05:23.463Z"
}
]
5 changes: 5 additions & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
{
"name": "backend",
"version": "1.0.0",
"description": "",
"license": "ISC",
"author": "",
"type": "module",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
Expand Down
25 changes: 25 additions & 0 deletions backend/routes/todoRoutes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import express from "express";
import { createTodo, listTodoById, listTodos, updateTodo, deleteTodo} from "../controllers/todoController.js";



const router = express.Router();

//get all
router.get("/", listTodos);

//get by id
router.get("/:id", listTodoById);

//create"
router.post("/", createTodo);

//update
router.put("/:id", updateTodo);

//delete
router.delete("/:id", deleteTodo);



export default router;
Loading