JavaScript is considered the top web development language today, running in millions of applications and websites. One of the building blocks for the JavaScript stack, on many of its variations, is REST API-based communication between the front-end and back-end tiers.
A popular way of implementing the REST API approach uses Express JS as the back-end web server and MongoDB as the document store. This concept easily maps MongoDB’s document model to the REST API payloads which are JSON by nature.
This article will provide a step-by-step tutorial on how to use Express with MongoDB Atlas, our Database-as-a-Service platform, to expose restful API endpoints for our clients.
Table of Contents
You can explore the full project in the following GitHub repo:
Express allows us to create a back-end middle tier running Node.js server exposing REST API routes to our application.
The Express.js server also connects to the MongoDB Atlas cluster via the Node.js Driver. If you wish to learn more about MongoDB and Node.js, read the following article.
Finally, our front-end application will be written in React to use the REST API endpoints hosted on the Express.js server. The application is a Tinder-like application for the sample_airbnb database, containing information on various listings, available as part of the sample datasets you can load into the Atlas cluster.
Users can swipe the listing cards to save or drop them and press the “like” button to add likes. Additionally, a double click will show details about the specific listing card presented.
Here are the main files in the project:
The “server” directory hosts the Express.js server application and all of its dependencies. The main files here are:
The “app/listings” directory is where the front-end React application code resides. The main files here are:
First, you will need to deploy an Atlas cluster. You can follow the Getting Started with Atlas guide to learn how to create a free Atlas account, create your first cluster, and get your connection string to the database.
Once we have the Atlas cluster available, we can load the sample data by clicking [...] > “Load Sample Data.” When data is loaded, we are ready to clone the project stub branch:
git clone -b stub
git@github.com:mongodb-developer/mongodb-express-rest-api-example.git
Let’s go to the “server” directory of the project and install the needed packages:
cd mongodb-express-rest-api-example/server
npm install
Now, we are ready to connect our Express server to the MongoDB Atlas Cluster.
Once you locate your connection string, create a config.env file in the server directory. There, assign a new ATLAS_URI variable the value of the connection string. Replace the <username>
and the <password>
with your database username and password. Once done, your file should look similar to the one below.
ATLAS_URI=mongodb+srv://<username>:<password>@sandbox.jadwj.mongodb.net/myFirstDatabase?retryWrites=true&w=majority
Next, open server/db/conn.js and add the implementation of the connectToServer function from the snippet below.
const { MongoClient } = require("mongodb");
const connectionString = process.env.ATLAS_URI;
const client = new MongoClient(connectionString, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
let dbConnection;
module.exports = {
connectToServer: function (callback) {
client.connect(function (err, db) {
if (err || !db) {
return callback(err);
}
dbConnection = db.db("sample_airbnb");
console.log("Successfully connected to MongoDB.");
return callback();
});
},
getDb: function () {
return dbConnection;
},
};
The main object this module exports out is the _db
variable, which will hold the "sample_airbnb
" database-level object. Via this object, we will be able to access any collection within that database or change its context to another database. In this tutorial, we will use only a single database named “sample_airbnb
.”
The main Express.js part in this tutorial is to expose REST API routes to perform Read, Create, Update, and Delete operations for our listing application. This can be extended for more complex application business logic as your use case requires.
The file that will host the routes is --- “server/routes/record.js”. It uses the Express Router feature:
const express = require("express");
// recordRoutes is an instance of the express router.
// We use it to define our routes.
// The router will be added as a middleware and will take control of requests starting with path /listings.
const recordRoutes = express.Router();
The Read route will be used when the /listings
path on a GET method is called. It will use a collection.find() to query our listingAndReviews
collection for the first 50 available listings:
// This section will help you get a list of all the documents.
recordRoutes.route("/listings").get(async function (req, res) {
const dbConnect = dbo.getDb();
dbConnect
.collection("listingsAndReviews")
.find({}).limit(50)
.toArray(function (err, result) {
if (err) {
res.status(400).send("Error fetching listings!");
} else {
res.json(result);
}
});
});
The code sends back the result set as the API response.
The Create route will record a “match” swipe in a “matches” collection. The body of this POST method will present a user session_id
and the swiped direction and listing_id
to create a “match” document.
// This section will help you create a new document.
recordRoutes.route("/listings/recordSwipe").post(function (req, res) {
const dbConnect = dbo.getDb();
const matchDocument = {
listing_id: req.body.id,
last_modified: new Date(),
session_id: req.body.session_id,
direction: req.body.direction
};
dbConnect
.collection("matches")
.insertOne(matchDocument, function (err, result) {
if (err) {
res.status(400).send("Error inserting matches!");
} else {
console.log(`Added a new match with id ${result.insertedId}`);
res.status(204).send();
}
});
});
The save is done via collection.insertOne() method with the prebuilt “matchDocument.”
You can also use InsertMany to insert multiple documents at once.
The Update route updates the “likes” field on a listing object. This is done via a POST method:
// This section will help you update a document by id.
recordRoutes.route("/listings/updateLike").post(function (req, res) {
const dbConnect = dbo.getDb();
const listingQuery = { _id: req.body.id };
const updates = {
$inc: {
likes: 1
}
};
dbConnect
.collection("listingsAndReviews")
.updateOne(listingQuery, updates, function (err, _result) {
if (err) {
res.status(400).send(`Error updating likes on listing with id ${listingQuery.id}!`);
} else {
console.log("1 document updated");
}
});
});
The method will use the collection.updateOne() method with a $inc on the “like” field to increment the likes.
Whenever a listing is dropped, we can delete it from the database so that it doesn’t appear anymore. This is done via the Delete route.
// This section will help you delete a record.
recordRoutes.route("/listings/delete/:id").delete((req, res) => {
const dbConnect = dbo.getDb();
const listingQuery = { listing_id: req.body.id };
dbConnect
.collection("listingsAndReviews")
.deleteOne(listingQuery, function (err, _result) {
if (err) {
res.status(400).send(`Error deleting listing with id ${listingQuery.listing_id}!`);
} else {
console.log("1 document deleted");
}
});
});
The route here includes the :id
parameter. This is the id of the listing to be deleted via collection.deleteOne().
Now that we have everything in place, we can launch the server:
npm start
[nodemon] starting `node server.js`
Successfully connected to MongoDB.
Server is running on port: 5000
Our React application consists mainly of the App.js React file and class.
import './App.css';
import TinderCard from 'react-tinder-card'
import axios from 'axios';
import { Component } from 'react';
import { v4 as uuid } from 'uuid';
class App extends Component {
constructor() {
super();
this.state = {
data: [],
session_id: uuid(),
liked: false
};
this.handleClick = this.handleClick.bind(this);
this.showDetails = this.showDetails.bind(this);
}
async onSwipe(direction, listingId, sessionId) {
this.setState({
liked: false
});
if (direction === "left") {
await axios.delete(`http://localhost:5000/listings/delete/${listingId}`)
} else {
await axios.post("http://localhost:5000/listings/recordSwipe", { id: listingId, session_id: sessionId, direction })
}
}
async handleClick(listingId) {
this.setState({
liked: !this.state.liked
});
await axios.post("http://localhost:5000/listings/updateLike", { id: listingId });
}
showDetails(listing) {
alert(`Name: ${listing.name}\n Price : $${listing.price['$numberDecimal']} \n Minimum Nights : ${listing.minimum_nights}\n Beds : ${listing.beds}`);
}
async componentWillMount() {
const response = await axios.get(`http://localhost:5000/listings`);
const json = await response.data;
this.setState({ data: json });
}
render() {
const likeButtonLabel = this.state.liked ? '❤' : 'Like';
return (
<div className="app">
<div>
<h1>LisTinder</h1>
<h2>Swipe left for drop or right to save...</h2>
<div className="card-container">
{this.state.data.map((listing) =>
<TinderCard className='swipe' key={listing.name} onSwipe={(dir) => this.onSwipe(dir, listing._id)} >
<div style={{ backgroundImage: 'url(' + listing.images.picture_url + ')' }} className='card'>
<div className="card-details">
<h3>{listing.name}</h3>
<div className="card-actions">
<button className="button" onClick={() => this.handleClick(listing._id)}>{likeButtonLabel}</button>
<button className="button" onClick={() => this.showDetails(listing)}>See Details</button>
</div>
</div>
</div>
</TinderCard>
)}
</div>
</div>
</div>
);
}
}
export default App;
We will use some third-party modules, like the “react-tinder-card”, which will allow us to create the swiping tiles and graphics. Those will interact with the application functions to handle events like “onSwipe,” “handleLikeClick,” “showDetails,” and “componentWillMount” (to show the data on page load).
Functions “onSwipe,” “handleLikeClick,” and “componentWillMount” will use the axios library to perform http REST API requests to our Express server. Those, in turn, will perform the needed action on our Atlas cluster.
Now we can start the application in a new terminal (the server process must remain running):
cd ../app/listings
npm install
npm start
Once all components are up and running, we can open the http://localhost:3000 URL and we should see our “LisTinder” application loaded:
Main App | Swipe Cards | Show Details |
---|---|---|
![]() | ![]() | ![]() |
Interaction with the tiles by swapping them will call our Express routes and perform application operations.
Successfully connected to MongoDB.
1 document updated
1 document deleted
Atlas App Services, MongoDB’s mobile database and development cloud services, offer a robust and scalable replacement to the self-hosted Express Server.
Creating an application is very easy with a generous free tier. In that application, you can create HTTP services with webhook endpoints to perform the same operations as the Express routes, without the need for maintaining and coding lots of boilerplate code. Those services are optimised for Atlas cluster access and will open a new world of opportunities like cloud functions, auth providers, GraphQL, and triggers.
Let’s port one route to a webhook. We will start by creating an HTTP service within a newly created Realm application UI.
Navigate to the “3d Party Services” section and click on the HTTP service type.
As part of defining the service, we need to configure the HTTP method this webhook will use and its associated function logic.
The associated function code:
// This function is the webhook's request handler.
exports = async function(payload, response) {
// Querying a mongodb service:
return await context.services.get("mongodb-atlas").db("sample_airbnb").collection("listingsAndReviews").find({}).limit(50);
};
Now we can use the webhook URL directly in the React application. Add the following to the app class in App.js:
async componentWillMount() {
const response = await axios.get(`<PASTE-YOUR-WEBHOOK-URL>`);
const json = await response.data;
this.setState({ data: json });
}
Wow, that's much easier and more scalable!
Using Express as a back-end middleware is a popular MongoDB stack design. Express is lightweight and approachable for JSON and REST API operations. MongoDB Atlas is a scalable and flexible document Database-as-a-Service and makes a perfect companion to Express in many stacks like MERN, MEAN, and MEVN.
Having said that, Atlas App Services and webhooks are a robust replacement for the Express tier, moving the need to manage an Express server and its dependencies on-prem.
The MongoDB Driver is the native driver for Node JS applications. Therefore, the Express JS server can use it to access MongoDB.
Visit the following links: