Let’s Create A URL Shortener

Today we will build a simple URL shortener using Node.js, MongoDb, and Vue.js.

Chances are you’ve heard about or used URL shortening services like bitly, and tinyurl. These services allows you to enter long (and quite ugly) URLs and in turn gives you a very short (more appealing) link which you can use instead of the long URL. Shorter links takes up less space and are easier to share and type.

Most URL shorteners also provides features such analytics that allows you to track the number of clicks your URL has received. However, in this tutorial we will focus on the main purpose of a URL shortener, shortening the URL. As such, our application will be simple. We will receive a lengthy URL and return a short link.

We’ll use node.js and MongoDB on the backend, and Vue.js to create the client app. Make sure you have these installed on your computer:

Get node here

Get MongoDB here

Get Vue.js here

Project Setup

To kick things off we will create our nodejs/express server

  • create a folder named server (or give it a name of choice)
  • cd into your folder
  • run npm init
  • install express – npm install express –save
  • we’ll need to setup CORS as well to allow access from our client app
  • npm install cors –save

We need to install some more packages as well. We need mongoose to connect with our MongoDB database; shortid to create short unique strings, and we need validate.js to validate the URLs received, and finally we need dotenv to load environment variables. run the following commands.

npm install mongoose --save
npm install shortid --save
npm install validate.js --save 
npm install dotenv --save

Create the following folder structure.

Database Setup

Lets create our database connection. Add the following to db.js. I’ll be using a local MongoDB connection.

const mongoose = require("mongoose");
mongoose.connect("mongodb://localhost/UrlShortener", {
    useNewUrlParser: true,
    useUnifiedTopology: true
});
mongoose.set('useCreateIndex', true)

Here we are setting our MongoDB connection to a local database called UrlShortner.

Next we’ll create a model for our URLs. Update URL.js to the following.

const mongoose = require("mongoose");

const urlSchema = new mongoose.Schema({
    longURL: {
        type: String,
        required: true
    },
    shortURL: {
        type: String,
        required: true,
    },
    shortUrlId: {
        type: String,
        required: true,
        unique: true
    }
});

module.exports = mongoose.model("URL", urlSchema);

Next, we’ll add the logic to both save and to find a URL inside our urlDb.js file.

const Url = require("../models/Url");

const save = (longURL, shortURL, shortUrlId) => {
    Url.create({ longURL, shortURL, shortUrlId })
};

const find = (shortUrlId) => Url.findOne({ shortUrlId: shortUrlId });

module.exports = {
    save,
    find
};

Create Express Server

Now we’ll setup our server. First, let’s add some environment variables to our .env file.

port = 5000
host = localhost

We can access these variables throughout our app using process.env.variable_name.

Next we’ll setup our express server inside of app.js

const express = require('express');
const app = express();
const cors = require('cors');
require('dotenv').config()
const port = process.env.port;
const host = process.env.host;
const bodyParser = require("body-parser"); //use to parse incoming request bodies


const urlServices = require("./services/urlServices");
const db = require("./data-access/db");
const urlDb = require("./data-access/urlDb");

const corsOptions = {
    origin: 'http://localhost:8080',
    optionsSuccessStatus: 200
}

app.use(cors(corsOptions))
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

app.listen(port, () => console.log("listening port " + port));

Here we are setting up a our basic server as well as requiring the needed packages and files such as:

dotenv: allows node to read in environment variables

body-parser: used to parse the body of incoming requests to our server.

urlServices: will contain some logic (such as validation) for processing URLs.

db: our database setup from the previous section.

urlDb: contains our functions for storing and retrieving URLs.

cors: used to allow other domains (e.g our front-end) to make request to our APIs. The origin: 'http://localhost:8080' inside the corsOptions variable tells our app to only accepts request from that domain, which will be our client. Vue.js default port is 8080.

Finally, we set our server to listen on the port specified in our .env file.

 

ADD OUR API Endpoints

Next we’ll create an endpoint that accepts a URL, stores it along with the shortened version and returns the shortened version to the user. Add the following to your app.js

app.post("/url", async (req, res) => {
    try {
        if (!!urlServices.validateUrl(req.body.url))
            return res.status(400).send({ msg: "Invalid URL." });

        const urlKey = urlServices.generateUrlKey();
        const shortUrl = `http://${host}:${port}/${urlKey}`

        await urlDb.save(req.body.url, shortUrl, urlKey)
        return res.status(200).send({ shortUrl });

    } catch (error) {
        return res.status(500).send({ msg: "Something went wrong. Please try again." });
    }
});

Here we are receiving a URL as part of our request body We then validate it by using the validateUrl() function inside urlService.js.

We also generate a URL ID (shortUrlId) for the given URL using the generateUrlKey() function.

We then create a short link for the URL using our server hostname and the shortUrlId. Next we save the URL, the short link, and the shortUrlId to our database. We then return the short link. If there’s an error, we return an appropriate error message.

 

Services

We used two functions above; validateUrl() and generateUrlKey(). Let’s create those functions. Add the following to urlServices.js.

const validate = require("validate.js");
const shortId = require("shortid");

const validateUrl = (url = "") => {
    return validate({ website: url }, {
        website: {
            url: {
                allowLocal: true
            }
        }
    });
}

const generateUrlKey = () => shortId.generate();

module.exports = { validateUrl, generateUrlKey: generateUrlKey };

Next step is to create an endpoint that accepts a shortUrlId, finds the shortUrlId inside our database and redirects the browser to the long URL associated with it. Add the following to your app.js file.

app.get("/:shortUrlId", async (req, res) => {
    try {
        const url = await urlDb.find(req.params.shortUrlId);
        return !url ? res.status(404).send("Not found") : res.redirect(301, url.longURL)

    } catch (error) {
        return res.status(500).send("Something went wrong. Please try again.")
    }
});

Our server is now ready. We can test it out using postman. Run node app.js to start your server. You can press Ctr+C to stop the server.

Postman test of our APIs

Client App

We are now set to create the client side of our application. We’ll be using Vue.js for this.

First, install the vue cli by runing:

npm install -g @vue/cli

Now, open your terminal, ensure you’re outside your server director, and run the following command to create a vue app

vue create client

Select the default preset or manually select in features if you’d like. Next, open up the client folder inside your code editor.

Delete the contents of App.Vue and delete the HelloWorld.vue file inside the components folder.

We’ll use the following folder structure. So, create the Home.vue component inside your components folder.

We’ll be using bootstrap for the styling. Inside the index.html file in the public folder, add the following link to bootstrap css.

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">

Next, update App.vue to the following.

<template>
  <div>
    <nav class="navbar navbar-dark bg-dark">
      <a class="navbar-brand" href="#">Shortly</a>
    </nav>
    <div class="container">
      <home />
    </div>
  </div>
</template>

<script>
import Home from "./components/Home.vue";
export default {
  name: "App",
  components: {
    Home,
  },
};
</script> 

We added a simple nav bar as well as importing in the Home.vue component and rendered it inside our container div.

Inside your terminal, within the client, run the command npm run serve to start you vue app. Vue uses hot reload, so you’ll only need to run this command once and your app will update each time you make a change and save. You should see similar output to this:

Access the local link to view your app. You should see a screen with your simple nav-bar.

The Home.vue component will contain the form that the user will interact with as well as our app logic. Let’s give it a simple design.

<template>
  <div>
    <div class="row">
      <div class="col col-12 offset-0 mt-2">
        <h1 class="jumbotron text-center text-white bg-primary">Create Click-Worthy Links</h1>
      </div>
    </div>

    <div class="col col-8 align-middle mt-5 offset-2">
      <div class="card">
        <div class="card-body">
          <form @submit.prevent="submit(url)">
            <div class="form-group">
              <label for="url">Enter Url</label>
              <textarea type="url" class="form-control" v-model="url" style="height:150px" />
            </div>
            <div class="for-group" v-show="shortUrl">
              <p>
                Short URL: :
                <a :href="shortUrl" class="text-primary">{{shortUrl}}</a>
              </p>
            </div>
            <div class="form-group">
              <button class="btn btn-primary" type="submit">Shorten URl</button>
            </div>
          </form>
        </div>
      </div>
    </div>
  </div>
</template>

We’ve created a simple design here. Notice the use of Vue’s v-model on our <textarea> tag for two way data binding. This will automatically store the user input in a data property call url.

You read about Vue’s two way binding here.

Save your changes and look at it in the browser. You should have the following.

Now let’s add the logic to submit a URL to our server and receive a shortened URL. Add the following <script> tag to Home.vue. Ensure that it is outside of the <template> tag.

Notice we are using axios to make the API calls. So lets install it.

npm install axios --save
<script>
import axios from "axios";
export default {
  data: () => {
    return {
      url: "",
      shortUrl: "",
    };
  },
  methods: {
    submit: async function (url) {
      try {
        const api = "http://localhost:5000/url";
        const response = await axios.post(api, {
          url,
        });
        this.shortUrl = response.data.shortUrl;
      } catch (error) {
        console.log(error);
      }
    },
  },
};
</script>

Here we have a method, submit, which gets called when a user submits a URL. we make a request to our server using axios. We update the shortUrl data property with the URL returned from the sever For errors, we log them to the console. Save your changes.

With the client app completed, we are now ready to fully test our URL shortener app. We need both our server and client app running.

If your server is no longer running, open the terminal in your sever directory and run node app.js. Now open the client app in the browser, choose a long URL of your choice, submit it via the form and click the URL that is returned to you. You should be redirected to the original site. Bam! Just like that, you’ve created your own simple URL shortener.

We’ve successfully created a simple URL shortener. When we click on the short link returned by the API, we are making another request to our server which accepts the random string that comes after http://localhost:5000/ as a parameter. It then searches our database for the random string and redirects the browser to the original URL associated with it.

Hope you enjoyed this. Leave your thoughts in the comments. Until next time, Think, learn, create, repeat!

 

 

About the Author

4 thoughts on “Let’s Create A URL Shortener

  1. I have to thank you for the efforts you have put in penning
    this blog. I am hoping to see the same high-grade content by you in the future as well.

    In fact, your creative writing abilities has encouraged me to get my own website now ;
    )

Leave a Reply

Your email address will not be published. Required fields are marked *

You may also like these