MERN stack TODO application [Backend]

We'll be creating a minimal full-stack app using the MERN stack (MongoDB for database, Express and Node for backend, and React for frontend) to perform CRUD operations.
Our app will allow users to
  • Create a todo
  • Read todos
  • Update a todo
  • Delete a todo
  • This series should enable you understand CRUD operations using MERN stack.
    In this part (part1), we will
  • Initialize our backend using npm and install necessary packages
  • Set up a MongoDB database
  • Set up server using Node and Express
  • Create a database schema to define a Todo
  • Set up API routes to create, read, update and delete documents from the database
  • Testing our API routes using Insomnia
  • Before we get started
    Prerequisites
    One should have at least some basic understanding of fundamental programming concepts and some experience with HTML, CSS, JavaScript.
    This post is not meant to explain the MERN stack, but it is a good introduction to building a full-stack app with it.
    Install
  • VS Code or any other editor
  • Latest version of Node.js
  • Insomnia or Postman
  • Prettier VS code extension to format the code
  • Part 1: Creating Backend
    1. Initializing our project
    Create a new folder and name it anything that you like and then open the folder in VS code and run the following code from the command prompt.
    npm init -y
    After running this command you will find a package.json if the folder.
    2. Setting up package.json
    i. Install the following dependencies
    Run the following commands in the terminal to install the dependencies
    npm i cors dotenv express mongoose
    cors: allows cross-origin api calls
    dotenv: needed to access data from .env files
    express: web application framework for node.js
    mongoose: It is needed to define the database schema and connecting to mongoDB
    ii. Install the following development dependencies
    Now install the following development dependencies, -D is used to install the development dependencies.
    npm i -D nodemon
    After installing the dependencies the package.json folder should look as follows.
    // package.json
    {
      "name": "mern-todo",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "keywords": [],
      "author": "",
      "license": "ISC",
      "dependencies": {
        "config": "^3.3.6",
        "cors": "^2.8.5",
        "dotenv": "^10.0.0",
        "express": "^4.17.1",
        "mongoose": "^5.13.2"
      },
      "devDependencies": {
        "nodemon": "^2.0.11"
      }
    }
    iii. change the main entry point to server.js
    Now, create a server.js file and a .env. The server.js file will be the entry point of the server and the .env file will contain the MONGO_URI. We also have to make the following changes in the package.json
    //package.json
    {
      "name": "mern-todo",
      "version": "1.0.0",
      "description": "",
      "main": "server.js", //changed
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "keywords": [],
      "author": "",
      "license": "ISC",
      "dependencies": {
        "config": "^3.3.6",
        "cors": "^2.8.5",
        "dotenv": "^10.0.0",
        "express": "^4.17.1",
        "mongoose": "^5.13.2"
      },
      "devDependencies": {
        "nodemon": "^2.0.11"
      }
    }
    Now, create the following folders
  • config: Inside the config folder, create a file named db.js. This file will contain the required code for connecting to the MongoDB database.

  • controllers: The controllers folder will contain the files which will have the methods for the end points to communicate with the database.

  • models: The models folder, will contain the files which will define the MongoDB schema

  • routers: The routers folder will contain the files with the endpoints.

  • At this stage the file structure should look as follows
    .
    ├── config
    │   └── db.js
    ├── controllers
    │   └── todo.js
    ├── models
    │   └── todo.js
    ├── node_modules
    ├── routes
    │   └── todo.js
    ├── .env
    ├── server.js
    ├── package-lock.json
    └── package.json
    iv. Change the scripts to the following
    "scripts": {
      "start":"node server.js",
      "dev":"nodemon server.js"
    }
    The package.json file should look as follows
    {
      "name": "mern-todo",
      "version": "1.0.0",
      "description": "",
      "main": "server.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "start": "node server.js", //added
        "dev": "nodemon server.js" //added
      },
      "keywords": [],
      "author": "",
      "license": "ISC",
      "dependencies": {
        "config": "^3.3.6",
        "cors": "^2.8.5",
        "dotenv": "^10.0.0",
        "express": "^4.17.1",
        "mongoose": "^5.13.2"
      },
      "devDependencies": {
        "nodemon": "^2.0.11"
      }
    }
    v. Setting up server
    We will do the following to setup the server
  • Import express
  • Initialize our app using express()
  • Set up a get method for the endpoint http://localhost:8000 using app.get()
  • Set the PORT to 8000 for our server to run
  • Have our app to listen to PORT using app.listen()
  • .
    ├── config
    │   └── db.js
    ├── controllers
    │   └── todo.js
    ├── models
    │   └── todo.js
    ├── node_modules
    ├── routes
    │   └── todo.js
    ├── .env
    ├── server.js <-- we are here
    ├── package-lock.json
    └── package.json
    The code will look as follows
    // server.js
    const express = require("express");
    const app = express();
    // initialize middleware
    app.use(express.json({ extended: false }));
    app.get("/", (req, res) => res.send("Server up and running"));
    // setting up port
    const PORT = process.env.PORT || 8000;
    app.listen(PORT, () => {
    console.log(`server is running on http://localhost:${PORT}`);
    });
    view raw server.js hosted with ❤ by GitHub
    And start the server using nodemon using the following code. Make sure you are running the following command from the project directory.
    npm run dev
    If the server has started successfully then it should show the following message in the terminal
    [nodemon] 2.0.11
    [nodemon] to restart at any time, enter `rs`
    [nodemon] watching path(s): *.*
    [nodemon] watching extensions: js,mjs,json
    [nodemon] starting `node server.js`
    server is running on http://localhost:8000
    You can also open http://localhost:8000 on your browser.
    vi. Getting the MONGO URI from mongoDB
    To connect to the database we will need the link for the mongoDB collection.
  • Log in to mongoDB
  • Create a new project
  • Build a cluster
  • Select cloud provider
  • Create cluster
  • wait for the cluster to be created.
  • Click on connect
  • click on allow access from anywhere. Then Add IP address
  • Create a database user. You'll need the username and password for the MongoDB URI.
  • Click on the Choose a connection method
  • Click on Connect your application
  • Select the following driver and version

    Connect Cluster

  • Copy the mongodb+srv and paste it in the .env file

  • vii. Setting up .env file
    //.env
    MONGO_URI = mongodb+srv://<username>:<password>@cluster0.owmij.mongodb.net
    Replace the <username> and <password> with your database username and password which you will set in step 9.
    viii. Connecting to database
    .
    ├── config
    │   └── db.js <-- we are here
    ├── controllers
    │   └── todo.js
    ├── models
    │   └── todo.js
    ├── node_modules
    ├── routes
    │   └── todo.js
    ├── .env
    ├── server.js
    ├── package-lock.json
    └── package.json
    Now, open the db.js file which is in the config folder and add the following changes.
  • Import mongoose
  • Import MONGO_URI from .env
  • Define the connectDB methof for connecting to the database
  • Export the connectDB method to be called in server.js
  • // config/db.js
    const mongoose = require("mongoose");
    const db = process.env.MONGO_URI;
    const connectDB = async () => {
    try {
    await mongoose.connect(db, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
    });
    console.log("MongoDB is connected");
    } catch (err) {
    console.error(err.message);
    process.exit(1);
    }
    };
    module.exports = connectDB;
    view raw db.js hosted with ❤ by GitHub
    Add the following changes in the server.js file.
  • Import dotenv
  • Import connectDB method from config/db.js
  • Call the connectDB method.
  • Let us make the the following changes in server.js
    // server.js
    require("dotenv").config(); //added
    const express = require("express");
    const connectDB = require("./config/db"); //added
    const app = express();
    // connect database
    connectDB();//added
    // initialize middleware
    app.use(express.json({ extended: false }));
    app.get("/", (req, res) => res.send("Server up and running"));
    // setting up port
    const PORT = process.env.PORT || 8000;
    app.listen(PORT, () => {
    console.log(`server is running on http://localhost:${PORT}`);
    });
    view raw server.js hosted with ❤ by GitHub
    Save the changes it will restart the server or use the command npm run dev. The terminal should show a message of MongoDB is connected which we have added in the db.js under the try block.
    ix. Defining database schema
    Create a todo.js file in the models folder. We will define the database schema in this file.
    .
    ├── config
    │   └── db.js
    ├── controllers
    │   └── todo.js
    ├── models
    │   └── todo.js <-- we are here
    ├── node_modules
    ├── routes
    │   └── todo.js
    ├── .env
    ├── server.js
    ├── package-lock.json
    └── package.json
  • Import mongoose
  • Create a Schema called TodoSchema
  • We will add two fields for our todo; title and description
  • Type of title will be String and it is a mandatory field
  • Type of description will be String and it is not a mandatory field
  • Export the model
  • The code will look as follows
    // models/todo.js
    const mongoose = require("mongoose");
    const TodoSchema = new mongoose.Schema({
    title: {
    type: "String",
    required: true,
    },
    description: {
    type: "String",
    },
    });
    const Todo = mongoose.model("todo", TodoSchema);
    module.exports = Todo;
    view raw models_todo.js hosted with ❤ by GitHub
    x. Defining the end points
    .
    ├── config
    │   └── db.js
    ├── controllers
    │   └── todo.js
    ├── models
    │   └── todo.js
    ├── node_modules
    ├── routes
    │   └── todo.js <-- we are here
    ├── .env
    ├── server.js
    ├── package-lock.json
    └── package.json
    We will define the end points for the CRUD operations
  • Import express
  • Initialize router
  • We will later import the methods for the endpoint from controllers
  • Define a GET method to read all the todo
  • Define a POST method to create a new todo
  • Define a PUT method to update a existing todo
  • Define a DELETE method to delete a existing todo
  • Export the router
  • The code will look as follows
    // router/todo.js
    const express = require("express");
    const router = express.Router();
    /**
    * @route GET api/todo
    * @description get all todo
    * @access public
    */
    router.get("/");
    /**
    * @route POST api/todo
    * @description add a new todo
    * @access public
    */
    router.post("/");
    /**
    * @route PUT api/todo/:id
    * @description update todo
    * @access public
    */
    router.put("/:id");
    /**
    * @route DELETE api/todo/:id
    * @description delete todo
    * @access public
    */
    router.delete("/:id");
    module.exports = router;
    view raw todo.js hosted with ❤ by GitHub
    xi. Defining the methods for the end points
    We will define the methods for the end points in the controllers folder
    .
    ├── config
    │   └── db.js
    ├── controllers
    │   └── todo.js <-- we are here
    ├── models
    │   └── todo.js
    ├── node_modules
    ├── routes
    │   └── todo.js
    ├── .env
    ├── server.js
    ├── package-lock.json
    └── package.json
  • Import Todo model from models/todo
  • Define the following four methods
    • getAllTodo
    • postCreateTodo
    • putUpdateTodo
    • deleteTodo
  • Export all the methods
  • // controllers/todo.js
    const Todo = require("../models/todo");
    exports.getAllTodo = (req, res) => {
    Todo.find()
    .then((todo) => res.json(todo))
    .catch((err) =>
    res
    .status(404)
    .json({ message: "Todo not found", error: err.message })
    );
    };
    exports.postCreateTodo = (req, res) => {
    Todo.create(req.body)
    .then((data) => res.json({ message: "Todo added successfully", data }))
    .catch((err) =>
    res
    .status(400)
    .json({ message: "Failed to add todo", error: err.message })
    );
    };
    exports.putUpdateTodo = (req, res) => {
    Todo.findByIdAndUpdate(req.params.id, req.body)
    .then((data) => res.json({ message: "updated successfully", data }))
    .catch((err) =>
    res
    .status(400)
    .json({ message: "Failed to update todo", error: err.message })
    );
    };
    exports.deleteTodo = (req, res) => {
    Todo.findByIdAndRemove(req.params.id, req.body)
    .then((data) =>
    res.json({ message: "todo deleted successfully", data })
    )
    .catch((err) =>
    res
    .status(404)
    .json({ message: "book not found", error: err.message })
    );
    };
    getAllTodo: The find() method will return all the todo in the collection. If the collection is empty then it will return a 404 error.
    postCreateTodo: The create() method will create a todo and return a success message. Otherwise, it will return a 400 error.
    putUpdateTodo: The findByIdAndUpdate() will require two parameters the id and data of the todo to be updated. The id parameter will be extracted from req.params.id.
    deleteTodo: The findByIdAndRemove() method will require only one parameter that is the id of the todo.
    xii. Adding the methods to the end points
    .
    ├── config
    │   └── db.js
    ├── controllers
    │   └── todo.js
    ├── models
    │   └── todo.js
    ├── node_modules
    ├── routes
    │   └── todo.js <-- we are here
    ├── .env
    ├── server.js
    ├── package-lock.json
    └── package.json
  • Import the methods for CRUD operations
  • Adding the methods to the end points
  • // routes/todo.js
    const express = require("express");
    const router = express.Router();
    const {
    getAllTodo,
    postCreateTodo,
    putUpdateTodo,
    deleteTodo,
    } = require("../controllers/todo");
    /**
    * @route GET api/todo
    * @description get all todo
    * @access public
    */
    router.get("/", getAllTodo);
    /**
    * @route POST api/todo
    * @description add a new todo
    * @access public
    */
    router.post("/", postCreateTodo);
    /**
    * @route PUT api/todo/:id
    * @description update todo
    * @access public
    */
    router.put("/:id", putUpdateTodo);
    /**
    * @route DELETE api/todo/:id
    * @description delete todo
    * @access public
    */
    router.delete("/:id", deleteTodo);
    module.exports = router;
    view raw routes_todo.js hosted with ❤ by GitHub
    xiii. Adding the routes end points in the server.js
    .
    ├── config
    │   └── db.js
    ├── controllers
    │   └── todo.js
    ├── models
    │   └── todo.js
    ├── node_modules
    ├── routes
    │   └── todo.js
    ├── .env
    ├── server.js <-- we are here
    ├── package-lock.json
    └── package.json
    The final part of completing the backend is to add the endpoints to the server.js file.
  • Import routes/todo.js
  • Add the routes endpoints to the middleware
  • // server.js
    require("dotenv").config();
    const express = require("express");
    const connectDB = require("./config/db");
    const app = express();
    // routes
    const todo = require("./routes/todo"); // added
    // connect database
    connectDB();
    // initialize middleware
    app.use(express.json({ extended: false }));
    app.get("/", (req, res) => res.send("Server up and running"));
    // use routes
    app.use("/api/todo", todo); // added
    // setting up port
    const PORT = process.env.PORT || 8000;
    app.listen(PORT, () => {
    console.log(`server is running on http://localhost:${PORT}`);
    });
    view raw server.js hosted with ❤ by GitHub
    3 Testing the end points using Insomnia
  • Creating a todo
  • We will send a POST request to http://localhost:8000/api/todo
  • Reading the todo
  • We will send a GET request to http://localhost:8000/api/todo
    You can check the changes in mongoDB in collections
  • Updating a todo
  • To update a todo we will send a PUT request to http://localhost:8000/api/todo/id
    The id has to be taken from the response message of the server.
    {
        "message": "Todo added successfully",
        "data": {
          "_id": "60ec0f9655f9735a60a2d967",
          "title": "test todo",
          "description": "test todo",
          "__v": 0
        }
      }
    For updating the todo we will need the id. We will get the id from the _id from the preview tab. We can get the id from the preview after using the GET request and POST request.
  • Deleting a todo
  • To delete a todo we will send a DELETE request to http://localhost:8000/api/todo/id
    4. Adding cors
    .
    ├── config
    │   └── db.js
    ├── controllers
    │   └── todo.js
    ├── models
    │   └── todo.js
    ├── node_modules
    ├── routes
    │   └── todo.js
    ├── .env
    ├── server.js <-- we are here
    ├── package-lock.json
    └── package.json
    Added cors so that we can make the api calls from the frontend application like react.
    require("dotenv").config();
    const express = require("express");
    const cors = require("cors"); // added
    const connectDB = require("./config/db");
    const app = express();
    // routes
    const todo = require("./routes/todo");
    // connect database
    connectDB();
    // cors
    app.use(cors({ origin: true, credentials: true })); // added
    // initialize middleware
    app.use(express.json({ extended: false }));
    app.get("/", (req, res) => res.send("Server up and running"));
    // use routes
    app.use("/api/todo", todo);
    // setting up port
    const PORT = process.env.PORT || 8000;
    app.listen(PORT, () => {
    console.log(`server is running on http://localhost:${PORT}`);
    });
    view raw server.js hosted with ❤ by GitHub
    You can see the entire code of this blog in GitHub

    30

    This website collects cookies to deliver better user experience

    MERN stack TODO application [Backend]