Let’s Create A URL Shortener
Tags: node, mongodb, javascript, webdev
Published: 2020-09-23 05:52:32

Full article can be found <a href="https://taslangraham.com/2020/08/23/node-url-shortener/">here</a>.
<p>Today we will build a simple URL shortener using Node.js, MongoDb, and Vue.js.</p>
 
 <p>Chances are you've heard about or used URL shortening services like <a rel="noreferrer noopener" href="https://bitly.com/" target="_blank"></a><a href="https://bitly.com/">bitly</a>, and <a rel="noreferrer noopener" href="https://tinyurl.com/" target="_blank">tinyurl</a>.

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. </p>
 
<p>Most URL shorteners also provides features such analytics that allows you to track the number of clicks your URL has received. </p>

<p>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. </p>
 
 <p>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: </p>
 
<p>Get node <a rel="noreferrer noopener" href="https://nodejs.org/en/download/" target="_blank">here</a></p>
 
<p>Get MongoDB <a href="https://docs.mongodb.com/manual/installation/" target="_blank" rel="noreferrer noopener">here</a></p>

<p>Get Vue.js <a href="https://vuejs.org/" target="_blank" rel="noreferrer noopener">here</a></p>


<h2>Project Setup</h2>

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

<ul>
<li>create a folder named server (or give it a name of choice)</li>
<li>cd into your folder</li><li>run <strong>npm init</strong></li>
<li>install express - <strong>npm</strong> <strong>install express --save</strong></li>
<li>we'll need to setup CORS as well to allow access from our client app</li>
<li><strong>npm install cors --save</strong></li>
</ul>

<p>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.</p>
 
```
npm install mongoose --save 

npm install shortid --save 

npm install validate.js --save  
 
npm install dotenv --save 
```

<p>Create the following folder structure.</p>

<!-- wp:image {"align":"center","id":766,"sizeSlug":"large"} -->
<div class="wp-block-image"><figure class="aligncenter size-large"><img src="https://taslangraham.files.wordpress.com/2020/08/folder-structure-5.png?w=256" alt="" class="wp-image-766"/></figure></div>
<!-- /wp:image -->

<!-- wp:heading -->
<h2>Database Setup</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Lets create our database connection. Add the following to <strong>db.js</strong>. I'll be using a local MongoDB connection.</p>
<!-- /wp:paragraph -->

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

<p>Here we are setting our MongoDB connection to a local database called <strong>UrlShortner</strong>.</p>

<p>Next we'll create a model for our URLs. Update <strong>URL.js</strong> to the following.</p>


```javascript

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);
```


<p>Next, we'll add the logic to both save and to find a URL inside our <strong>urlDb.js</strong> file.</p>


```javascript
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
}; 
```

<h2>Create Express Server</h2>

<p>Now we'll setup our server. First, let's add some environment variables to our <strong>.env</strong> file.</p>
```
port = 5000
host = localhost 
```
<p>We can access these variables throughout our app using <code>process.env.</code><strong>variable_name</strong>.</p>
 
 
<p>Next we'll setup our express server inside of <strong>app.js</strong></p>
 

```javascript
 
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)); 
```

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

 
<p><strong>dotenv</strong>: allows node to read in environment variables</p>
 
 
<p><strong>body-parser</strong>: used to parse the body of incoming requests to our server.</p>
 

 
<p><strong>urlServices</strong>: will contain some logic (such as validation) for processing URLs.</p>
 

 
<p><strong>db</strong>: our database setup from the previous section.</p>
 

 
<p><strong>urlDb</strong>: contains our functions for storing and retrieving URLs.</p>
 

 
<p><strong>cors</strong>: used to allow other domains (e.g our front-end) to make request to our APIs.</p>

<p>The <code>origin: 'http://localhost:8080'</code> inside the <strong>corsOptions</strong> variable tells our app to only accepts request from that domain, which will be our client. Vue.js default port is 8080.</p>
 
 
<p>Finally, we set our server to listen on the port specified in our <strong>.env</strong> file.</p>
 
 
<p></p>
 
 
<h2>ADD OUR API Endpoints</h2>
 
<p>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 <strong>app.js</strong></p>
<!-- /wp:paragraph -->

``` javascript

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." });
    }
}); 
```
<!-- wp:paragraph -->
<p>Here we are receiving a URL as part of our request body We then validate it by using the <strong>validateUrl()</strong> function inside <strong>urlService.js</strong>. </p>
 
 
<p>We also generate a URL ID (<strong>shortUrlId</strong>) for the given URL using the <strong>generateUrlKey()</strong> function. </p>
 
<p>We then create a short link for the URL using our server hostname and the <strong>shortUrlId</strong>.</p>
<p>Next we save the URL, the short link, and the <strong>shortUrlId</strong> to our database. We then return the short link. If there's an error, we return an appropriate error message.</p>
 
<p></p>
<!-- /wp:paragraph -->

 
<h2>Services</h2>
 
 
<p>We used two functions above; <strong>validateUrl()</strong> and <strong>generateUrlKey()</strong>.  Let's create those functions. Add the following to <strong>urlServices.js</strong>.</p>
 

```javascript 
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 }; 
```
 
<p>Next step is to create an endpoint that accepts a <strong>shortUrlId</strong>, finds the <strong>shortUrlId</strong> inside our database and redirects the browser to the long URL associated with it. Add the following to your <strong>app.js</strong> </p>

```javascript

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.")
    }
}); 
```
 
<p>Our server is now ready. We can test it out using postman. Run <code>node app.js</code> to start your server. You can press Ctr+C to stop the server.</p>
 

<!-- wp:image {"align":"center","id":783,"sizeSlug":"large"} -->
<div class="wp-block-image"><figure class="aligncenter size-large"><img src="https://taslangraham.files.wordpress.com/2020/08/n.gif?w=933" alt="" class="wp-image-783"/><figcaption>Postman test of our APIs</figcaption></figure></div>
<!-- /wp:image -->

<h2>Client App</h2>

<p>We are now set to create the client side of our application. We'll be using Vue.js for this.</p>



<p>First, install the vue cli by runing: </p>
```
npm install -g @vue/cli
```

<!-- wp:paragraph -->
<p>Now, open your terminal, ensure you're outside your server director, and run the following command to create a vue app</p>
```
vue create client 
```
<p>Select the <strong>default</strong> preset or manually select in features if you'd like. Next, open up the client folder inside your code editor.</p>

<p>Delete the contents of <strong>App.Vue</strong> and delete the <strong>HelloWorld.vue</strong> file inside the components folder.</p>

<p>We'll use the following folder structure. So, create the <strong>Home.vue</strong> component inside your components folder.</p>


<!-- wp:image {"align":"center","id":786,"sizeSlug":"large"} -->
<div class="wp-block-image">
<figure class="aligncenter size-large">
<img src="https://taslangraham.files.wordpress.com/2020/08/client.png?w=256" alt="" class="wp-image-786"/>
</figure>
</div>


<p>We'll be using bootstrap for the styling. Inside the <strong>index.html</strong> file in the public folder, add the following link to bootstrap css.</p>
<!-- /wp:paragraph -->

```html

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

<p>Next, update <strong>App.vue</strong> to the following.</p>

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

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

<p>Inside your terminal, within the client, run the command <code>npm run serve</code> 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:</p>

<!-- wp:image {"align":"center","id":799,"sizeSlug":"large"} -->
<div class="wp-block-image"><figure class="aligncenter size-large"><img src="https://taslangraham.files.wordpress.com/2020/08/run-serve.png?w=716" alt="" class="wp-image-799"/></figure></div>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>Access the <strong>local </strong>link to view your app. You should see a screen with your simple nav-bar.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"align":"center","id":802,"width":373,"height":437,"sizeSlug":"large","className":"is-style-default","ampLightbox":false,"ampNoLoading":false} -->
<div class="wp-block-image is-style-default"><figure class="aligncenter size-large is-resized"><img src="https://taslangraham.files.wordpress.com/2020/08/nav.png?w=696" alt="" class="wp-image-802" width="373" height="437"/></figure></div>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>The <strong>Home.vue </strong>component will contain the form that the user will interact with as well as our app logic. Let's give it a simple design.</p>
<!-- /wp:paragraph -->

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

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

<p>You read about Vue's two way binding <a rel="noreferrer noopener" href="https://vuejs.org/v2/guide/forms.html" target="_blank">here</a>. </p>

<p>Save your changes and look at it in the browser.  You should have the following.</p>

<!-- wp:image {"id":795,"width":682,"height":370,"sizeSlug":"large","className":"is-style-default"} -->
<figure class="wp-block-image size-large is-resized is-style-default"><img src="https://taslangraham.files.wordpress.com/2020/08/client-design1-e1598134239127.png" alt="" class="wp-image-795" width="682" height="370"/></figure>
<!-- /wp:image -->

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

<!-- wp:paragraph -->
<p>Notice we are using axios to make the API calls. So lets install it.</p>
<!-- /wp:paragraph -->

```
npm install axios --save 
```
<!-- /wp:syntaxhighlighter/code -->

```javascript
<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> 
```
<!-- wp:paragraph -->
<p>Here we have a method, <strong>submit</strong>, which gets called when a user submits a URL. we make a request to our server using axios.</p> 
<p>We update the <strong>shortUrl</strong> data property with the URL returned from the sever For errors, we log them to the console. Save your changes.</p>


<!-- wp:paragraph -->
<p>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. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>If your server is no longer running, open the terminal in your server directory and run <code>node app.js</code>.</p>
<p>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. </p>
<p>Bam! Just like that, you've created your own simple URL shortener.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"id":810,"width":690,"height":720,"sizeSlug":"large"} -->
<figure class="wp-block-image size-large is-resized"><img src="https://taslangraham.files.wordpress.com/2020/08/final-1.gif?w=950" alt="" class="wp-image-810" width="690" height="720"/></figure>

<a href="https://ibb.co/LhRyM2S"><img src="https://i.ibb.co/1JQN3wZ/final.gif" alt="final" border="0"></a> 
<p>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 <strong>http://localhost:5000/</strong> as a parameter.</p> 
<p>It then searches our database for the random string and redirects the browser to the original URL associated with it.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Hope you enjoyed this. Leave your thoughts in the comments. Until next time, <em>Think, learn, create, repeat</em>! </p>
<!-- /wp:paragraph -->

<p>Found this article useful?</p>
<!-- wp:image {"align":"center","id":838,"width":264,"height":61,"sizeSlug":"large","className":"is-style-default"} -->
<div class="wp-block-image is-style-default"><figure class="aligncenter size-large is-resized"><a href="https://www.buymeacoffee.com/taslan"><img src="https://taslangraham.files.wordpress.com/2020/09/default-orange.png?w=434" alt="" class="wp-image-838" width="264" height="61"/></a></figure></div>
<!-- /wp:image -->

<!-- wp:paragraph -->

<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><a rel="noreferrer noopener" href="https://www.buymeacoffee.com/taslan" target="_blank"></a></p>
<!-- /wp:paragraph -->

<br/>
Read more articles by Taslan at www.taslangraham.com