Get started with the MERN stack: Build a blog with MongoDB Atlas
Tags: mongodb, react, tutorial, webdev
Published: 2022-06-17 08:56:00

## Introduction

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.

![mern schema](https://www.koyeb.com/static/images/tutorials/deploy-a-mern-stack-with-express-react-and-mongodb-atlas/mern-stack-koyeb.jpg)

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](https://yarnpkg.com/) and [Node.js](https://nodejs.org/en/download/) installed
- A [MongoDB Atlas account](https://www.mongodb.com/cloud/atlas/register) to create a managed MongoDB database 
- A [Postman account](https://www.postman.com/) and [Postman Desktop Agent](https://www.postman.com/downloads/) to test the API
- A [GitHub account](https://github.com) to version and deploy your application code on Koyeb
- A [Koyeb account](https://app.koyeb.com) 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:

1. [Set up the blog application project](#set-up-blog-application-project)
2. [Create a MongoDB Atlas database](#create-a-database-on-mongo-atlas)
3. [Define the blog post model and the article schema](#define-the-blog-post-model-and-the-article-schema)
4. [Implement the schema using Mongoose](#implement-the-schema-using-mongoose)
5. [Configure the blog's API endpoints with Express](#configure-the-blog-s-api-endpoints-with-express)
6. [Test the API endpoints using Postman](#test-the-api-endpoints-using-postman)
7. [Set up the blog's UI with React, Axios, and reusable components](#set-up-the-blog-s-ui-with-react-axios-and-reusable-components)
8. [Deploy the blog app on Koyeb](#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:

```bash
mkdir mongo-blog
```

Move into `mongo-blog` and setup Express using `express-generator`:

```bash
cd mongo-blog
npx 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:

```bash
{
  "name": "mongo-blog",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "dependencies": {
    "cookie-parser": "~1.4.4",
    "debug": "~2.6.9",
    "express": "~4.16.1",
    "http-errors": "~1.6.3",
    "jade": "~1.11.0",
    "morgan": "~1.9.1"
  }
}
```

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:

```bash
yarn add nodemon --save-dev
yarn add cors
```

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`:

```bash
{
...
  "scripts": {
+   "dev": "nodemon ./bin/www",
    "start": "node ./bin/www"
  },
...
```

In `app.js` we are going to require `cors` and attach it to the app:

```javascript
const createError = require('http-errors');
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const logger = require('morgan');
const cors = require('cors');

const indexRouter = require('./routes/index');
const usersRouter = require('./routes/users');

const app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(cors());

app.use('/', indexRouter);
app.use('/users', usersRouter);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};
  
  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = 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:

```bash
yarn add mongoose
```

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:

```bash
{
...
  "scripts": {
    "dev": "nodemon ./bin/www",
    "start": "node ./bin/www",
+   "build-client": "cd ./client && yarn build"
  },
...
```

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`:

```bash
mkdir client
cd client
npx 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`:

```bash
yarn add axios react-router-dom react-hook-form
```

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:

```javascript
{
...
  "scripts": {
    "start": "react-scripts start",
+   "build": "BUILD_PATH='../public' react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
...
```

Under `/client/src`, edit the `index.js` file:

```
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter } from 'react-router-dom';

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById('root')
);

reportWebVitals();
```

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: 

```bash
yarn add bootstrap@5.1.3 react-bootstrap
```

Finally, we are going to drop one file to prepare for our deployment: `package-lock.json`. From your project's root directory:

```bash
rm package-lock.json
```

If you want to verify that you setup everything correctly, take a look at project directory structure:
```
├── app.js
├── bin
│   └── www
├── package.json
├── public
│   ├── images
│   ├── javascripts
│   └── stylesheets
│       └── style.css
├── routes
│   ├── index.js
│   └── users.js
└── views
    ├── error.pug
    ├── index.pug
    └── layout.pug
└── client
    ├── package.json
    ├── yarn.lock
    ├── public
    └── src
        ├── App.js
        ├── App.css
        ├── App.test.js
        ├── index.js
        ├── index.css
        ├── logo.svg
        ├── reportWebVitals.js
        └── setupTests.js
```

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](https://www.mongodb.com/cloud/atlas/lp/try2). 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:

```
mongodb+srv://<username>:<password>@mongo-client-db.r5bv5.mongodb.net/<database_name>?retryWrites=true&w=majority
```

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:

```javascript
...
const mongoose = require('mongoose');
const CONNECTION_STRING = process.env.CONNECTION_STRING;

// setup connection to mongo
mongoose.connect(CONNECTION_STRING);
const db = mongoose.connection;

// recover from errors
db.on('error', console.error.bind(console, 'connection error:'));
...
```

Since the connection string is an environment variable, to test it in development we can add it to the `package.json`:

```bash
{
...
  "devDependencies": {
    "nodemon": "^2.0.15"
  },
+ "nodemonConfig": {
+   "env": {
+     "CONNECTION_STRING": "YOUR_CONNECTION_STRING"
+   }
+ }
}

```

To ensure everything is running as expected, run the application locally:

```
yarn dev
```

## 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:

```bash
mkdir models
touch /models/post.js
```

Add this code to the `post.js` file:

```
// Dependencies
const mongoose = require('mongoose');
const Schema = mongoose.Schema;

// Defines the Post schema
const PostSchema = new Schema({
  title: { type: String, required: true },
  content: { type: String, required: true },
  author: { type: String, required: true },
  tags: { type: [String] },
  createdAt: { type: Date, default: Date.now },    
});

// Sets the createdAt parameter equal to the current time
PostSchema.pre('save', (next) => {
  now = new Date();
  if (!this.createdAt) {
    this.createdAt = now;
  }

  next();
});

// Exports the PostSchema for use elsewhere.
module.exports = mongoose.model('Post', PostSchema);
```

Here is an explanation of what we are doing here: 
1. Require Mongoose and use the `Schema` class to create `PostSchema`.
2. When creating the object `PostSchema`, we add the fields title, content, author, tags, createdAt.
3. Instruct `PostSchema` to automatically add the creation date right before saving the new post inside the database for us.
4. 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:

1. GET `/api/posts` returns all the posts in descending order, from the latest to the earliest.
2. GET `/api/posts/:id` returns a single blog post given its id.
3. POST `/api/posts` saves a new blog post into the db.
4. PUT `/api/posts/:id` updates a blog post given its id.
5. 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`:

```bash
touch /routes/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:

```javascript
const express = require('express');
const router = express.Router();
// Require the post model
const Post = require('../models/post');

/* GET posts */
router.get('/', async (req, res, next) => {
  // sort from the latest to the earliest
  const posts = await Post.find().sort({ createdAt: 'desc' });
  return res.status(200).json({
    statusCode: 200,
    message: 'Fetched all posts',
    data: { posts },
  });
});
...
```

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`: 

```javascript
...
/* GET post */
router.get('/:id', async (req, res, next) => {
 // req.params contains the route parameters and the id is one of them
  const post = await Post.findById(req.params.id);
  return res.status(200).json({
    statusCode: 200,
    message: 'Fetched post',
    data: {
      post: post || {},
    },
  });
});
...

```

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`:

```javascript
...
/* POST post */
router.post('/', async (req, res, next) => {
  const { title, author, content, tags } = req.body;

  // Create a new post
  const post = new Post({
    title,
    author,
    content,
    tags,
  });

  // Save the post into the DB
  await post.save();
  return res.status(201).json({
    statusCode: 201,
    message: 'Created post',
    data: { post },
  });
});
...
```

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`:

```javascript
...
/* PUT post */
router.put('/:id', async (req, res, next) => {
  const { title, author, content, tags } = req.body;

  // findByIdAndUpdate accepts the post id as the first parameter and the new values as the second parameter
  const post = await Post.findByIdAndUpdate(
    req.params.id,
    { title, author, content, tags },
  );
  
  return res.status(200).json({
    statusCode: 200,
    message: 'Updated post',
    data: { post },
  });
});
...
```

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`:

```javascript
...
/* DELETE post */
router.delete('/:id', async (req, res, next) => {
  // Mongo stores the id as `_id` by default
  const result = await Post.deleteOne({ _id: req.params.id });
  return res.status(200).json({
    statusCode: 200,
    message: `Deleted ${result.deletedCount} post(s)`,
    data: {},
  });
});

module.exports = router;
```

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:

```javascript
const createError = require('http-errors');
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const logger = require('morgan');
const mongoose = require('mongoose');
const cors = require('cors');
const CONNECTION_STRING = process.env.CONNECTION_STRING;

const indexRouter = require('./routes/index');
const postsRouter = require('./routes/posts');

const app = express();

// view engine setup to a
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

// setup connection to mongo
mongoose.connect(CONNECTION_STRING);
const db = mongoose.connection;

db.on('error', console.error.bind(console, 'connection error:'));

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(cors());

app.use('/', indexRouter);
app.use('/api/posts', postsRouter);

// Return the client
app.get('/posts*', (_, res) => {
  res.sendFile(path.join(__dirname, 'public') + '/index.html');
});

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;
```

## Test the API endpoints using Postman

In the absence of a client, we can use [POSTMAN](https://www.postman.com/) 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:

![create-blog-post](https://www.koyeb.com/static/images/tutorials/deploy-a-mern-stack-with-express-react-and-mongodb-atlas/create-blog-post.png)

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:

![get-all-posts](https://www.koyeb.com/static/images/tutorials/deploy-a-mern-stack-with-express-react-and-mongodb-atlas/get-all-posts.png)

![get-post](/static/images/tutorials/deploy-a-mern-stack-with-express-react-and-mongodb-atlas/get-post.png)

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:

![update-post](/static/images/tutorials/deploy-a-mern-stack-with-express-react-and-mongodb-atlas/update-post.png)

If you are unsure whether it was correctly updated, go ahead and call GET `/api/posts/post_id` again:

![get-updated-post](/static/images/tutorials/deploy-a-mern-stack-with-express-react-and-mongodb-atlas/get-updated-post.png)

Finally, test that deleting the post works as expected:

![delete-post](/static/images/tutorials/deploy-a-mern-stack-with-express-react-and-mongodb-atlas/delete-post.png)

Run GET `/api/posts` again and you should get an empty list of posts as result:

![get-empty-list-of-posts](/static/images/tutorials/deploy-a-mern-stack-with-express-react-and-mongodb-atlas/get-empty-list-of-posts.png)

## 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:

```javascript

import { Routes, Route } from 'react-router-dom';
import Home from './pages/home';

function App() {
  return (
      <Routes>
        <Route path="/" element={<Home />} />
      </Routes>
  );
}

export default App;
```

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: 

```javascript
// Import Bootstrap CSS
import 'bootstrap/dist/css/bootstrap.min.css';
import { Routes, Route } from 'react-router-dom';
import Home from './pages/home';
// Import the Navbar, Nav and Container components from Bootstrap for a nice layout
import Navbar from 'react-bootstrap/Navbar';
import Nav from 'react-bootstrap/Nav';
import Container from 'react-bootstrap/Container';

function App() {
  return (
    <>
      <Navbar bg="dark" expand="lg" variant="dark">
        <Container>
          <Navbar.Brand href="/">My Blog</Navbar.Brand>
          <Navbar.Toggle aria-controls="basic-navbar-nav" />
          <Nav className="me-auto">
            <Nav.Link href="/posts/new">New</Nav.Link>
          </Nav>
        </Container>
      </Navbar>
      <Routes>
        <Route path="/" element={<Home />} />
      </Routes>
    </>
  );
}

export default App;
```

In a few lines of code we created a decent layout that. Once we implement `Home`, our home page should look like this:

![react-homepage](/static/images/tutorials/deploy-a-mern-stack-with-express-react-and-mongodb-atlas/react-homepage.png)

We previously defined all the client routes, so we can add them all in `App` along with main components that we will implement later:

```javascript
import 'bootstrap/dist/css/bootstrap.min.css';
import { Routes, Route } from 'react-router-dom';

// We are going to implement each one of these "pages" in the last section
import Home from './pages/home';
import Post from './pages/post';
import Create from './pages/create';
import Edit from './pages/edit';

import Navbar from 'react-bootstrap/Navbar';
import Nav from 'react-bootstrap/Nav';
import Container from 'react-bootstrap/Container';

function App() {
  return (
    <>
      <Navbar bg="dark" expand="lg" variant="dark">
        <Container>
          <Navbar.Brand href="/">My Blog</Navbar.Brand>
          <Navbar.Toggle aria-controls="basic-navbar-nav" />
          <Nav className="me-auto">
            <Nav.Link href="/posts/new">New</Nav.Link>
          </Nav>
        </Container>
      </Navbar>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/posts/:id" element={<Post />} />
        <Route path="/posts/new" element={<Create />} />
        <Route path="/posts/:id/edit" element={<Edit />} />
      </Routes>
    </>
  );
}

export default App;
```

### 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:

1. 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.
2. 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:

```bash
  mkdir lib
  touch /lib/http.js
```

Add the following code to `http.js`:

```javascript
import axios from 'axios';
// When building the client into a static file, we do not need to include the server path as it is returned by it
const domain = process.env.NODE_ENV === 'production' ? '' : 'http://localhost:3000';

const http = (
  url,
  {
    method = 'GET',
    data = undefined,
  },
) => {
  return axios({
    url: `${domain}${url}`,
    method,
    data,
  });
};

// Main functions to handle different types of endpoints
const get = (url, opts = {}) => http(url, { ...opts });
const post = (url, opts = {}) => http(url, { method: 'POST', ...opts });
const put = (url, opts = {}) => http(url, { method: 'PUT', ...opts });
const deleteData = (url, opts = {}) => http(url, { method: 'DELETE', ...opts });

const methods = {
  get,
  post,
  put,
  delete: deleteData,
};

export default methods;
```

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: 
1. Call the server GET `/api/posts` endpoint after the first rendering
2. Store the array posts in the state
3. 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:

```bash
mkdir pages
touch pages/home.js
```
Add the following code to `home.js`:

```javascript
import { useEffect, useState } from 'react';
// Link component allow users to navigate to the blog post component page
import { Link } from 'react-router-dom';
import Container from 'react-bootstrap/Container';
import ListGroup from 'react-bootstrap/ListGroup';
import Image from 'react-bootstrap/Image';
import http from '../lib/http';
// utility function to format the creation date
import formatDate from '../lib/formatDate';

const Home = () => {
  // useState allows us to make use of the component state to store the posts
  const [posts, setPosts] = useState([]); 
  useEffect(() => {
    // Call the server to fetch the posts and store them into the state
    async function fetchData() {
      const { data } = await http.get('/api/posts');
      setPosts(data.data.posts);
    }
    fetchData();
  }, []);
  
  return (
    <>
      <Container className="my-5" style={{ maxWidth: '800px' }}>
        <Image
          src="avatar.jpeg"
          width="150"
          style={{ borderRadius: '50%' }}
          className="d-block mx-auto img-fluid"
        />
        <h2 className="text-center">Welcome to the Digital Marketing blog</h2>
      </Container>
      <Container style={{ maxWidth: '800px' }}>
        <ListGroup variant="flush" as="ol">
          {
            posts.map((post) => {
              // Map the posts to JSX
              return (
                <ListGroup.Item key={post._id}> 
                  <div className="fw-bold h3">
                    <Link to={`/posts/${post._id}`} style={{ textDecoration: 'none' }}>{post.title}</Link>
                  </div>
                  <div>{post.author} - <span className="text-secondary">{formatDate(post.createdAt)}</span></div>
                </ListGroup.Item>
              );
            })
          }
        </ListGroup>
      </Container>
    </>
  );
};

export default Home;
```

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`:

```bash
touch lib/formatDate.js
```

Add the following to the `formatDate.js` file:

```javascript
const formatDate = (date, locale = 'en-US') => {
  if (!date) return null;

  const options = { year: 'numeric', month: 'long', day: 'numeric' };
  const formattedDate = new Date(date);
  return formattedDate.toLocaleDateString(locale, options);
};

export default formatDate;
```

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:

![react-homepage](https://www.koyeb.com/static/images/tutorials/deploy-a-mern-stack-with-express-react-and-mongodb-atlas/react-homepage.png)

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`:

1. When hitting `/posts/post_id` the client calls the server API to fetch the specific blog post.
2. The post is stored in the component state.
3. Using react-boostrap, we create a simple-but-effective UI for the users to read the post.
4. 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`:

```bash
touch post.js
```

Add the following code to `post.js`:

```javascript
import { useEffect, useState } from 'react';
import { useParams, useNavigate, Link } from 'react-router-dom';
import Container from 'react-bootstrap/Container';
import Button from 'react-bootstrap/Button';
import http from '../lib/http';
import formatDate from '../lib/formatDate';

const Post = () => {
  const { id: postId } = useParams();
  const [post, setPost] = useState({});
  const navigate = useNavigate();
  // Fetch the single blog post
  useEffect(() => {
    async function fetchData() {
      const { data } = await http.get(`/api/posts/${postId}`);
      setPost(data.data.post);
    }
    fetchData();
  }, [postId]);
  // Delete the post and redirect the user to the homepage
  const deletePost = async () => {
    await http.delete(`/api/posts/${postId}`);
    navigate('/');
  }
  
  
  return (
    <>
      <Container className="my-5 text-justified" style={{ maxWidth: '800px' }}>
        <h1>{post.title}</h1>
        <div className="text-secondary mb-4">{formatDate(post.createdAt)}</div>
        {post.tags?.map((tag) => <span>{tag} </span>)}
        <div className="h4 mt-5">{post.content}</div>
        <div className="text-secondary mb-5">- {post.author}</div>
        <div className="mb-5">
          <Link
            variant="primary"
            className=" btn btn-primary m-2"
            to={`/posts/${postId}/edit`}
          >
            Edit
          </Link>
          <Button variant="danger" onClick={deletePost}>Delete</Button>
        </div>
        <Link to="/" style={{ textDecoration: 'none' }}>&#8592; Back to Home</Link>
      </Container>
    </>
  );
};

export default Post;
```

The UI will look like this:

![react-post-content](https://www.koyeb.com/static/images/tutorials/deploy-a-mern-stack-with-express-react-and-mongodb-atlas/react-post-content.png)

As we will redirect the user to another page when editing the blog post, create the file `edit.js` inside `/pages`:

```bash
touch edit.js
```

The UI will show a form filled with the blog post data for title, author, content and tags. Users can
1. Edit each one of the fields
2. 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`:

```javascript
import { useEffect } from 'react';
import { useParams, useNavigate, Link } from 'react-router-dom';
import { useForm } from 'react-hook-form';
import Container from 'react-bootstrap/Container';
import Button from 'react-bootstrap/Button';
import Form from 'react-bootstrap/Form';
import http from '../lib/http';

const Edit = () => {
  const { id: postId } = useParams();
  const navigate = useNavigate();
  const { register, handleSubmit, reset } = useForm();
  // we call the API to fetch the blog post current data
  useEffect(() => {
    async function fetchData() {
      const { data } = await http.get(`/api/posts/${postId}`);
      // by calling "reset", we fill the form fields with the data from the database
      reset(data.data.post);
    }
    fetchData();
  }, [postId, reset]);
  
  const onSubmit = async ({ title, author, tags, content }) => {
    const payload = {
      title,
      author,
      tags: tags.split(',').map((tag) => tag.trim()),
      content,
    };
    await http.put(`/api/posts/${postId}`, { data: payload });
    navigate(`/posts/${postId}`);
  };
  
  return (
    <Container className="my-5" style={{ maxWidth: '800px' }}>
      <h1>Edit your Post</h1>
      <Form onSubmit={handleSubmit(onSubmit)} className="my-5">
        <Form.Group className="mb-3">
          <Form.Label>Title</Form.Label>
          <Form.Control type="text" placeholder="Enter title" {...register('title')} />
        </Form.Group>
        <Form.Group className="mb-3">
          <Form.Label>Author</Form.Label>
          <Form.Control type="text" placeholder="Enter author" {...register('author')} />
        </Form.Group>
        <Form.Group className="mb-3">
          <Form.Label>Tags</Form.Label>
          <Form.Control type="text" placeholder="Enter tags" {...register('tags')} />
          <Form.Text className="text-muted">
            Enter them separately them with ","
          </Form.Text>
        </Form.Group>
        <Form.Group className="mb-3">
          <Form.Label>Content</Form.Label>
          <Form.Control as="textarea" rows={3} placeholder="Your content..." {...register('content')} />
        </Form.Group>
        <Button variant="primary" type="submit">Save</Button>
      </Form>
      <Link to="/" style={{ textDecoration: 'none' }}>&#8592; Back to Home</Link>
    </Container>
  );
};

export default Edit;
```

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:

![react-edit-post](https://www.koyeb.com/static/images/tutorials/deploy-a-mern-stack-with-express-react-and-mongodb-atlas/react-edit-post.png)

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:

```javascript
import { useNavigate, Link } from 'react-router-dom';
import { useForm } from 'react-hook-form';
import Container from 'react-bootstrap/Container';
import Button from 'react-bootstrap/Button';
import Form from 'react-bootstrap/Form';
import http from '../lib/http';

const Post = () => {
  const navigate = useNavigate();
  const { register, handleSubmit } = useForm();

  const onSubmit = async ({ title, author, tags, content }) => {
    const payload = {
      title,
      author,
      tags: tags.split(',').map((tag) => tag.trim()),
      content,
    };
    await http.post('/api/posts', { data: payload });
    navigate('/');
  };
  
  return (
    <Container className="my-5" style={{ maxWidth: '800px' }}>
      <h1>Create new Post</h1>
      <Form onSubmit={handleSubmit(onSubmit)} className="my-5">
        <Form.Group className="mb-3">
          <Form.Label>Title</Form.Label>
          <Form.Control type="text" placeholder="Enter title" {...register('title')} />
        </Form.Group>
        <Form.Group className="mb-3">
          <Form.Label>Author</Form.Label>
          <Form.Control type="text" placeholder="Enter author" {...register('author')} />
        </Form.Group>
        <Form.Group className="mb-3">
          <Form.Label>Tags</Form.Label>
          <Form.Control type="text" placeholder="Enter tags" {...register('tags')} />
          <Form.Text className="text-muted">
            Enter them separately them with ","
          </Form.Text>
        </Form.Group>
        <Form.Group className="mb-3">
          <Form.Label>Content</Form.Label>
          <Form.Control as="textarea" rows={3} placeholder="Your content..." {...register('content')} />
        </Form.Group>
        <Button variant="primary" type="submit">Publish</Button>
      </Form>
      <Link to="/" style={{ textDecoration: 'none' }}>&#8592; Back to Home</Link>
    </Container>
  );
};

export default Post;
```

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:

![react-create-post](https://www.koyeb.com/static/images/tutorials/deploy-a-mern-stack-with-express-react-and-mongodb-atlas/react-create-post.png)

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:

```
{
  "name": "mongo-blog",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "dev": "nodemon ./bin/www",
    "start": "node ./bin/www",
    "build-client": "cd ./client && yarn build"
  },
  "dependencies": {
    "cookie-parser": "~1.4.4",
    "cors": "^2.8.5",
    "debug": "~2.6.9",
    "express": "~4.16.1",
    "http-errors": "~1.6.3",
    "jade": "~1.11.0",
    "mongoose": "^6.2.3",
    "morgan": "~1.9.1"
  },
  "devDependencies": {
    "nodemon": "^2.0.15"
  }
}

```

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:

```
gh repo create <YOUR_GITHUB_REPOSITORY> --private
```

Initialize a new git repository on your machine and add a new remote pointing to your GitHub repository:

```
git init
git remote add origin git@github.com:<YOUR_GITHUB_USERNAME>/<YOUR_GITHUB_REPOSITORY>.git
git branch -M main
```

Add all the files in your project directory to the git repository and push them to GitHub:

```
git add .
git commit -m "Initial commit"
git push -u origin main
```

Once your code is added to your GitHub repository, log in on Koyeb and from the Control Panel, click on the button "Create App".

On the App Creation Page, fill in:

1. Name your application, for example `mern-blog`. 
2. For "Deployment method", choose Github.
3. Select the git repository and specify the branch where you pushed the code to. In my case, `main`.
4. In application configuration, add the build command "yarn build-client" and the start command "yarn start"
5. Add a Secret environment variable with the key `CONNECTION_STRING` and the connection string provided by Mongo Atlas.
6. Enter the port 3000, as this is the one we exposed from the server.
7. Name the service, for example `main`.

Once you click on "Create App", 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](https://github.com/koyeb/example-mern-application).

## 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.

If you have any questions or suggestions to improve this guide, feel free to reach out to us on [Slack](https://slack.koyeb.com/).