Todo List Application using MEAN Stack
The todo list is very important tool to manage our tasks in this hectic schedule. This article explores how to build to-do list application using the MEAN stack—MongoDB, Express.js, Angular, and Node.js. We’ll walk you through the process of setting up backends with Node.js and Express.js, integrating MongoDB for data storage efficiency, and using Angular for connector interactions. By the end, you’ll have not only a working todo list app but a deeper understanding of MEAN stack synergy. Let’s start this journey of MEAN stack development together.
Output Preview: Let us have a look at how the final output will look like
Prerequisites:
Approach to create Todo List Application using MEAN Stack :
- To perform some basic operations like add, delete, edit and read tasks, we will make another folder controllers, inside that we will make one file employeeRouters.js, within this we will use express router. With the help of this we can tackle all the operations.
- Now, we will create mongodb database connection function, here we will use mongoose library. With the help of mongoose.connect method we can connect.
- After that, we will create models such as task contains title, description, createdAt, deadline and completed or not.
- App Component renders both completed and not completed tasks. Here, we can see the delete and edit functionality.
- TodoAdd Component only add tasks. Here, we will use EventEmitters, whenever user will click on Add button then it will emit that task to the App Component, so that, it can be easily added to the database and easily rendered.
- To add, delete or edit tasks seamlessly, we will use angular services, which will use httpClient, with the help of this we will send request to backend and accordingly the response will come.
Steps to create Application:
Step 1: Install node on your system depending on your Operating System:
Step 2: Make NodeJs directory and go to NodeJs
mkdir NodeJs
cd NodeJs
Step 3: Run npm init to initialize the Node application.
npm init -y
Step 4: Install the required dependencies:
npm i express mongoose cors body-parser
Folder Structure(Backend):
Backend
Dependencies:
"dependencies": {
"body-parser": "^1.20.2",
"cors": "^2.8.5",
"express": "^4.18.3",
"mongoose": "^8.2.0"
}
Code Example: Create the required files as shown in folder structure and add the following codes.
// index.js
import express from 'express';
import bodyParser from 'body-parser';
import employeeRouters from './controllers/employeeRouters.js';
import cors from 'cors';
const app = express();
const port = 3001;
// CORS configuration
app.use(function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header(
'Access-Control-Allow-Methods',
'GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE'
);
res.header(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept, x-access-token, x-refresh-token, _id'
);
res.header(
'Access-Control-Expose-Headers',
'x-access-token, x-refresh-token'
);
next();
});
app.use(bodyParser.json());
// Routes
app.use('/tasks', employeeRouters);
app.on('error', (err) => {
console.error(`Error during startup: ${err.message}`);
});
app.listen(port, () => {
console.log(`App has started on port ${port}`);
});
// controllers/employeeRouters.js
import express from "express";
import { TaskModel } from "../models.js";
import { connect } from "../dbConfig.js";
connect();
const router = express.Router();
router.get("/", async (req, res) => {
try {
const tasks = await TaskModel.find();
res.send(tasks);
} catch (err) {
console.log("Error while getting tasks");
res.status(400).send("Error while fetching tasks");
}
});
router.post("/", async (req, res) => {
try {
const name = req.body.name;
const createdAt = Date.now();
const desc = req.body.desc;
const deadline = req.body.deadline;
const completed = req.body.completed;
const newTask = new TaskModel({
name: name,
createdAt: createdAt,
desc: desc,
deadline: deadline,
completed: completed
})
console.log(newTask);
const result = await newTask.save();
console.log("Task Saved");
res.send(result);
} catch (err) {
console.log(err);
res.status(400).send("Task not saved");
}
})
router.put("/:id", async (req, res) => {
try {
const id = req.params.id;
const name = req.body.name;
const desc = req.body.desc;
const deadline = req.body.deadline;
const completed = req.body.completed;
console.log(name + " " + id);
const createdAt = Date.now();
const emp = {
name: name,
createdAt: createdAt,
desc: desc,
deadline: deadline,
completed: completed
}
const updated = await TaskModel.findByIdAndUpdate(id,
{ $set: emp }, { new: true });
if (!updated) {
console.log("Task not found");
return res.status(404).json({ error: "Task not found" });
}
return res.status(200)
.json({ message: "Task Updated Successfully" });
} catch (err) {
console.log(err);
return res.status(500)
.json({ error: "Internal Server Error" });
}
})
router.delete("/:id", async (req, res) => {
try {
const id = req.params.id;
const deletedTask = await TaskModel.findByIdAndDelete(id);
if (!deletedTask) {
console.log("Task not found");
return res.status(404)
.json({ error: "Task not found" });
}
console.log("Task deleted Successfully");
return res.status(200)
.json({ message: "Task deleted Successfully" });
} catch (err) {
console.log(err);
return res.status(500)
.json({ error: "Internal Server Error" });
}
});
export default router;
// dbConfig.js
import mongoose from 'mongoose';
export async function connect() {
try {
const res = await mongoose.connect("Your MongoDB string");
console.log("DB Connected >>>");
} catch (err) {
console.log(err);
}
}
connect();
// models.js
import mongoose from "mongoose";
const TaskSchema = new mongoose.Schema({
name: { type: String, default: 'Task1' },
createdAt: { type: Date, default: Date.now },
desc: { type: String, default: 'Sample Desc1' },
deadline: { type: String, default: new Date().toDateString() },
completed: { type: Boolean, default: false }
});
export const TaskModel = mongoose.model("Task", TaskSchema);
Start the server using the following command.
node index.js
Step 5: Install Angular CLI, powerful tool to deal with angular project
npm install -g @angular/cli
Step 6: Create Project and change directory
ng new to-do-app
cd to-do-app
Step 7: Steup Tailwind
1. Install talwind CSS
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init
2. Configure your tailwind.config.js file.
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./src/**/*.{html,ts}",
],
theme: {
extend: {},
},
plugins: [],
}
3. Add the Tailwind directives to your CSS
Add the @tailwind directives in your ./src/styles.css file.
@tailwind base;
@tailwind components;
@tailwind utilities;
Step 8: Run the below command to make Angular service webrequest
ng g s webrequest
Step 9: Run the below command to create TodoAdd component
ng g c MyComponents/todo-add
Important Configurations in app.config.ts file:
// src/app/app.config.ts:
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { provideClientHydration } from '@angular/platform-browser';
import { provideHttpClient, withFetch } from '@angular/common/http';
export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes), provideClientHydration(),provideHttpClient(withFetch())]
};
Folder Structure(Frontend):
Dependencies:
"dependencies": {
"@angular/animations": "^17.2.0",
"@angular/common": "^17.2.0",
"@angular/compiler": "^17.2.0",
"@angular/core": "^17.2.0",
"@angular/forms": "^17.2.0",
"@angular/platform-browser": "^17.2.0",
"@angular/platform-browser-dynamic": "^17.2.0",
"@angular/platform-server": "^17.2.0",
"@angular/router": "^17.2.0",
"@angular/ssr": "^17.2.2",
"express": "^4.18.2",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.3"
},
"devDependencies": {
"@angular-devkit/build-angular": "^17.2.2",
"@angular/cli": "^17.2.2",
"@angular/compiler-cli": "^17.2.0",
"@types/express": "^4.17.17",
"@types/jasmine": "~5.1.0",
"@types/node": "^18.18.0",
"autoprefixer": "^10.4.18",
"jasmine-core": "~5.1.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"postcss": "^8.4.35",
"tailwindcss": "^3.4.1",
"typescript": "~5.3.2"
}
Code Example: Create the required files and add the following codes.
<!-- src/app/app.component.html -->
<div>
<div class="text-3xl font-semibold max-w-sm mx-auto
bg-blue-50 text-center p-3 rounded-md mt-2
hover:bg-blue-500 hover:text-white transition
duration-500 ease-in-out my-5">
Todo List
</div>
<app-todo-add (todoAdd)="addTask($event)"></app-todo-add>
<div class="max-w-lg md:max-w-xl lg:max-w-3xl
flex flex-col md:flex-row mx-auto">
<div class="flex flex-col my-3 p-2 m-2 mb-1 rounded-md
text-white font-semibold text-white font-semibold md:w-2/4 lg:w-2/4">
<p class="font-semibold text-xl text-black">Todo</p>
<ul *ngFor="let task of tasks" class="flex flex-col rounded-md">
<div *ngIf="task.completed === false">
<ng-container *ngIf="edit === true && editTask._id === task._id; else elseBlock">
<div class="flex flex-col">
<input autofocus type="text" class="p-2 rounded-md font-semibold
bg-blue-300"
[(ngModel)]="editTask.name" />
<textarea rows="3" cols="10" class="p-2 rounded-md font-semibold
my-2 bg-blue-300"
[(ngModel)]="editTask.desc"></textarea>
<p class="font-semibold text-black mt-1">Deadline</p>
<input type="date" class="p-2 rounded-md font-semibold bg-blue-300 my-2"
[(ngModel)]="editTask.deadline" />
<button (click)="handleUpdate(task)" class="bg-green-500
hover:bg-green-700 p-1
rounded-md text-white font-semibold">
Update
</button>
</div>
</ng-container>
<ng-template #elseBlock>
<div class="p-2 m-2 mb-1 bg-blue-400 rounded-md">
<p class="leading-loose my-1">{{ task.name }}</p>
<p class="leading-loose my-2 overflow-auto">{{ task.desc }}</p>
<p class="font-semibold text-white mt-1">Deadline</p>
<p class="leading-loose mb-2 overflow-auto">
{{ task.deadline }}
</p>
<hr class="mb-2" />
<div class="flex flex-row justify-between bg-blue-50 p-2 rounded-md">
<span>
<button (click)="handleEdit(task)" class="mr-2 bg-blue-500
hover:bg-blue-700 p-2
rounded-md text-white font-semibold">
Edit
</button>
<button (click)="handleDelete(task)" class="bg-red-500
hover:bg-red-700 p-2
rounded-md text-white font-semibold">
Delete
</button>
</span>
<button (click)="handleComplete(task)" class="ml-2
bg-green-500 hover:bg-green-700 p-2
rounded-md text-white font-semibold">
Completed
</button>
</div>
</div>
</ng-template>
</div>
</ul>
</div>
<div class="flex flex-col my-3 p-2 m-2 mb-1 rounded-md
font-semibold md:w-2/4 lg:2/4 text-black">
<p class="font-semibold text-xl">Completed</p>
<ul *ngFor="let task of tasks" class="flex flex-col">
<div *ngIf="task.completed === true; else elseBlock">
<div class="my-3 bg-blue-100 p-2 rounded-md">
<p class="leading-loose my-1">{{ task.name }}</p>
<p class="leading-loose my-2 overflow-auto">{{ task.desc }}</p>
<p class="font-semibold text-black mt-1">Deadline</p>
<p class="leading-loose mb-2 overflow-auto">{{ task.deadline }}</p>
<hr class="mb-2" />
<div class="flex flex-row justify-between bg-blue-50 p-2 rounded-md">
<span>
<button (click)="handleDelete(task)" class="bg-red-500
hover:bg-red-700 p-2
rounded-md text-white font-semibold">
Delete
</button>
</span>
</div>
</div>
</div>
<ng-template #elseBlock> </ng-template>
</ul>
</div>
<div></div>
</div>
</div>
<!-- src/app/MyComponents/todo-add/todo-add.component.html -->
<form
(ngSubmit)="handleSubmit()"
class="max-w-md md:max-w-lg lg:max-w-xl
mx-auto m-3 p-2 rounded-md bg-blue-100
flex flex-col justify-evenly"
>
<input
type="text"
name="name"
placeholder="Enter your task name"
class="p-2 rounded-md font-semibold"
[(ngModel)]="name"
name="name"
(ngModelChange)="handleChange()"
autofocus
/>
<textarea
rows="3"
cols="10"
name="desc"
placeholder="Enter your task description"
class="p-2 rounded-md font-semibold text-black my-2"
[(ngModel)]="desc"
name="desc"
(ngModelChange)="handleChange()"
>
</textarea>
<p class="font-semibold text-black mt-1">Deadline</p>
<input
type="date"
name="deadline"
class="p-2 rounded-md font-semibold text-black my-2"
[(ngModel)]="deadline"
name="deadline"
(ngModelChange)="handleChange()"
autofocus
/>
<button
type="submit"
[disabled]="disable"
class="bg-blue-500 hover:bg-blue-600 p-2
rounded-md text-white font-semibold"
>
Add
</button>
</form>
// src/app/app.component.ts
import { Component, OnInit } from '@angular/core';
import { TodoAddComponent } from './MyComponents/todo-add/todo-add.component';
import { WebrequestService } from './service/webrequest.service';
import { CommonModule } from '@angular/common';
import { NgIf } from '@angular/common';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-root',
standalone: true,
imports: [TodoAddComponent, CommonModule, NgIf, FormsModule],
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
title = 'Your App Title';
tasks: any[] = [];
edit: boolean = false;
editTask: any = {};
constructor(private webrequest: WebrequestService) { }
ngOnInit(): void {
this.getTasks();
}
getTasks(): void {
this.webrequest.get('tasks').subscribe(
(res: any) => {
this.tasks = res;
console.log(this.tasks);
},
(error: any) => {
console.error('Error fetching tasks:', error);
}
);
}
addTask(task: any): void {
this.webrequest.post('tasks', task).subscribe(
(res: any) => {
console.log('Task added:', res);
this.getTasks();
},
(error: any) => {
console.error('Error adding task:', error);
}
);
}
handleDelete(task: any): void {
this.webrequest.delete('tasks/' + task._id, {}).subscribe(
(res: any) => {
console.log('Task Deleted:', res);
this.getTasks();
},
(error: any) => {
console.log(error);
console.error('Error Deleting task:', error);
}
);
}
handleEdit(task: any): void {
this.edit = true;
console.log(task);
this.editTask = task;
console.log(task);
}
handleComplete(task: any): void {
this.webrequest
.put('tasks/' + task._id, {
name: task.name,
desc: task.desc,
deadline: task.deadline,
completed: true,
})
.subscribe(
(res: any) => {
console.log('Task Updated:', res);
this.getTasks();
},
(error: any) => {
console.error('Error Updating task:', error);
}
);
}
handleUpdate(task: any): void {
console.log(task.name);
console.log(task);
this.edit = false;
this.webrequest
.put('tasks/' + task._id, {
name: task.name,
desc: task.desc,
deadline: task.deadline,
})
.subscribe(
(res: any) => {
console.log('Task Updated:', res);
this.getTasks();
},
(error: any) => {
console.error('Error Updating task:', error);
}
);
}
}
// src/app/service/webrequest.service.ts:
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class WebrequestService {
readonly Root_URL;
constructor(private http: HttpClient) {
this.Root_URL = "http://localhost:3001/";
}
get(uri: string) {
return this.http.get(this.Root_URL + uri);
}
post(uri: string, payload: object) {
return this.http.post(this.Root_URL + uri, payload)
}
delete(uri: string, payload: object) {
console.log(uri);
console.log(this.Root_URL + uri);
return this.http.delete(this.Root_URL + uri)
}
put(uri: string, payload: object) {
return this.http.put(this.Root_URL + uri, payload)
}
}
// src\app\MyComponents\todo-add\todo-add.component.ts
import { Component, EventEmitter, Inject, OnInit, Output } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
import { WebrequestService } from '../../service/webrequest.service';
import { NgIf } from '@angular/common';
export @Component({
selector: 'app-todo-add',
standalone: true,
imports: [FormsModule, NgIf],
templateUrl: './todo-add.component.html',
styleUrls: ['./todo-add.component.css']
})
class TodoAddComponent implements OnInit {
task: any;
constructor(private webrequest: WebrequestService) {
}
ngOnInit(): void {
this.handleChange();
}
name: string = 'Sample';
desc: string = "Sample description";
deadline: string = new Date().toDateString();
disable: boolean = true;
@Output() todoAdd: EventEmitter<any> = new EventEmitter();
handleChange() {
if (this.name.length > 0 && this.desc.length > 0) {
this.disable = false;
} else {
this.disable = true;
}
}
handleSubmit() {
console.log(this.name);
console.log(this.desc);
this.todoAdd.emit({
name: this.name,
desc: this.desc,
deadline: this.deadline
} as any)
this.name = "";
this.desc = "";
this.deadline = new Date().toDateString();
this.handleChange();
}
}
Run the frontend application using the following command
ng serve --open
Output: