Get started with the MERN stack: Build a blog with MongoDB Atlas
32 minIntroduction
MERN is a full-stack solution named after the technologies that make up the stack: MongoDB, Express, React, and Node.js.
- M - MongoDB is a NoSQL document-based database. Databases are used to persist any data the users will need. In this guide, we are going to use MongoDB Atlas, MongoDB's managed database solution.
- E - Express.js is a flexible and minimalist web framework for building Node.js applications
- R - React.js is a front-end frameowrk that lets you build interactive UIs.
- N - Node.js is an asynchronous event-driven JavaScript runtime designed to build scalable network applications.
Here is a schema for an overview of how these technologies interact to form a web application.
React is used to create the components on the client-side of the application while Express and Node.js are used for building the server-side. Then, MongoDB is used to persist data for the application.
This is the first guide in a mini-series focused on the popular MERN stack. In this guide, we will create a sample blog app. The second guide in this mini-series will focus on creating a microservice to add extra search capabilities to this blog app by using Mongo Atlas Search.
At the end of this guide we will have a full-functioning basic blog web app where authors can post, edit and delete articles. To complete the tutorial, the application will be deployed on the internet by using the Koyeb serverless platform.
We will deploy our application to Koyeb using git-driven deployment, which means all changes we make to our application's repository will automatically trigger a new build and deployment on the serverless platform. By deploying on Koyeb, our application will benefit from native global load balancing, autoscaling, autohealing, and auto HTTPS (SSL) encryption with zero configuration on our part.
Requirements
To successfully follow this tutorial, you need the following:
- A local environment with Yarn and Node.js installed
- A MongoDB Atlas account to create a managed MongoDB database
- A Postman account and Postman Desktop Agent to test the API
- A GitHub account to version and deploy your application code on Koyeb
- A Koyeb account to deploy and run the application
Steps
The steps to creating a blog application with a MERN stack and deploying it to production on Koyeb include:
- Set up the blog application project
- Create a MongoDB Atlas database
- Define the blog post model and the article schema
- Implement the schema using Mongoose
- Configure the blog's API endpoints with Express
- Test the API endpoints using Postman
- Set up the blog's UI with React, Axios, and reusable components
- Deploy the blog app on Koyeb
Set up the blog application project
To get started, create the project folder mongo-blog
and install all the related dependencies. Open your terminal and create the project folder:
Move into mongo-blog
and setup Express using express-generator
:
By using npx we can execute express-generator without installing the package.
You will be prompted several questions to create the package.json
file such as the project's name, version, and more.
Add the following code to the package.json
file:
Next, we are going to add 2 more packages:
nodemon
to reload the server. As we are developing in our local environment, we want our server to reload whenever a change in the code occurs.cors
to allow cross-origin resource sharing. This is important when the React-based client calls the server API in our local environment.
In your terminal, install them by running:
The option "--save-dev" installed nodemon as a devDependency, which are packages that are only needed for local development. Perfect for us since we only need it for local development.
Open your package.json
and add one more command under scripts
:
In app.js
we are going to require cors
and attach it to the app:
We are going to use mongoose
, a very straight-forward ORM built for Node, to model our application data and connect to a Mongo database to store our posts. Add it by running:
Next, we need to add an extra script to build the client bundle.js
. In package.json
, add the extra script so your file looks like this:
Next, run yarn install
in the terminal to install the packages.
Now, we can move on to setting up the client. First, at the root of your project directory create a folder /client
, move into this folder and install React using create-react-app
:
Similarly to express-generator
, this command will create a ready-to-go React project hiding most of the tedious configurations required in the past.
On top of the basic packages, like react
and react-dom
, we have to think about what other packages our blog client needs:
- The client will make API calls to the server to perform basic CRUD operations on the database.
- There are gonna be different pages to create, read, edit and delete blog posts.
- We want there to be forms to create and edit a post.
These are very common functionalities and fortunately the yarn ecosystem offers tons of different packages. For the purpose of the tutorial, we are gonna install axios
to make API calls, react-router-dom
to handle client routing and react-hook-form
to submit form data.
In the terminal, go ahead and install them under /client
:
For our application, the server and client share the same repository. This means we can use the folder /public
located in the project's root directory to return the static client after it is built. To do this, we need to tweak the "build" script inside /client/package.json
to build the static files in it:
Under /client/src
, edit the index.js
file:
This creates easy entry points for the components we are going to build for our blog.
Now, let's talk about styling. We don't really want to spend too much time dealing with CSS so we are using Bootstrap, specifically react-bootstrap
so that we can include all the UI components we need without really adding CSS. From /client
, run:
Finally, we are going to drop one file to prepare for our deployment: package-lock.json
. From your project's root directory:
If you want to verify that you setup everything correctly, take a look at project directory structure:
Go ahead and start the server by running yarn dev
on the terminal, then open the browser at http://localhost:3000
and if everything was setup correctly you should see a welcome message from Express.
Create a database on Mongo Atlas
The easiest way to create our MongoDB database is to use MongoDB Atlas. MongoDB Atlas hosts databases on AWS, Google Cloud, Azure and makes it easy to operate and scale your Mongo database.
From the "Database Deployments" page, click "Build a Database".
- Choose the "shared" plan which starts for free.
- Select your preferred cloud provider and region.
- Enter a cluster name, liike "mongo-blog-db".
- Click the "Create Cluster" button.
- Select the "Username & Password" authentication option, enter a username and password and click the "Create User button". Store the username and password somewhere safe, we will use this information during deployment.
- Enter "0.0.0.0/0" without the quotes into the IP Address field of the IP Access List section, and click the "Add Entry" button.
- Click the "Finish and Close" button and then the "Go to Databases" button. You will be redirected to the "Data Deployments" page, with your new MongoDB cluster now visible.
- Click the "Connect" button next to your MongoDB cluster name, select the "Connect your application" option and copy your database connection string to a safe place for later use. A typical connection string should look like this:
You have now created a MongoDB database!
To connect the database to our application, move back the codebase. Open app.js
and add this code to require mongoose
, connect it to the database by using the connection string, and recover from potential errors:
Since the connection string is an environment variable, to test it in development we can add it to the package.json
:
To ensure everything is running as expected, run the application locally:
Define the blog post model and the article schema
With the database now up and running, it is time to create our first model Post
.
The basic schema for a blog post is defined by a title, the content of the post, the author, a creation date and optionally tags. The following should help us visualize the schema:
Fields | Type | Required |
---|---|---|
title | String | X |
author | String | X |
content | String | X |
tags | Array | |
createdAt | Date | X |
Implement the schema using Mongoose
Mongoose's straightforward syntax makes creating models a very simple operation. At the root of your project, add a new folder models
and add a post.js
file there:
Add this code to the post.js
file:
Here is an explanation of what we are doing here:
- Require Mongoose and use the
Schema
class to createPostSchema
. - When creating the object
PostSchema
, we add the fields title, content, author, tags, createdAt. - Instruct
PostSchema
to automatically add the creation date right before saving the new post inside the database for us. - We export the model to use it within our controllers to perform CRUD operations on the posts.
Configure the blog's API endpoints with Express
Now that we have completed the modelling of our blog posts we can create API endpoints to work with them. As mentioned earlier, our blog app allows users to write, read, edit and delete posts. Now we will code a few endpoints to achieve all that. Specifically:
- GET
/api/posts
returns all the posts in descending order, from the latest to the earliest. - GET
/api/posts/:id
returns a single blog post given its id. - POST
/api/posts
saves a new blog post into the db. - PUT
/api/posts/:id
updates a blog post given its id. - DELETE
/api/posts/:id
deletes a blog post.
Create CRUD endpoints using express routes
Thanks to express-generator
scaffolding we already have the routes folder /routes
inside mongo-blog
. Inside routes
, create a new file posts.js
:
Using the express Router
object we are going to create each endpoint. The first one, GET /api/posts
retrieves the posts using our newly created Post model function find()
, sorts them using sort()
and then returns the whole list to the client:
In one single line of code we fetched and sorted the post, that's Mongoose magic!
We can implement GET /api/posts/:id
similarly but this time we are using findById
and we are passing the URL parameter id
. Add the following to posts.js
:
If we cannot find any post with the id
that is passed, we still return a positive 200 HTTP status with an empty object as post.
At this point, we have functioning endpoints but without any posts in the database, so we cannot really do much. To change this, we will create a POST /api/posts
endpoint, so we can start adding posts.
In req.body
we will collect the title, author, content and tags coming from the client, then create a new post, and save it into the database. Add the following to posts.js
:
Next, we want to retrieve and update a post. For this action, we can create a PUT /api/posts/:id
endpoint while Mongoose provides a handy function findByIdAndUpdate
. Again, add this code to posts.js
:
The last action we will add is the ability to delete a specific blog post by sending its id
. Mongoose once again provides a function deleteOne
that we can use to tell our Mongo database to delete the post with that id
. Add the following to posts.js
:
Following the steps above, we have just built our new router. Now, we have to attach it to our server and test it out using Postman, an API platform for building and using APIs. Open app.js
and under indexRouter
go ahead and add postsRouter
as well. At this point, your app.js
file should look like this:
Test the API endpoints using Postman
In the absence of a client, we can use POSTMAN to test our API. Extremely flexible and easy to use, Postman allows us to specificy the type of request (i.e., GET, POST, PUT, and DELETE); the type of payload, if any; and several other options to fine-tune our tests.
If you closed the server, go ahead and start it again in the terminal by running yarn dev
.
We currently have an empty database, so the very first test can be the creation of a post. To create a post, specify that we want a POST request to http://localhost:3000/api/posts
. For the body payload, select raw
and choose JSON
in the dropdown menu, so that we can use JSON syntax to create it. Here is the result of the call:
To make sure the post was really created, we can make a call to http://localhost:3000/api/posts
to get the full list of posts as well as http://localhost:3000/api/posts/:post_id
to fetch the single post:
Since we have just one post, the result of the API calls should be almost the same as GET /api/posts
returns an array of posts with a single item in it.
If you want to update the post, for example if you want to change the title and add an extra tag, you can pass the new data in the API call JSON body:
If you are unsure whether it was correctly updated, go ahead and call GET /api/posts/post_id
again:
Finally, test that deleting the post works as expected:
Run GET /api/posts
again and you should get an empty list of posts as result:
Set up the blog's UI with React, Axios, and reusable components
Since the server-side of the application is now complete, it is now time work on the client-side of the application.
Client routes and basic layout
One of the very first things to define are the routes of our web application:
- The home page
- Single blog posts pages
- Create a new post and edit posts
With that in mind, here are the proposed URLs:
| URL | Description | | -------------------- | :------------------------ | --- | | / | Home page | X | | /posts/:post_id | Post content page | | /posts/new | Page to create a new post | | /posts/:post_id/edit | Page to edit a post |
The routes will all reside under /client/src/App.js
using react-router-dom
components Routes
and Route
. Move into App.js and edit the file with the following:
In this example we are rendering the Home
component when the browser hits the home page.
App.js
acts as the root component of our client, so we can imagine the shared layout of our blog being rendered through App
. Our blog page will have a Navbar with a button that will let you create a new post. This Navbar will be visible on every page of our client application, so it is best to render it here in App.js
. Move into App.js
and add this code:
In a few lines of code we created a decent layout that. Once we implement Home
, our home page should look like this:
We previously defined all the client routes, so we can add them all in App
along with main components that we will implement later:
Axios client
Our client will have to make API calls to the server to perform operations on the database. This is why we installed axios
earlier.
We will wrap it inside an http
library file and export it as a module. We do this for two reasons:
- We need to take into account that making API calls in local is like calling a different server. As client and servers run on different ports, this is a completely different configuration compared to the deployment we will do on Koyeb later on.
- The HTTP object is exported along with the basic methods to call GET, POST, PUT and DELETE endpoints.
In /client/src
, create a new folder /lib
and inside add an http.js
file:
Add the following code to http.js
:
We have just finished setting up our client to make API calls to the server to perform operations on the database.
In the next section, we will see how we can use the http
object.
Create containers and reusable components
React is component-based, meaning that we can create small and encapsulated components and reuse them all over the web application as basic building pieces for more complex UIs.
The very first component we are going to build is Home
, which in charge of rendering the list of posts as well as the header of the home page.
To render the list of posts, Home
has to:
- Call the server GET
/api/posts
endpoint after the first rendering - Store the array posts in the state
- Render the posts to the user and link them to
/posts/:post_id
to read the content
Under /client/src
, create a folder /pages
and a file home.js
in it:
Add the following code to home.js
:
About formatDate
, this is a utility function that formats the post creation date to "Month DD, YYYY". We are expecing to call it in other components as well. This is why it is decoupled from Home
into its own file.
In the terminal create the file formatDate.js
under /lib
:
Add the following to the formatDate.js
file:
The 'formatDate' function takes the date from the database, creates a Date
object and formats it by setting locale and options. The resulting UI will look like this:
Next, we will set up the part of the UI to display the blog posts. The logic behind showing the blog post content is not too different than the one we saw for Home
:
- When hitting
/posts/post_id
the client calls the server API to fetch the specific blog post. - The post is stored in the component state.
- Using react-boostrap, we create a simple-but-effective UI for the users to read the post.
- On top of this, we add 2 buttons to either "edit" or "delete" the posts. Specifically, "edit" is nothing more than a link to
/posts/post_id/edit
and delete calls DELETE/api/posts/:post_id
and then redirects the user to the home page.
Open the terminal and create a post.js
under /pages
:
Add the following code to post.js
:
The UI will look like this:
As we will redirect the user to another page when editing the blog post, create the file edit.js
inside /pages
:
The UI will show a form filled with the blog post data for title, author, content and tags. Users can
- Edit each one of the fields
- Submit the data to the server by calling PUT
/api/posts/:post_id
Note that we are using react-hook-form
to register fields, collect the data and submit to the server. In this tutorial, we are not performing any validation on the data but it is fairly simple to be added thanks to react-hook-form simple API.
Add the following code to edit.js
:
With a centralized app state, we would not need to call the API once again as we would have the post data already available in the client. However, in order not to avoid adding extra business logic to pass data on different views or handle refreshing the page, we simply call /api/posts/post_id
once again.
Here is the page UI as of now:
The final action we will add is to allow users the ability to create their own posts. We already created the button "New" in the navbar that redirects to /posts/new
.
Similarly to the previous page edit.js
, we prompt a form for the user to fill out. Fields are initially empty as we are expecting to store a brand new blog post in the database.
Add a new file create.js
in /pages
and enter the following code:
To start the create-react-app, run yarn start
in the terminal. By default it runs on port 3000, which is currently used by the Express server. So, in the terminal create-react-app is going to suggest using a different port, most likely 3001. Click "Enter" and the client app will restart on port 3001.
If you want to add an image to your homepage, add it under /client/public
as avatar.jpeg
. When you are done, your UI should resemble this:
Congratulations, we finished building the UI! We are now ready to deploy our blog app on the internet!
Deploy the blog app on Koyeb
We are going to deploy our application on Koyeb using git-driven deployment with GitHub. Each time a change is pushed to our application, this will automatically trigger Koyeb to perform a new build and deployment of our application. Once the deployment passes necessary health checks, the new version of our application is promoted to the internet. In case the health checks are not passed, Koyeb will maintain the latest working deployment to ensure our application is always up and running.
Before we dive into the steps to deploy on the Koyeb, we need to remove the connection string to the Mongo database from our code as we will inject it from the deployment configuration for security.
Before we dive into the steps to deploy on the Koyeb, we need to remove the connection string to the Mongo database from our code as we will inject it from the deployment configuration for security. Update your package.json
file by removing the connection string we added earlier to test our application locally:
To deploy on Koyeb, we need to create a new GitHub repository from the GitHub web interface or using the GitHub CLI with the following command:
Initialize a new git repository on your machine and add a new remote pointing to your GitHub repository:
Add all the files in your project directory to the git repository and push them to GitHub:
Once your code is added to your GitHub repository, log in on Koyeb and from the Overview tab on the control panel, click Create Web Service to begin:
- Choose GitHub as the deployment method.
- Select the repository where you pushed the code.
- In the Builder section, click the Override toggle associated with the Build command and enter
yarn build-client
in the field. Click the Override toggle for the Run command and enteryarn start
in the field. - In the Environment variables section, click Add variable and create a Secret variable called
CONNECTION_STRING
set to the connection string provided by Mongo Atlas. - In the Exposed ports section, change the port to 3000.
- Choose a name for the App and Service and click Deploy.
Once you click on "Deploy", Koyeb will take care of deploying your application in just a few seconds. Koyeb will return a public URL to access the app.
Good job! We now have a blog app that is live! Your application now benefits from built-in continuous deployment, global load balancing, end-to-end encryption, its own private network with service mesh and discovery, autohealing, and more.
If you would like to look at the code for this sample application, you can find it here.
Conclusions
In this first part of the series of the MERN web apps series, we built the basic blocks of an online blog application. We initially set up a MongoDB Atlas database, created an Express API server to fetch the data and a React client to show the data to the users. There are several enhancements we could add on the client-side such as form validation, code refactoring, and more. We will see you soon on the second part where you are going to explore the search abilities of Mongo Atlas.
Since we deployed the application on Koyeb using git-driven deployment, each change you push to your repository will automatically trigger a new build and deployment on the Koyeb Serverless Platform. Your changes will go live as soon as the deployment passes all necessary health checks. In case of a failure during deployment, Koyeb maintains the latest working deployment in production to ensure your application is always up and running.
Questions or suggestions to improve this guide? Join us on the community platform to chat!