TODO REST API example using NodeJS

Subscribe to my newsletter and never miss my upcoming articles

Listen to this article

Welcome to this tutorial of building a "REST API using NodeJS". Here I will show you to build a REST API from scratch. We will build a "TODO" API, where we can perform CRUD(Create, Read, Update & Delete) operations on a TODO object.

For the sake of simplicity, I will add a real database and user authentication later in my other post.

For now, we will save TODO's in a dummy array.

Let's get started.

I assume that you have node and npm installed on your computer.

Boilerplate Code Setup

Create an empty folder in your desired location on your computer. In my case my folder location is Desktop> Projects> NodeJS > nodejs-REST-api. Open your preferred code editor and navigate into the empty folder.

My preferred code editor is Visual Studio Code.So I will be using VSCode for this tutorial.

Open the integrated terminal using control(⌃) + backtick() on macOS and control(^) + backtick() on windows. The integrated terminal will open the project folder inside it by default.

Alternatively, you can also open terminal/powershell and navigate to your project folder.

Run npm init in the terminal to initate the empty project. Confirm all the details as it will ask for confirmation. Enter yes and continue.

You will end up having a package.json and package-lock.json file. These are the centralised locations for managing the various third-party packages that are used by our project.

Next, run npm install --save express. This will install express in the project folder. Now run npm install --save-dev nodemon. This will install nodemon as a development dependency to auto-start our development server whenever we make any changes in the project files.

Also we need another third party package body-parser to handle JSON data parsing. Run npm install --save body-parser.

For now we are done with installing the required packages. Lets move on with the app.

Create a file app.js on the root level of the project folder.

In app.js import express and body-parser package. Now, in this app.js file we will import express and body-parser package and we will start our server on port 3000.

const express = require('express'); 
const bodyParser = require ('body-parser'); 

const app = express(); // executing express as a function exposes us an app variable in which we give the port on which the app should run 

app.listen(3000);

This above code will start the server on port: 3000. But first we need to configure our start script in package.json. In your scripts configure the start script like this.

"scripts": { "start": "nodemon app.js" },

Now in your terminal use npm start in your terminal. This will start the server on port: 3000.

Visit http://localhost:3000 on your browser and you will see the loading spinner in anticlockwise motion or you will get Cannot GET / in your browser. Basically, this is because we haven't defined any routes/ endpoints yet but our server is working.

Avoiding CORS Errors

We are building an API so our goal will be to run the backend on different servers and the frontend will be on other servers.

This is a typical case. If we try to access our API from a different server we will face a CORS or Cross-Origin Reference Sharing error.

To avoid CORS error use the following middleware in app.js before starting the server, i.e, before app.listen(3000);. This will allow us to access our API from virtually anywhere.

app.use((req, res, next) => {
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    next();
});

Thus our development server has started and we will now move on to defining the API endpoints.

Defining Routes for CRUD operations on TODO

We will follow the MVC (Models, Views, and Controllers) design pattern for our API. So we make models and controller folders in the root directory of our project folder. We will not have views though as we will not serve any server-side rendered content but only JSON data.

In the routes folder, create a new file todo.js. We will import express here as we did in app.js and we will define four routes:

  1. Fetching all todos, i.e, a GET request to get all todos.
  2. Creating a new todo, i.e, a POST request to create a new todo.
  3. Editing an existing todo, i.e, a PUT request to edit an existing todo.
  4. Deleting a todo, i.e, a DELETE request to delete a todo.

By the way, if you want to know more about these HTTP methods. Here is the official article on Mozilla Developer Network.

1.Fetching all todos

Fetching any data requires a GET request. So we will define a route and its controller action.

In the routes/todo.js we first import express and initialize an empty array named todos.

const express = require('express');
const router = express.Router();

let todos = [];

router.get('/todos', (req, res, next) => {
    res.status(200).json({todos: todos});
});

module.exports = router;

Here, we initialize a GET request by using router.get() to the endpoint http://localhost:3000/todos and by hitting this endpoint we can fetch all todos.

The function after the /todo takes three arguments, i.e, req, res and next for

  1. Accessing the request body.
  2. Response object
  3. To pass to the next middleware in the row.

Note: Don't use next() where you send a response as it will give an error.

So, as we hit this endpoint we get a JSON object as a response containing all todos with a response code of 200.

Also, we need to export the express router function so that we can import our router function and use it anywhere. We do this by module.exports = router;.

But wait! This endpoint will not work yet as we haven't registered it in the starting point of our app, i.e, app.js.

In our app.js file, we import this route and use it as a middleware.

const express = require('express'); 
const bodyParser = require ('body-parser'); 
const todoRoutes = require('./routes/todo');//import routes

const app = express(); 

app.use(bodyParser.json()); //to send and receive data in JSON format

app.use((req, res, next) => { //middleware to avoid CORS errors
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    next();
});

app.use(todoRoutes); //use routes as a middleware

app.listen(3000);

app.js Now, our app.js will look like this, and now we can test the endpoint. Go to your browser and visit http://localhost:3000/todos and Wallah you will get a response as JSON which will be an empty array. The response will look like,

{
    "todos": []
}

2. Creating a new todo

For creating a todo we require a POST request. So, let's define a route and controller action for this in our routes/todo.js file.

router.post('/todo', (req, res, next) => {
    const body = req.body;
    const newTodo = {
        id: new Date().toString(),
        text: body.text
    };
    todos.push(newTodo);
    res.status(201).json({ message: 'Added Todo', todo: newTodo, todos: todos });
});

Here, we have a POST request by using router.post() and we extract data from the incoming request body by req.body.

Now we create a new TODO object assigning a unique id and the text that we take as input from the user.

After creating the TODO object we use the built-in javascript function that we can use on any array,push(), and push it to the todos array.

Hence, now we can save our todos.

We can test this endpoint by using any API testing app because we cannot send a POST request directly from our browser. I recommend using Postman.

When we hit this endpoint we have to enclose our data in JSON that will contain our TODO. This has to be a POST request to http://localhost:3000/todo

{
"text":"Your_TODO_String"
}

After hitting this endpoint with the correct data format and the correct type of request we will get a response that will look like this,

{
"message": "Added Todo",
    "todo": {
        "id": "Sat Jul 25 2020 23:29:18 GMT+0530 (India Standard Time)",
        "text": "Your_TODO_String"
    },
"todos": [
        {
            "id": "Sat Jul 25 2020 23:29:18 GMT+0530 (India Standard Time)",
            "text": "Your_TODO_String"
        }
    ]
}

Now, we can cross-check by fetching all todos by hitting the first endpoint. This newly added todo will surely show up.

3. Editing an existing todo

For editing a TODO we can use either a POST or a PUT request. I will use PUT here. In our routes/todo.js file, we define the route and controller. But to identify which TODO we need to update we need to pass the TODO id.

We will pass it in the request params.

router.put('/todo/:todoId', (req, res, next) => {
    const params = req.params;
    const todoId = params.todoId;
    const body = req.body;
    const todoIndex = todos.findIndex(todoItem => todoItem.id === todoId);
    if (todoIndex >= 0) {
        todos[todoIndex] = {
            id: todos[todoIndex].id,
            text: body.text
        };
        return res.status(200).json({ message: 'Updated Todo', todos: todos });
    }
    res.status(404).json({ message: 'Todo not found' });
});

Here, we use : to specify that our link/endpoint contains a parameter and we can extract it by using req.params.

So our endpoint will look like http://localhost:3000/todo/<todo_id_that_is_to_be_edited>.

From the req.body we extract the data that will update/replace the todo text.

Then we search the array starting from the beginning to the end using the todoId.

If we find any todo that has the same todoId as we passed in our endpoint, then we replace the old TODO with our new TODO.

If we don't find the TODO, we send an error response with a status code of 404 and a message with Todo not found.

So the data we send to the endpoint http://localhost:3000/todoId, will be in JSON and will be a PUT request. It will be like,

{
"text":"Your_TODO_String_that_will_replace_the_string_of_existing_todo"
}

And after successfully updating the TODO we will get a response like,

{
"message": "Updated Todo",
"todos": [
        {
            "id": "Sat Jul 25 2020 23:29:18 GMT+0530 (India Standard Time)",
            "text": "Your_TODO_String_that_will_replace_the_string_of_existing_todo"
        }
    ]
}

Make sure to cross-check by fetching all todos by hitting the first endpoint.

4.Deleting a todo

Just like for editing a TODO we need to pass todoId, for deleting, we also need to pass todoId in the request params.

router.delete('/todo/:todoId', (req, res, next) => {
    const params = req.params;
    const todoId = params.todoId;
    todos = todos.filter(todoItem => todoItem.id !== todoId);
    res.status(200).json({ message: 'Deleted todo', todos: todos });
});

Here we have a DELETE request and we extract the todoId exactly as we did it in editing the todo.

Javascript has a built-in function for arrays named filter() which can be executed on any array. According to our new condition which we specify in the function which runs on each item on the array todoItem.id !== todoId.

This operation results in a new array which excludes the TODO having its id equal to todoId. Thus we delete the TODO from the todos array.

So the data we send to the endpoint [http://localhost:3000/todoId](http://localhost:3000/todoId), will be in JSON and will be a DELETE request. Be sure to provide the correct todoId.

And after successfully deleting the TODO we will get a response like,

{
"message": "Deleted Todo",
"todos": [
        {
            "id": "Sat Jul 25 2020 23:28:57 GMT+0530 (India Standard Time)",
            "text": "Celebrate only after completion!!"
        }
    ]
}

After all these endpoints our routes/todo.js file will look like this,

const express = require('express');
const router = express.Router();

let todos = [];

router.get('/', (req, res, next) => {
    res.status(200).json({ todos: todos });
});

router.post('/todo', (req, res, next) => {
    const body = req.body;
    const newTodo = {
        id: new Date().toString(),
        text: body.text
    };
    todos.push(newTodo);
    res.status(201).json({ message: 'Added Todo', todo: newTodo, todos: todos });
});

router.put('/todo/:todoId', (req, res, next) => {
    const params = req.params;
    const todoId = params.todoId;
    const body = req.body;
    const todoIndex = todos.findIndex(todoItem => todoItem.id === todoId);
    if (todoIndex >= 0) {
        todos[todoIndex] = {
            id: todos[todoIndex].id,
            text: body.text
        };
        return res.status(200).json({ message: 'Updated Todo', todos: todos });
    }
    res.status(404).json({ message: 'Todo not found' });
});
router.delete('/todo/:todoId', (req, res, next) => {
    const params = req.params;
    const todoId = params.todoId;
    todos = todos.filter(todoItem => todoItem.id !== todoId);
    res.status(200).json({ message: 'Deleted todo', todos: todos });
});

exports.default = router;

routes/todo.js

Conclusion

Congrats😎! we successfully created a REST API using NodeJS which can perform CRUD operations on TODO.

Hope you enjoyed coding with me and I can now see you imagine making your own REST API using NodeJS.

Also for more help here is a link to my Github repository that has almost similar code but uses typescript. Don't hesitate to check it out. The typescript version resides in the src folder and the pure javascript version resides in the dist folder in the repository. Make sure to play with it by downloading or cloning the repository.

You can also check out my other repositories here. Happy Coding👨🏽‍💻‼️

No Comments Yet