MERN URL Shortener App - Part 2
Tags: react, mongodb, node, javascript
Published: 2020-07-07 20:10:29

In this multi-part tutorial, we will work together to build an URL Shortener app (basic version of bitly) using React, NodeJS, Express and MongoDB. This will be basically a full-stack application build with MERN stack.

We will be learning Context API, Hooks, Express router, and building a Custom API With JWT Authentication.

On our first part, we successfully installed all modules and completed setup of our Express and MongoDB. Currently we have our Express server running and connected to our MongoDB Atlas database.

In this part, we will setup Mongoose model for URL, and Express routers for generating short URL from long URL and successful redirection.

Let's jump in then...

## Step 4 - Setting up Mongoose model for MongoDB

Mongoose models are database schema constructors. Models are responsible for creating and reading documents from the underlying MongoDB database. 

Though this is a very small app, but to keep scalability in mind, we will structure the application as such that all the similar config files are stacked together in separate directories.

So let's create a directory `models` in our root directory, and inside that `models` directory create a file `Url.js`. This file will store the database schema which we will use to read and modify data from the database specific to only urls.

Let's setup our URL schema by editing the `Url.js` file:

```javascript
const mongoose = require('mongoose');

const UrlSchema = new mongoose.Schema({
  urlCode: {
    type: String,
  },
  longUrl: {
    type: String,
    required: true,
  },
  shortUrl: {
    type: String,
    required: true,
  },
  date: {
    type: String,
    default: Date.now,
  },
});

module.exports = mongoose.model('url', UrlSchema);
```

To explain the above code:

1. We are calling `mongoose` module in the `mongoose` variable.
2. Creating a new variable `UrlSchema` which is constructing a new Mongoose schema.
3. We are adding all the fields or tables inside this schema:
    * urlcode: This field will store the short id for the url
    * longUrl: This field will store the actual URL sent by client
    * shortUrl: This field will store the shorten URL generated by our application
    * data: This field will store the date and time when the url is generated

    Then we are exporting this model using `module.exports = mongoose.model('url', UrlSchema);`. Here `url` is our database collection name, which will be created automatically on first request to the database. 


## Step 5 - Setting up Express routes

To keep consistency on our application workflow, We will now create a new directory `routes` in our root directory and inside that a file `genurl.js` for configuring our Express routes.

Before we get onto our routes, we need to setup our Base URL in our config file, which is `default.json` inside the `config` directory.

```json
{
  "mongouri": "mongodb+srv://priyajit:indiatimes@cluster0.bb04n.mongodb.net/urlshortner?retryWrites=true&w=majority",
  "baseURI": "http://localhost:5000"
}
```

_Note: The Base URL will be the main URL of the short URL that we will be generating. In our case, we are using our default Node URL as our Base URL, but in production this needs to be replaced with an actual URL (i.e https://bit.ly/)_

We can now edit the route file `genUrl.js` and create our first route.

First we will bring in all our required modules:

```javascript
const express = require('express');
const router = express.Router();
const validUrl = require('valid-url');
const shortid = require('shortid');
const config = require('config');
```

then we will bring in the Mongoose model for Url:

```javascript
const Url = require('../model/Url');
```

Now we will setup our route, which will be a POST request and this route will be accessible to all.

First, we will create a POST function to get the data from client-end. 

```javascript
router.post('/', async (request, response) => {
  // We will handle the requested data here

});

module.exports = router;
```

Inside the above function, we will do all our data handling. First, we will destructure and fetch the URL sent from client-end.

```javascript
const { longUrl } = request.body;
```

Then, we will get our Base URL and will validate the same using our `valid-url` module, to check if the base URL is an FQDN:

```javascript
const baseUrl = config.get('baseURI');

if (!validUrl.isUri(baseUrl)) {
  return res.status(401).json('Invalid base url');
}
```

We will now generate a short code, which along with the Base URL will identify the long URL and will redirect to it.

```javascript
const urlCode = shortid.generate();
```

now we will validate the long URL sent from client-end. If it validates we will generate and store the short URL to the database, and if it doesn't then we will return an error.

```javascript
if (validUrl.isUri(longUrl)) {
  // We will generate short URL here

} else {
  res.status(401).json('Invalid Long Url');
}
```

We will now generate short URL inside the above conditional statement. The steps will be:
    1. we will first check if the long URL sent from client-end already exists in our database.
    2. if it does exist, we will get the short URL from the database and will send it back as a response.
    3. if it doesn't exist, we will create a variable `shortUrl` and store the new short URL, by concatenating our Base URL and our short code.
    4. then we will use our Mongoose Url model and store all the required data to our database, and then we will send the short URL as a response back to the client-end.

This is how we can achieve all of the above steps:

```javascript
try {
      let url = await Url.findOne({ longUrl });

      if (url) {
        res.json(url);
      } else {
        const shortUrl = baseUrl + '/' + urlCode;

        url = new Url({
          longUrl,
          shortUrl,
          urlCode,
          date: new Date(),
        });

        await url.save();

        res.json(url);
      }
    } catch (err) {
      console.error(err.message);
      res.status(500).json('Server Error');
    }
```
_Note: As I am using Async/Await to deal with Promises, I have used Try/catch block. You can of course use .then() and .catch(), if you want to._

As route is now in place, we can edit our `server.js` file to call this route whenever an HTTP POST request is sent to `http://localhost:5000/api/genurl`

Let's edit our `server.js` file now to call the above route.

```javascript
app.use('/api/genurl', require('./routes/genurl'));
```

## Step 6 - Testing with Postman

1. Add `http://localhost:5000/api/genurl/` as URL and set the request type to `POST`

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/vsst65cplidcn0igha59.png)

2. Add `Content-Type` to `application/json` in Headers

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/gjuya71t25bsnk5ktltw.png)

3. Add the URL you want inside `Body` as json object, where key will be `longUrl` and value is an URL that you want to shorten.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/kemw5kfrs0c2own6tqhy.png)

et voilà - you get everything as json response.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/t0s87xusycfbz8aqdz21.png)

But `shortUrl` will not load the `longUrl` just now, because we haven't added any route for that.

## Step 7 - ShortURL to LongURL Redirection

Let's set that up now. And for that, we need to edit our `server.js` file to include below line to add our route first.

```javascript
app.use('/', require('./routes/redirect'));
```
_This will return an error as we haven't created our route file yet_

Now create `redirect.js` file in our `routes` directory and add the following:

```javascript
const express = require('express');
const router = express.Router();
const Url = require('../model/Url');

router.get('/:code', async (req, res) => {
  try {
    const url = await Url.findOne({ urlCode: req.params.code });

    if (url) {
      return res.redirect(url.longUrl);
    } else {
      return res.status(404).json('No Url found');
    }
  } catch (err) {
    console.error(err.message);
    res.status(500).json('Server Error');
  }
});

module.exports = router;
```

Now let's iterate over what are we doing with the above codes:
1. First, we are calling in Express and initializing `router`
2. then we are calling in our Mongoose `Url` model
3. next we are creating a `GET` function with `:code` as our parameter, which will have the `urlCode` sent from `shortUrl`
4. now, in a Try/Catch block we are requesting complete URL data where `urlCode` in our database `url` collection matches the sent `urlCode`.
5. then in a conditional block, if `urlCode` matches, we will fetch the `longUrl` and will redirect to it, and if not, we will send an error as a response, that `No Url found`.
6. and if any error happens while connecting to the database or fetching from the database, we will send a `Server error` as a response.

We are now all set and can copy hit the `shortUrl` in a browser and it will redirect to it's `longUrl`.

In the next part, we will setup React and build a frontend to generate short URL from long Url.

Follow me on [Twitter](https://twitter.com/the_ghost_dev) and feel free to drop me any suggestion or just to say Hi!