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:
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:
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.
Installing MongoDB is simple:
Package Name | Description |
mongodb-org | A metapackage that will automatically install the four component packages listed below. |
mongodb-org-server | Contains the mongod daemon and associated configuration and init scripts. |
mongodb-org-mongos | Contains the mongos daemon. |
mongodb-org-shell | Contains the mongo shell. |
mongodb-org-tools | Contains 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 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:
In this example, the following will be applied.
This project uses the following tools:
The dependency packages used:
This example involves the following tasks:
To make development simple, install nodemon and add the script to the package.json package
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}`
});
});
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
});
});
It is almost impossible to avoid errors in any application. In this example, we shall set up the error handlers in the following steps:
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
}
});
});
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.
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);
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);
});
You are about to tackle the most difficult challenge in my opinion. Take a good look at the Mongoose documents at this juncture.
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();
});
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);
});
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
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.
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:
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({});
}
});
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.
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.
Alibaba Clouder - May 5, 2019
Alibaba Clouder - March 29, 2019
Alibaba Clouder - January 4, 2019
Alibaba Clouder - August 13, 2020
Alibaba Clouder - June 13, 2018
Alibaba Clouder - March 29, 2019
A secure, reliable, and elastically scalable cloud database service for automatic monitoring, backup, and recovery by time point
Learn MoreOpenAPI Explorer allows you to call an API through its web interface or WebCLI, and view the entire process.
Learn MoreAPI Gateway provides you with high-performance and high-availability API hosting services to deploy and release your APIs on Alibaba Cloud products.
Learn MoreLeverage cloud-native database solutions dedicated for FinTech.
Learn MoreMore Posts by Alex