How to set-up a powerful API with Nodejs, GraphQL, MongoDB, Hapi, and Swagger

Separating your frontend and backend has many advantages:

Separating your frontend and backend has many advantages:

  • The biggest reason why reusable APIs are popular — APIs allow you to consume data from a web client, mobile app, desktop app — any client really.
  • Separation of concerns. Long gone are the days where you have one monolithic-like app where everything is bundled together. Imagine you have an extremely convoluted application. Your only option is to hire extremely experienced/senior developers due to the natural complexity.

I’m all for hiring juniors and training your staff, and that’s exactly why you should separate concerns. With separation of concerns, you can reduce the complexity of your application by splitting responsibilities into “micro-services” where each team is specialized in their micro-service.

As mentioned above, the on-boarding/ramp-up process is much quicker thanks to splitting up responsibilities (backend team, frontend team, dev ops team, and so on)


Forward thinking and getting started

We will be building a very powerful, yet flexible, GraphQL API based on Nodejs with Swagger documentation powered by MongoDB.

The main backbone of our API will be Hapi.js. We will go over all the technology in substantial detail.

At the very end, we will have a very powerful GraphQL API with great documentation.

The cherry on top will be our integration with the client (React, Vue, Angular)


Prerequisites

  • NodeJS installed
  • Basic JavaScript
  • Terminal (any will do, preferably bash-based)
  • Text editor (any will do)
  • MongoDB (install instructions here) — Mac: brew install mongodb

Let’s goo!

Open the terminal and create the project. Inside the project directory we initialize a Node project.

Creating our project

Next, we want to setup our Hapi server, so let’s install the dependencies. You can either use Yarn or NPM.

yarn add hapi nodemon

Before we go on, let’s talk about what hapi.js is and what it can do for us.

hapi enables developers to focus on writing reusable application logic instead of spending time building infrastructure.

Instead of going with Express, we are going with Hapi. In a nutshell, Hapi is a Node framework. The reason why I chose Hapi is rather simple — simplicity and flexibility over boilerplate code_._

Hapi enables us to build our API in a very rapid manner.

Optional: check out this quick crash course on hapi.js:

The second dependency we installed was the good-ole nodemon. Nodemon restarts our server automatically whenever we make changes. It speeds up our development by a big factor.

Let’s open our project with a text editor. I chose Visual Studio Code.

Setting up a Hapi server is very straightforward. Create a index.js file at the root directory with the contents of the following:

  • We require the hapi dependency
  • Secondly, we make a constant called server which creates a new instance of our Hapi server — as the arguments, we pass an object with the port and host options.
  • Third and finally, we create an asynchronous expression called init. Inside the init method, we have another asynchronous method which starts the server. See server.start() — at the bottom we call the init()function.

If you’re unsure about async await — watch this:

Now, if we head over to [http://localhost:4000](http://localhost:4000) we should see the following:

Which is perfectly fine, since the Hapi server expects a route and a handler. More on that in a second.

Let’s quickly add the script to run our server with nodemon. Open package.json and edit the scripts section.

Now we can do the following 😎


Routing

Routing is very intuitive with Hapi. Let’s say you hit / — what would you expect to happen? There are three main components in play here.

  • What’s the path? — path
  • What’s the HTTP method? Is it a GET — POST or something else? — method
  • What will happen if that route is reached? — handler

Inside the init method we attached a new method to our server called route with options passed as our argument.

If we refresh our page we should see return value of our root handler

Well done, but there is so much more we can do!


Setting up our database

Right, next up we are going to setup our database. We’re going to use mongodb with mongoose.

Let’s face it, writing MongoDB validation, casting and business logic boilerplate is a drag. That’s why we wrote Mongoose.

The next final ingredient related to our database is mlab. Instead of running mongo on our local computer, we are gonna use a cloud provider like mlab.

The reason why I chose mlab is because of the free plan (useful for prototyping) and how simple it is to use. There are more alternatives out there, and I encourage you to explore all of them ❤

Head over to https://mlab.com/ and signup.

Let’s create our database.

And finally create a user for the database. That will be all we will be editing on mlab.


Connecting mongoose with mlab

Open index.js and add the following lines and credentials. We are basically just telling mongoose which database we want to connect. Make sure to use your credentials.

If you want to brush up your MongoDB skills, here’s a solid series.

If everything went according the plan, we should see ‘connected to database’ in the console.


Wohoo!

Good job! Take a quick break and grab some coffee, we are almost ready to dive into the “cool parts”.


Creating Models

With mongoDB, we follow the convention of models. In other words — data modeling.

It’s a relatively simple concept which you will be able to grasp. Basically we just declare our schema for collections. Think of collections as tables in an SQL database.

Let’s create a directory called models. Inside we will create a file Painting.js

Painting.js is our painting model. It will hold all data related to paintings. Here’s how it will look:

  • We require the mongoose dependency.
  • We declare our PaintingSchema by calling the mongoose schema constructor and passing in the options. Notice how it’s strongly typed: for example the name field can consist of a string, and techniques consists of an array of strings.
  • We export the model and name it Painting

Let’s fetch all of our paintings from the database

First we need to import the Painting model to index.js


Adding new routes

Ideally, we want to have URL endpoints reflecting our actions.

such as /api/v1/paintings — /api/v1/paintings/{id} — and so on.

Let’s start off with a GET and POST route. GET fetches all the paintings and POST adds a new painting.

Notice we modified the route to be an array of objects instead a single object. Also, arrow functions 😊

  • We created a GET for [/api/v1/paintings](http://localhost:4000/api/v1/paintings) path. Inside the handler we are calling the mongoose schema. Mongoose has built-in methods — the handy method we are using is find() which returns all paintings since we’re not passing in any conditions to find by. Therefore it returns all records.
  • We also created a POST for the same path. The reason for that is we’re following REST conventions. Let’s deconstruct (pun intended) the route handler — remember in our Painting schema we declared three fields: name — url — techniques 
  • Here we are just accepting those arguments from the request (we will be doing that with postman in a sec) and passing the request arguments to our mongoose schema. After we’re done passing arguments, we call the save() method on our new record, which saves it to the mlab database.

If we head over to [http://localhost:4000**/api/v1/paintings**](http://localhost:4000/api/v1/paintings) we should see an empty array.

Why empty? Well we haven’t added any paintings just yet. Let’s do that now!

Install postman, it’s available for all platforms.

After installation, open postman.

  • On the left you can see the method options. Change that to POST
  • Next to the POST method we have the URL. That’s the URL we want to send our method to.
  • On the right you can see blue button which sends the request.
  • Below the URL bar we have the options. Click on the body and fill in the fields like in the example.
{
  "name": "Mona Lisa",
  "url": "https://en.wikipedia.org/wiki/Mona_Lisa#/media/File:Mona_Lisa,_by_Leonardo_da_Vinci,_from_C2RMF_retouched.jpg",
  "techniques": ["Portrait"]
}

POST paintings

Alright. Good to go! Let’s open [http://localhost:4000/api/v1/paintings](http://localhost:4000/api/v1/paintings)

Excellent! We still have some way to go! Next up — GraphQL!


Here’s the source code just in case anyone needs it :-)


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.

Building A GraphQL API With Nodejs And MongoDB

Building A GraphQL API With Nodejs And MongoDB

In this tutorial, you’ll build and deploy a GraphQL server with Node.js that can query and mutate data from a MongoDB database that is running on Ubuntu 18.04.

While REST APIs are amongst the most popular when it comes to client consumption, they are not the only way to consume data and they aren’t always the best way. For example, having to deal with many endpoints or endpoints that return massive amounts of data that you don’t need are common. This is where GraphQL comes in.

With GraphQL you can query your API in the same sense that you would query a database. You write a query, define the data you want returned, and you get what you requested. Nothing more, nothing less. I actually had the opportunity to interview the co-creator of GraphQL on my podcast in an episode titled, GraphQL for API Development, and in that episode we discuss GraphQL at a high level.

You might remember that I wrote a tutorial titled, Getting Started with GraphQL Development Using Node.js which focused on mock data and no database. This time around we’re going to take a look at including MongoDB as our NoSQL data layer.

A few assumptions are going to be made going forward. I’m going to assume that you already have access to a MongoDB instance. If you don’t and need help getting MongoDB setup, you might check out my tutorial titled, Getting Started with MongoDB as a Docker Container Deployment. The other assumption is that you have Node.js installed and configured. While this tutorial will be a working example, if you want to get into more depth with GraphQL, I suggest you check out my eBook and video course titled Web Services for the JavaScript Developer.

Creating a New Node.js Project for GraphQL Development

The first step in this tutorial will be to create a new project with the dependencies and boilerplate GraphQL code. From the command line, execute the following commands:

npm init -y
npm install express express-graphql graphql mongoose --save

The above commands will create a new package.json file and install our dependencies. The backbone of this project will use Express.js, which is also commonly used for RESTful APIs. However, we’ll be processing GraphQL requests and connecting them to MongoDB with the Mongoose ODM.

The next step is to create an app.js file and include the following boilerplate JavaScript code:

const Express = require("express");
const ExpressGraphQL = require("express-graphql");
const Mongoose = require("mongoose");
const {
    GraphQLID,
    GraphQLString,
    GraphQLList,
    GraphQLNonNull,
    GraphQLObjectType,
    GraphQLSchema
} = require("graphql");

var app = Express();

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

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

const PersonType = new GraphQLObjectType({
name: "Person",
fields: {
id: { type: GraphQLID },
firstname: { type: GraphQLString },
lastname: { type: GraphQLString }
}
});

const schema = new GraphQLSchema({});

app.use("/graphql", ExpressGraphQL({
schema: schema,
graphiql: true
}));

app.listen(3000, () => {
console.log("Listening at :3000...");
});

In the above code we are importing our dependencies, initializing Express.js, and connecting to our MongoDB instance with Mongoose. We’re planning to use a very simple Mongoose model which will also be seen in our GraphQL model.

Rather than reiterating the content that was discussed in the previous tutorial, we’re going to focus primarily on the schema in our project which will contain queries and mutations.

Designing GraphQL Queries for Retrieving MongoDB NoSQL Data

When it comes to our schema, there will be queries for retrieving data and mutations for creating, updating, or deleting data. We’re going to start by designing our queries which will retrieve data from MongoDB.

Take a look at the following queries:

const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: "Query",
fields: {
people: {
type: GraphQLList(PersonType),
resolve: (root, args, context, info) => {
return PersonModel.find().exec();
}
},
person: {
type: PersonType,
args: {
id: { type: GraphQLNonNull(GraphQLID) }
},
resolve: (root, args, context, info) => {
return PersonModel.findById(args.id).exec();
}
}
}
})
});

In the above code we have a people query as well as a person query. One will retrieve multiple documents and the other will retrieve a single document. When we wish to query for multiple documents, we specify we want to return a GraphQLList of the PersonType that we had created. This PersonType maps to our document model. We can simply do a find for all documents within our people MongoDB collection.

The person query is similar, but not quite the same. In the person query we accept an argument which must be present. With that argument we can use the findById function and return the result.

We didn’t need to specify the document properties because they are already conveniently mapped to our model because of our naming conventions. If we wanted to use different names, we could specify the properties in the find operation.

If we wanted to query for our data from a client facing application, we could run something like this:

{
people {
id,
firstname,
lastname
}
person(id: "123") {
firstname
}
}

You can try to run this query by navigating to http://localhost:3000/graphql in your web browser because we have GraphiQL enabled for troubleshooting.

Designing GraphQL Mutations for Creating, Changing, or Deleting MongoDB Documents

Now that we can query for documents, we probably want a way to create documents. Mutations are designed nearly the same as queries, but they are executed differently in the client facing application.

Within your schema we can modify it to the following:

const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: "Query",
fields: {
people: {
type: GraphQLList(PersonType),
resolve: (root, args, context, info) => {
return PersonModel.find().exec();
}
},
person: {
type: PersonType,
args: {
id: { type: GraphQLNonNull(GraphQLID) }
},
resolve: (root, args, context, info) => {
return PersonModel.findById(args.id).exec();
}
}
}
}),
mutation: new GraphQLObjectType({
name: "Mutation",
fields: {
person: {
type: PersonType,
args: {
firstname: { type: GraphQLNonNull(GraphQLString) },
lastname: { type: GraphQLNonNull(GraphQLString) }
},
resolve: (root, args, context, info) => {
var person = new PersonModel(args);
return person.save();
}
}
}
})
});

Notice that now we have a mutation and not just a query. For the mutation, we are requiring two arguments to exist and we are using them in the resolve function. Using the arguments, without further data validation, we create a new model instance and save it to the database. The results are returned to the client that executed the mutation.

To try this mutation, the following could be executed:

mutation CreatePerson($firstname: String!, $lastname: String!) {
person(firstname: $firstname, lastname: $lastname) {
id,
firstname,
lastname
}
}

The above code would make sense in GraphiQL. The variables would be populated with another part of the GraphiQL application. Various frameworks will have different expectations when it comes to executing mutations, but the same idea applies.

Conclusion

You just saw how to use MongoDB as the NoSQL database in your GraphQL web application. GraphQL can be used in combination with REST or as an alternative depending on your business needs. If you’d like to know how to create a RESTful application with MongoDB, you might want to check out my previous tutorial titled, Building a REST API with MongoDB, Mongoose, and Node.js.

This example was quite simple and a lot more can be accomplished with GraphQL.

Thanks For Visiting, Keep Visiting. If you liked this post, share it with all of your programming buddies!

Building A REST API With MongoDB, Mongoose, And Node.js

Building A REST API With MongoDB, Mongoose, And Node.js

In this tutorial, we build A REST API With MongoDB, Mongoose, And Node.js .... Mongoose is Nodejs package for modeling Mongodb.

In this tutorial, we build A REST API With MongoDB, Mongoose, And Node.js .... Mongoose is Nodejs package for modeling Mongodb.

About a week or so ago I had written a tutorial titled, Getting Started with MongoDB as a Docker Container Deployment, which focused on the deployment of MongoDB. In that tutorial we saw how to interact with the MongoDB instance using the shell client, but what if we wanted to actually develop a web application with MongoDB as our NoSQL database?

In this tutorial we’re going to see how to develop a REST API with create, retrieve, update, and delete (CRUD) endpoints using Node.js and the very popular Mongoose object document modeler (ODM) to interact with MongoDB.

Before we get too invested in this tutorial, I wanted to point out that the focus of this tutorial won’t be around installing, configuring, or deploying MongoDB instances. I recommend you take a look at my previous tutorial if you need help with that. The assumption is that your MongoDB database is ready to go.

Configuring Node.js with Express Framework

To start things off, let’s go ahead and create a fresh Node.js project with the appropriate dependencies. From the command line, execute the following:

npm init -y
npm install express body-parser mongoose --save


The above commands will create a new package.json file and install Express.js, Mongoose, and a package that will allow us to pass JSON data around in our requests.

For simplicity, we’re going to keep all of our code within a single JavaScript file. Create an app.js file at the root of your project and include the following boilerplate code:

const Express = require("express");
const Mongoose = require("mongoose");
const BodyParser = require("body-parser");

var app = Express();

app.use(BodyParser.json());
app.use(BodyParser.urlencoded({ extended: true }));

app.post("/person", async (request, response) => {});
app.get("/people", async (request, response) => {});
app.get("/person/:id", async (request, response) => {});
app.put("/person/:id", async (request, response) => {});
app.delete("/person/:id", async (request, response) => {});

app.listen(3000, () => {
    console.log("Listening at :3000...");
});


In the above code we are doing a few things. First we are importing each of the dependencies that we had previously downloaded when creating our project. Next we are initializing Express Framework and configuring the body-parser package so we can receive JSON data in our payloads.

The API we develop will be create, retrieve, update, and delete (CRUD) oriented hence the five endpoint functions that are prepared. We’ll be adding the MongoDB and Mongoose logic to each of these endpoint functions.

Finally, we are listening for requests to our application on port 3000.

Interacting with MongoDB using the Mongoose ODM

With the foundation of our REST API in place, we can focus on the database logic. Within the project’s app.js file include the following near where you initialized Express Framework:

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


The above line will connect to our MongoDB instance running on localhost and it will either use or create a thepolyglotdeveloper database. If you followed the Docker example, the database will have already been created. If you’re not using localhost, change it up as necessary.

After the connection information is in place, we can define our document models. This particular example will only have a single document model that looks like the following:

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


Our model is person which will create a people collection within our database. Each document in our collection will have the firstname and lastname properties. The model can be significantly more complex than what we have here.

So let’s take a look at each of our endpoints, starting with the creation of data:

app.post("/person", async (request, response) => {
    try {
        var person = new PersonModel(request.body);
        var result = await person.save();
        response.send(result);
    } catch (error) {
        response.status(500).send(error);
    }
});


When the client makes a POST request to our endpoint, we can use our PersonModel and the JSON payload provided to save into the database. Only the most basic of data validation is happening on our JSON payload and rather than using promises directly, we are using async and await, which in my opinion is a little cleaner. If the save is successful, we return the data back to the client facing application.

Now that we have data in our database, we can try to retrieve it:

app.get("/people", async (request, response) => {
    try {
        var result = await PersonModel.find().exec();
        response.send(result);
    } catch (error) {
        response.status(500).send(error);
    }
});


There are two different retrievals that can happen. We can retrieve everything, or we can retrieve something in particular. The first retrieval that we are looking at is the everything scenario. Using a find with no properties will get all data for a given collection, which in our scenario is the people collection. That data is then returned to the client facing application.

On the other side of things, we can try to retrieve a single document based on its stored id value:

app.get("/person/:id", async (request, response) => {
    try {
        var person = await PersonModel.findById(request.params.id).exec();
        response.send(person);
    } catch (error) {
        response.status(500).send(error);
    }
});


Given an id value defined by the client facing application, we can call the findById function rather than the find function. This function will find a single document based on the associated id. When we have this data we will return it back to the user.

Not bad so far, right?

Let’s finish this simple API with an update and a delete endpoint. Starting with an update, we can do the following:

app.put("/person/:id", async (request, response) => {
    try {
        var person = await PersonModel.findById(request.params.id).exec();
        person.set(request.body);
        var result = await person.save();
        response.send(result);
    } catch (error) {
        response.status(500).send(error);
    }
});


When the client provides an id value, we can first find the document by the id. Once we’ve found the document, we can set the properties that were provided in the request payload. Again, basic validation is performed. For example, if a property is provided that doesn’t appear in our model, it will be stripped out. In our model, none of the properties are required. This means whatever data appears in the payload, as long as it is valid, it will override what already exists. We can save any changes we made back to the database and return the data back to the client.

Our final endpoint for this example is the delete endpoint:

app.delete("/person/:id", async (request, response) => {
    try {
        var result = await PersonModel.deleteOne({ _id: request.params.id }).exec();
        response.send(result);
    } catch (error) {
        response.status(500).send(error);
    }
});


Again, we are expecting an id to be provided from the client facing application. When we have an id, we can use it in the deleteOne function and the document will be removed from the database.

If you run your application, you can play around with it using a tool like Postman or similar.

Conclusion

You just saw how to build a simple RESTful API using popular technologies such as Node.js, JavaScript, and MongoDB as the NoSQL database. If you’ve been following the blog, you might remember I did something similar in a tutorial titled, Developing a RESTful API with Node.js and MongoDB Atlas. With Atlas, we built a REST API, but it was with a cloud deployment of MongoDB.

If you’re interested in learning more about RESTful API development, I encourage you to check out my eBook and video course titled, Web Services for the JavaScript Developer, as it goes into significantly more depth.

A video version of this article can be found below.