Create a REST API in Node.js with MongoDB on Alibaba Cloud

By Alex Mungai Muchiri, Alibaba Cloud Tech Share Author. Tech Share is Alibaba Cloud’s incentive program to encourage the sharing of technical knowledge and best practices within the cloud community.

MongoDB is designed to scale and offers superior performance compared to conventional relational databases. Specifically, the NoSQL model on which Mongo is based solves some very important challenges associated with SQL schemas. They include:

  1. Impeccable handling of large sets of unstructured, structured, or rapidly-changing data structures.
  2. It is agile and allows iteration of schemas quickly.
  3. Perfectly-suited for flexible object-oriented programming.
  4. It is easy to scale-out across geographically distributed systems.

Accordingly, choosing MongoDB as the backend for your mobile application is a wise consideration. Notably, scaling with the number of users is important, and so is being able to iterate the database to accept varying data structures. Rather than using rows and columns, MongoDB stores data in documents thus allowing room for structural changes. On the whole, Mongo scalability is three-tier:

  1. Cluster Scale: database distribution across hundreds of nodes across data centers.
  2. Performance: perform more than 100,000+ R/W ops per second with strict latency
  3. Data scale: sustain more than 1 billion documents

In this article, we are going to explore how to create a powerful REST API in Node.js with MongoDB. Server configuration with Mongo is quite easy, all you need is have your Alibaba Cloud Elastic Compute Service (ECS) instance ready for work. In our case, we will be using Ubuntu 16.04 as our operating system.

Install MongoDB on Alibaba Cloud ECS

Installing MongoDB is simple:

Package NameDescriptionmongodb-orgA metapackage that will automatically install the four component packages listed below.mongodb-org-serverContains the mongod daemon and associated configuration and init scripts.mongodb-org-mongosContains the mongos daemon.mongodb-org-shellContains the mongo shell.mongodb-org-toolsContains the following MongoDB tools: mongoimport bsondump, mongodump, mongoexport, mongofiles, mongorestore, mongostat, and mongotop.

The mongodb-org-server package provides an initialization script that starts mongod with the /etc/mongod.conf configuration file.

Use this link and follow the instructions to install MongoDB on ECS Ubuntu Server: https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/

After installation, we can now proceed to build our simple REST API

REST APIs

REST is an acronym for Representational State Transfer. REST APIs are very popular these days and are used to handle the server-side of both web and mobile applications. In fact, most large internet companies have at least deployed some REST APIs such as the Google calendar API. Once a database is built, you can use APIs to deliver data and content to applications with ease. They represent how a server responds to and accepts requests to allow for CRUD processes. This tutorial involves building a simple API to post questions, post answers, as well as vote and remove answers on the scalable MongoDB. Key to a successful implementation of the project is structuring the routes correctly. In that case, routes will be programmed to:

  1. Analyze posted questions
  2. Read, create, edit and remove answers
  3. Upvote or downvote answers

In this example, the following will be applied.

Development environment

This project uses the following tools:

  1. Plain JavaScript
  2. Node.js
  3. Express (JS framework)
  4. MongoDB (Database)
  5. Yarn (package management)
  6. Visual Studio Code as editor
  7. Postman (to test API endpoints)

Dependencies

The dependency packages used:

  1. Parser (parses the body of incoming endpoint requests)
  2. Express (activates the application)
  3. Nodemon (restarts server to effect changes)
  4. Mongoose (simplifies MongoDB interactions through object data models)
  5. Morgan (a middleware to log HTTP requests)
  6. Eslint with Airbnb (it is an extension to help creation of high quality code)

Summary of the Tutorial

This example involves the following tasks:

  1. Using express to create API routes
  2. Modelling API data
  3. Contacting MongoDB using Mongoose
  4. Cleaning up and testing the API using Postman

Building API routes Using Express

The Basics

To make development simple, install nodemon and add the script to the package.json package

  1. Create a basic web server using express (express has a tutorial for this process)
  2. Ensure that the express middleware is set to be flexible
  3. Include a body parser to handle requests
  4. Incorporate the parser in the express middleware

Creating Routes for Questions

  1. In the server, create a file to store routes
  2. Include both POST and GET routes to find questions and add new
  3. For specific questions, create a GET route.
router.get('/', (req, res) => {
res.json({ response: 'a GET request for LOOKING at questions' });
});
router.post('/', (req, res) => {
res.json({
response: 'a POST request for CREATING questions',
body: req.body
});
});
router.get('/:qID', (req, res) => {
res.json({
response: `a GET request for LOOKING at a special answer id: ${req.params.qID}`
});
});

Answer Routes

  1. The first step is to install the Morgan HTTP requests logger. The package will help to analyze requests for you.
  2. Next, create a POST route to accept answers.
  3. Next, include PUT and DELETE to allow editing and deleting of answers
  4. Create a POST route for upvoting and downvoting answers (we shall use a simple vote in this tutorial)
router.post('/:qID/answers', (req, res) => {
res.json({
response: 'a POST request for CREATING answers',
question: req.params.qID,
body: req.body
});
});
router.put('/:qID/answers/:aID', (req, res) => {
res.json({
response: 'a PUT request for EDITING answers',
question: req.params.qID,
answer: req.params.aID,
body: req.body
});
});
router.delete('/:qID/answers/:aID', (req, res) => {
res.json({
response: 'a DELETE request for DELETING answers',
question: req.params.qID,
answer: req.params.aID,
body: req.body
});
});
router.post('/:qID/answers/:aID/vote-:dec', (req, res) => {
res.json({
response: 'a POST request for VOTING on answers',
question: req.params.qID,
answer: req.params.aID,
vote: req.params.dec,
body: req.body
});
});

Error Handlers

It is almost impossible to avoid errors in any application. In this example, we shall set up the error handlers in the following steps:

  1. The middleware will act as our ‘error-catcher’.
  2. After catching error 404, use the middleware to pass it to a custom handler that responds with JSON response format (otherwise, use the 500 as shown below)
  3. Set up a validation middleware to allow error upvoting or downvoting.
app.use((req, res, next) => {
const err = new Error('Not Found');
err.status = 404;
next(err);
});
app.use((err, req, res, next) => {
res.status(err.status || 500);
res.json({
error: {
message: err.message
}
});
});

Use Mongoose to Connect to MongoDB

Choose the Data Model for the API

It is very critical that the data types chosen for storage in MongoDB have the right structures and relationships expressed clearly. Mongoose will act as our gateway to the MongoDB. We shall use it to create schemas that will accept JSON data format. The best approach for our case is to use question objects that have answer properties. Nonetheless, it is noteworthy to have in mind that Mongo documents have a maximum number of storage units and thus answers to a question are not unlimited.

Create Schemas

  1. Depending on your chosen parent-children structure, create appropriate schema on Mongoose
  2. Use your schemas to build a model
const AnswerSchema = new Schema({
text: String,
createdAt: { type: Date, default: Date.now },
updatedAt: { type: Date, default: Date.now },
votes: { type: Number, default: 0 }
});
const QuestionSchema = new Schema({
text: String,
createdAt: { type: Date, default: Date.now },
answers: [AnswerSchema]
});
const Question = mongoose.model('Question', QuestionSchema);

Incorporate Sorting and Voting into the Function

  1. Priority is given to the newest answers
  2. Include a storage option for votes in the answer schema
  3. Instruct Mongoose to pre-sort all answers while saving
  4. Use the parent method to have answers referenced to the question parent document
  5. Avoid using arrow functions in the specification
const sortAnswers = (a, b) => {
if (a.votes === b.votes) {
return b.updatedAt - a.updatedAt;
}
return b.votes - a.votes;
};
QuestionSchema.pre('save', function (next) {
this.answers.sort(sortAnswers);
next();
});
AnswerSchema.method('update', function (updates, callback) {
Object.assign(this, updates, { updatedAt: new Date() });
this.parent().save(callback);
});
AnswerSchema.method('vote', function (vote, callback) {
if (vote === 'up') {
this.votes += 1;
} else {
this.votes -= 1;
}
this.parent().save(callback);
});

Link the API to MongoDB

You are about to tackle the most difficult challenge in my opinion. Take a good look at the Mongoose documents at this juncture.

Error Handling

  1. In this case, we shall use the param method to have callbacks for qID and aID routes.
  2. The method allows error identification when a question or answer is not available in the database.
router.param('qID', (req, res, next, id) => {
Question.findById(id, (err, doc) => {
if (err) return next(err);
if (!doc) {
err = new Error('Document not found');
err.status = 404;
return next(err);
}
req.question = doc;
return next();
});
});
router.param('aID', (req, res, next, id) => {
req.answer = req.question.answers.id(id);
if (!req.answer) {
err = new Error('Answer not found');
err.status = 404;
return next(err);
}
return next();
});

Getting the Questions Routes Ready

  1. This will find questions in your Mongo database using a GET route.
  2. It will return the appropriate questions.
  3. It will also post new questions in JSON format to the database using a POST route.
  4. Include a GET route for a single question.
router.get('/', (req, res, next) => {
Question.find({}).sort({ createdAt: -1 }).exec((err, questions) => {
if (err) return next(err);
res.json(questions);
});
});
router.post('/', (req, res) => {
const question = new Question(req.body);
question.save((err, question) => {
if (err) return next(err);
res.status(201);
res.json(question);
});
});
router.get('/:qID', (req, res) => {
res.json(req.question);
});

Routes to Update Answers

  1. The POST execution to create answers is straightforward: the method involves pushing the answer to the question document and saving it as JSON format.
  2. User the update method to allow answer updating by returning the new JSON input.
  3. Accordingly, use the remove method when using the DELETE route to remove answers.
  4. For voting, we use a POST route embedded on the middleware to save votes in the vote method.

The example below illustrates this operation:

router.post('/:qID/answers', (req, res, next) => {
req.question.answers.push(req.body);
req.question.save((err, question) => {
if (err) return next(err);
res.status(201);
res.json(question);
});
});
router.put('/:qID/answers/:aID', (req, res, next) => {
req.answer.update(req.body, (err, result) => {
if (err) return next(err);
res.json(result);
});
});
router.delete('/:qID/answers/:aID', (req, res) => {
req.answer.remove(err => {
req.question.save((err, question) => {
if (err) return next(err);
res.json(question);
});
});
});
router.post(
'/:qID/answers/:aID/vote-:dec',
(req, res, next) => {
if (req.params.dec.search(/^(up|down)$/) === -1) {
const err = new Error(`Not possible to vot for ${req.params.dec}!`);
err.status = 404;
next(err);
} else {
req.vote = req.params.dec;
next();
}
},
(req, res, next) => {
req.answer.vote(req.vote, (err, question) => {
if (err) return next(err);
res.json(question);
});
}
);

Great! Now our REST API looks all set for consumption. We are now going to get into the next phase

Cleaning Up and Testing the Endpoints

Testing the API

The simplest way to test the functionality of all endpoints is to use Postman. You can use a Chrome extension or download the desktop application. Whatever your choice, that’s fine, I personally like the Chrome extension. Postman is simple to use and allows HTTP tests. You can also set up an automated test if you don’t want to do it manually.

Cross-Origin Resource Sharing

CORS has been restricted for security reasons. Basically, the method allows domain resource access by a browser. Consequently, we need to have a middleware to allow API consumption by domains involving the following steps:

  1. The header should have access to all origins
  2. Enable HTTP requests
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept'
);
if (req.method === 'Options') {
res.header('Access-Control-Allow-Methods', 'PUT, POST, DELETE');
return res.status(200).json({});
}
});

Get the Front-end Working

Your API is all set to connect with all types of front ends. The process is as simple as using the proper header and route to post, get, put or delete.

Conclusion

REST API is a fantastic tool that can be used to setup very scalable back-end microservices. We have used Mongoose in this project to implement a simple API on MongoDB, a scalable open source database. Get your mobile application running on MongoDB with Alibaba Cloud. I hope you enjoyed this article.

Reference:

https://www.alibabacloud.com/blog/create-a-rest-api-in-node-js-with-mongodb-on-alibaba-cloud_593851?spm=a2c41.11826438.0.0