How to perform CRUD operations and consume RESTful API using React?

How to perform CRUD operations and consume RESTful API using React?

In this post, we go through a tutorial on how to create a React application that can perform CRUD functions and take in data from a RESTful API. Recently we have experienced rapid growth of mobile, Internet-based communication. We have...

In this post, we go through a tutorial on how to create a React application that can perform CRUD functions and take in data from a RESTful API.

Recently we have experienced rapid growth of mobile, Internet-based communication. We have smartphones, tablets, netbooks, and other connected devices that create a need to serve appropriate content for each specific front-end profile. Also, the Internet is becoming available for new regions and social groups, constantly increasing web traffic. On the other side, users' computers and browsers, and JavaScript itself are getting more and more powerful, providing more possibilities for processing data on the web via the client-side. In this situation, the best solution can often be to send data as the response, instead of sending page content. That is, we don't need to reload the whole page on each request, but send back the corresponding data and let the client (front-end stuff) process the data.

We can develop a backend application exposing a remote API (usually based on the REST protocol) and a front-end (usually JavaScript) application, which communicates with the API and renders all the data on the device.

If backend data is consumed by humans, we need to develop a user interface (UI) to provide the possibility for users to manage the data. Modern web applications should have responsive and friendly UIs, ensuring adequate user experience. Also, modern UIs can be arbitrarily complex, with multi-panel, nested layouts, paging, progress bars, etc. In this case, the component model can be the right solution. React.js is a light-weight JavaScript framework, which is oriented toward the creation of component-based web UIs. React doesn't provide any means for communicating with the backend, but we can use any communication library from inside React components.

Before starting development, we need to set up a React.js development environment.

1. React.js Development Environment Set Up

There are several ways to use React.js. The simplest way is just to include React libraries in the

Using Hapi.js, Mongoose, And MongoDB To Build A REST API

Using Hapi.js, Mongoose, And MongoDB To Build A REST API

In this tutorial we’re going to develop a simple RESTful API using Hapi.js, Joi and Mongoose as the backend framework, and MongoDB as the NoSQL database. Rather than just using Hapi.js as a drop in framework replacement, I wanted to improve upon what we had previously seen, by simplifying functions and validating client provided data.

In this tutorial we’re going to develop a simple RESTful API using Hapi.js, Joi and Mongoose as the backend framework, and MongoDB as the NoSQL database. Rather than just using Hapi.js as a drop in framework replacement, I wanted to improve upon what we had previously seen, by simplifying functions and validating client provided data.

To continue on my trend of MongoDB with Node.js material, I thought it would be a good idea to use one of my favorite Node.js frameworks. Previously I had written about using Express.js with Mongoose, but this time I wanted to evaluate the same tasks using Hapi.js.

If you haven’t seen my previous tutorial, don’t worry because it is not a requirement. However, the previous tutorial is a valuable read if you’re evaluating Node.js frameworks. What is required is having a MongoDB instance available to you. If you’re unfamiliar with deploying MongoDB, you might want to check out my tutorial titled, Getting Started with MongoDB as a Docker Container Deployment

Creating a Hapi.js Project with MongoDB and Data Validation Support

With MongoDB available to us, we can create a fresh Hapi.js project with all the appropriate dependencies. Create a new project directory and execute the following commands:

npm init -y
npm install hapi joi mongoose --save


The above commands will create a new package.json file and install the Hapi.js framework, the Joi validation framework, and the Mongoose object document modeler (ODM).

We’re going to add all of our application code into a single project file. Create an app.js file and include the following boilerplate JavaScript code:

const Hapi = require("hapi");
const Mongoose = require("mongoose");
const Joi = require("joi");

const server = new Hapi.Server({ "host": "localhost", "port": 3000 });

server.route({
    method: "POST",
    path: "/person",
    options: {
        validate: {}
    },
    handler: async (request, h) => {}
});

server.route({
    method: "GET",
    path: "/people",
    handler: async (request, h) => {}
});

server.route({
    method: "GET",
    path: "/person/{id}",
    handler: async (request, h) => {}
});

server.route({
    method: "PUT",
    path: "/person/{id}",
    options: {
        validate: {}
    },
    handler: async (request, h) => {}
});

server.route({
    method: "DELETE",
    path: "/person/{id}",
    handler: async (request, h) => {}
});

server.start();


We’ve added quite a bit of code to our app.js file, but it isn’t really anything beyond the Hapi.js getting started material found on the framework’s website. Essentially we’ve imported the dependencies that we downloaded, defined our servers settings, defined the routes which are also referred to as endpoints, and started the server.

You’ll notice that not all of our routes are the same. We’re developing a create, retrieve, update, and delete (CRUD) based REST API with validation on some of our endpoints. In particular we’ll be adding validation logic to the endpoints that save data to the database, not retrieve or remove.

With our boilerplate code in place, lets take a look at configuring MongoDB and adding our endpoint logic.

Interacting with the Database using the Mongoose ODM

Remember, I’m assuming you already have access to an instance of MongoDB. At the top of your app.js file, after you defined your server configuration, we need to connect to MongoDB. Inculde the following line to establish a connection:

Mongoose.connect("mongodb://localhost/thepolyglotdeveloper");


You’ll need to swap out my connection string information with your connection string information. When working with Mongoose, we need to have a model defined for each of our collections. Since this is a simple example, we’ll only have one model and it looks like the following:

const PersonModel = Mongoose.model("person", {
    firstname: String,
    lastname: String
});


Each of our documents will contain a firstname and a lastname, but neither of the two fields are required. These documents will be saved to the people collection which is the plural form of our ODM model.

At this point in time MongoDB is ready to be used.

It is now time to start developing our API endpoints, so starting with the creation endpoint, we might have something like this:

server.route({
    method: "POST",
    path: "/person",
    options: {
        validate: {}
    },
    handler: async (request, h) => {
        try {
            var person = new PersonModel(request.payload);
            var result = await person.save();
            return h.response(result);
        } catch (error) {
            return h.response(error).code(500);
        }
    }
});


We’ve skipped the validation logic for now, but inside our handler we are taking the payload data sent with the request from the client and creating a new instance of our model. Using our model we can save to the database and return the response back to the client. Mongoose will do basic validation against the payload data based on the schema, but we can do better. This is where Joi comes in with Hapi.js.

Let’s look at the validate object in our route:

validate: {
    payload: {
        firstname: Joi.string().required(),
        lastname: Joi.string().required()
    },
    failAction: (request, h, error) => {
        return error.isJoi ? h.response(error.details[0]).takeover() : h.response(error).takeover();
    }
}


In the validate object, we are choosing to validate our payload. We also have the option to validate the params as well as the query of a request, but neither are necessary here. While we could do some very complex validation, we’re just validating that both properties are present. Rather than returning a vague error to the user if either are missing, we’re returning the exact error using the failAction which is optional.

Now let’s take a look at retrieving the data that had been created. In a typical CRUD scenario, we can retrieve all data or a particular piece of data. We’re going to accommodate both scenarios.

server.route({
    method: "GET",
    path: "/people",
    handler: async (request, h) => {
        try {
            var person = await PersonModel.find().exec();
            return h.response(person);
        } catch (error) {
            return h.response(error).code(500);
        }
    }
});


The above route will execute the find function in Mongoose with no query parameters. This means that there is no criteria to search for which results in all data being returned from the collection. Similarly, we could return a particular piece of data.

If we wanted to return a particular piece of data we could either provide parameters in the find function, or use the following:

server.route({
    method: "GET",
    path: "/person/{id}",
    handler: async (request, h) => {
        try {
            var person = await PersonModel.findById(request.params.id).exec();
            return h.response(person);
        } catch (error) {
            return h.response(error).code(500);
        }
    }
});


In the above endpoint we are accepting an id route parameter and are using the findById function. The data returned from the interaction is returned to the client facing application.

With the create and retrieve endpoints out of the way, we can bring this tutorial to an end with the update and delete endpoints. Starting with the update endpoint, we might have something like this:

server.route({
    method: "PUT",
    path: "/person/{id}",
    options: {
        validate: {
            payload: {
                firstname: Joi.string().optional(),
                lastname: Joi.string().optional()
            },
            failAction: (request, h, error) => {
                return error.isJoi ? h.response(error.details[0]).takeover() : h.response(error).takeover();
            }
        }
    },
    handler: async (request, h) => {
        try {
            var result = await PersonModel.findByIdAndUpdate(request.params.id, request.payload, { new: true });
            return h.response(result);
        } catch (error) {
            return h.response(error).code(500);
        }
    }
});


Just like with the create endpoint we are validating our data. However, our validation is a little different than the previous endpoint. Instead of making our properties required, we are just saying they are optional. When we do this, any property that shows up that isn’t in our validator, we will be throwing an error. So for example, if I wanted to include a middle name, it would fail.

Inside the handler function, we can use a shortcut function called findByIdAndUpdate which will allow us to find a document to update and update it in the same operation rather than doing it in two steps. We are including the new setting so that the latest document information can be returned back to the client.

The delete endpoint will be a lot simpler:

server.route({
    method: "DELETE",
    path: "/person/{id}",
    handler: async (request, h) => {
        try {
            var result = await PersonModel.findByIdAndDelete(request.params.id);
            return h.response(result);
        } catch (error) {
            return h.response(error).code(500);
        }
    }
});


Using an id parameter passed from the client, we can execute the findByIdAndDelete function which will find a document by the id, then remove it in one swoop rather than using two steps.

You should be able to play around with the API as of now. You might want to use a tool like Postman before trying to use with a frontend framework like Angular or Vue.js.

Conclusion

You just saw how to create a REST API with Hapi.js and MongoDB. While we used Mongoose and Joi to help us with the job, there are other alternatives that can be used as well.

While Hapi.js is awesome, in my opinion, if you’d like to check out how to accomplish the same using a popular framework like Express.js, you might want to check out my tutorial titled, Building a REST API with MongoDB, Mongoose, and Node.js. I’ve also written a version of this tutorial using Couchbase as the NoSQL database. That version of the tutorial can be found here.

A video version of this tutorial can be seen below.

Creating a RESTful Web API with Node.js and Express.js from scratch

Creating a RESTful Web API with Node.js and Express.js from scratch

In this article, I’ll show you step by step how to create a RESTful Web API with Node.js and Express.js by building a simple and useful Todo API. This article assumes you have basic javascript knowledge and terminal using capabilities.

In this article, I’ll show you step by step how to create a RESTful Web API with Node.js and Express.js by building a simple and useful Todo API. This article assumes you have basic javascript knowledge and terminal using capabilities.

You can also build a Web API in Node.js by using another framework except Express.js but Express.js is one of the most popular web framework for Node.js.

You can found the final source code of this Web API in this github repository.

Let’s start to create our mentioned Web API.

Before start

If you have never used Node.js or npm package manager you should install them.

To check whether the Node.js is already installed on your computer, open your terminal and run node -v command. If you see your Node.js version it's installed. Otherwise go to below link.

Click here to download and install Node.js (You can choose LTS version)

And if you don’t have any IDE or text editor for writing javascript I advice you Visual Studio Code.

Click here to download VS Code (Optional)

About express-generator

In fact we could use <a href="https://expressjs.com/en/starter/generator.html" target="_blank">express-generator</a> tool which designed to creating an Express Web API quickly but I want to create this API from scratch because of that tool puts some extra files and folder structures that we don't need them now. But you can use this useful tool next time on creating new Web API. I won't use it now due to keep article simple.

Creating Project

Go to your workspace root folder and create a new folder there named "todo-api".

Then create "package.json" and "server.js" files into "todo-api" folder like below.

package.json

{
    "name": "todo-api",
    "version": "1.0.0",
    "scripts": {
        "start": "node server.js"
    },
    "dependencies": {
        "express": "^4.16.4"
    }
}

server.js

const http = require('http');
const express = require('express');
const app = express();
app.use(express.json());
app.use('/', function(req, res) {
    res.send('todo api works');
});
const server = http.createServer(app);
const port = 3000;
server.listen(port);
console.debug('Server listening on port ' + port);

After creating above files open your terminal in the "todo-api" folder and run npm installcommand.

This command will be install your project dependencies which pointed at the "package.json" file.

After finished package download process, downloaded dependency files will be installed into"node_modules" folder at the root of the "todo-api" folder.

After finished package installing then run npm start to start our Web API.

Now our Web API listening. To see result open your web browser then write localhost:3000 to address bar and press enter.

As result you’ll see our request handler response in your browser: “todo api works”.

This is a dead simple Express.js Web API. And it needs the some development. For example we need to an api endpoint to get todo items. So let’s add a new API endpoint for this.

Create a new folder named "routes" in the root of the "todo-api" folder.

Then create a "items.js" file inside of "routes" folder and put following codes inside it.

Your final folder structure should be like below;

/todo-api
/node_modules
/routes
    items.js
package.json
server.js

items.js

const express = require('express');
const router = express.Router();
const data = [
    {id: 1, title: 'Finalize project', order: 1, completed: false, createdOn: new Date()},
    {id: 2, title: 'Book ticket to London', order: 2, completed: false, createdOn: new Date()},
    {id: 3, title: 'Finish last article', order: 3, completed: false, createdOn: new Date()},
    {id: 4, title: 'Get a new t-shirt', order: 4, completed: false, createdOn: new Date()},
    {id: 5, title: 'Create dinner reservation', order: 5, completed: false, createdOn: new Date()},
];
router.get('/', function (req, res) {
    res.status(200).json(data);
});
router.get('/:id', function (req, res) {
    let found = data.find(function (item) {
        return item.id === parseInt(req.params.id);
    });
    if (found) {
        res.status(200).json(found);
    } else {
        res.sendStatus(404);
    }
});
module.exports = router;

Initial code of "items.js" file contains two endpoints. First one gets all todo items and second one gets one item which matches given id parameter.

Before testing items routes we should register it in the "server.js" file.

Modify "server.js" file like below to register new item routes.

server.js

const http = require('http');
const express = require('express');
const itemsRouter = require('./routes/items');
const app = express();
app.use(express.json());
app.use('/items', itemsRouter);
app.use('/', function(req, res) {
    res.send('todo api works');
});
const server = http.createServer(app);
const port = 3000;
server.listen(port);
console.debug('Server listening on port ' + port);

Now run npm start to start our Web API.

Then open your web browser and write localhost:3000/items to address bar and press enter.

You’ll see todo items json array in the response body.

And write localhost:3000/items/3 to address bar and press enter.

You’ll see the todo item which has id 3 in the response body.

But not finished up yet.

CRUD Operations and HTTP methods

I think we’ll need CRUD operations to Create, Read, Update and Delete todo items.

We have already two endpoints for getting items. So we need Create, Update and Delete endpoints.

Let’s add also these endpoints into the items.js file.

Our final "items.js" file and endpoints should be like below.

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

const data = [
  {id: 1, title: 'Finalize project',          order: 1, completed: false, createdOn: new Date()},
  {id: 2, title: 'Book ticket to London',     order: 2, completed: false, createdOn: new Date()},
  {id: 3, title: 'Finish last article',       order: 3, completed: false, createdOn: new Date()},
  {id: 4, title: 'Get a new t-shirt',         order: 4, completed: false, createdOn: new Date()},
  {id: 5, title: 'Create dinner reservation', order: 5, completed: false, createdOn: new Date()},
];

router.get('/', function (req, res) {
  res.status(200).json(data);
});

router.get('/:id', function (req, res) {
  let found = data.find(function (item) {
    return item.id === parseInt(req.params.id);
  });

  if (found) {
    res.status(200).json(found);
  } else {
    res.sendStatus(404);
  }
});

router.post('/', function (req, res) {
  let itemIds = data.map(item => item.id);
  let orderNums = data.map(item => item.order);

  let newId = itemIds.length > 0 ? Math.max.apply(Math, itemIds) + 1 : 1;
  let newOrderNum = orderNums.length > 0 ? Math.max.apply(Math, orderNums) + 1 : 1;

  let newItem = {
    id: newId,
    title: req.body.title,
    order: newOrderNum,
    completed: false,
    createdOn: new Date()
  };

  data.push(newItem);

  res.status(201).json(newItem);
});

router.put('/:id', function (req, res) {
  let found = data.find(function (item) {
    return item.id === parseInt(req.params.id);
  });

  if (found) {
    let updated = {
      id: found.id,
      title: req.body.title,
      order: req.body.order,
      completed: req.body.completed
    };

    let targetIndex = data.indexOf(found);

    data.splice(targetIndex, 1, updated);

    res.sendStatus(204);
  } else {
    res.sendStatus(404);
  }
});

router.delete('/:id', function (req, res) {
  let found = data.find(function (item) {
    return item.id === parseInt(req.params.id);
  });

  if (found) {
    let targetIndex = data.indexOf(found);

    data.splice(targetIndex, 1);
  }

  res.sendStatus(204);
});

module.exports = router;

Short Explanation

I wanna explain shortly some points of our last codes.

First of all you must have noticed that our api works on a static data and keeps it on memory. All of our GET, POST, PUT and DELETE http methods just manipulate a json array. The purpose of this is to keep article simple and draw attention to the Web API structure.

Due to this situation our POST method has some extra logic such as calculating next item ids and order numbers.

So you can modify logic and data structures in these http methods to use a database or whatever you want.

Testing API with Postman

We have tested the GET methods of our Web API in our web browser and seen responses. But we can’t test directly POST, PUT and DELETE http methods in web browser.

If you want to test also other http methods you should use Postman or another http utility.

Now I’ll show you how to test the Web API with Postman

Before we start click here and install Postman.

When you first launch Postman after installing you’ll see start window. Close this start window by clicking close button on top right corner. Then you must see following screen.

An empty Postman request

Sending GET Request

Before sending a request to API we should start it by running npm startcommand as we do before.

After start the Web API and seeing “Server listening on…” message write localhost:3000/itemsto address bar as seen below and click Send button. You'll see todo items array as API response like below.

Sending a GET request with Postman

You can try similarly by giving an item id in request url like this localhost:3000/items/3

Sending POST Request

To sending a POST request and create a new todo item write localhost:3000/items to address bar and change HTTP verb to POST by clicking arrow at front of the address bar as seen below.

Sending a POST request with Postman

Before sending the POST request you should add request data to body of the request by clicking body tab and selecting raw and JSON as seen below.

Attaching a JSON body to POST request in Postman

Now click Send button to send POST request to the Web API. Then you must get “201 Created” http response code and seeing created item in the response body.

To see the last status of todo items send a get request to localhost:3000/itemsaddress. You must see newly created item at the end of the list.

Sending PUT Request

Sending PUT request is very similar to sending POST request.

The most obvious difference is request url should be pointed specific item like this localhost:3000/items/3

And you should choose PUT as http verb instead of POST and send all of the required data in the request body unlike POST.

For example you could send a JSON body in the PUT request as below.

An example JSON body for PUT request

{
    "title": "New title of todo item",
    "order": 3,
    "completed": false
}

When you click Send button you must get “204 No Content” http response code. You can check item you updated by sending a get request.

Sending DELETE Request

To send a DELETE request, change the request url to address a specific item id like this localhost:3000/items/3

And select DELETE as http verb and click Send button.

You must get “204 No Content” http response code as result of the DELETE operation.

Send a get request and see the last status of list.

About the DELETE Http Request

I want to say a few words about DELETE http request. You must have noticed something in our delete code. DELETE request returns “204 No Content” every situation.

Http DELETE requests are idempotent. So what that mean? If you delete a resource on server by sending DELETE request, it’s removed from the collection. And every next DELETE request on the same resource won’t change outcome. So you won’t get “404 Not Found” in the second request. Each request returns same response whether succeed or not. That’s mean idempotent operation.

Conclusion

Finally we’ve tested all http methods of our Web API.

As you can see, it works just fine.

Thanks for reading ❤

If you liked this post, share it with all of your programming buddies!