Open In App

Todo List Application using MEAN Stack

Last Updated : 22 Mar, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

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

gfg_todo_task

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):

gfg-to-do-backedn-dir

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.

Javascript
// 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}`);
});
Javascript
// 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;
Javascript
// 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();
Javascript
// 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):

gfg-to-do-backend-dir

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.

HTML
<!-- 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>
HTML
<!-- 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>
Javascript
// 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);
                }
            );
    }
}
JavaScript
// 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)
    }
}
JavaScript
// 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:

gfg_to_do

Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads