Make a URL shortener in Node.js!

kacperturon

Kacper Turon

Posted on February 14, 2023

Make a URL shortener in Node.js!

Intro

I will show you how to create a simple and efficient basic URL shortener in Node.js with Express.js - it is a great personal project for learning with the bonus of being easily adaptable and expandable.

Full code: https://github.com/kacperturon/urlshortener

Prerequisites:

Create new Node project npm init -y install Express.js npm i express add to package.json to scripts a 'start' script start: "node index.js"

Optionally:


1. Set up the server and 2 endpoints

app.post('/shorten', (req, res) => { 
  const { url, key } = req.body;
  ...
}
app.get('/:hash', (req, res) => {
  const { hash } = req.params;
  ...
}
app.listen(port, () => console.log(`Server: URL shortener started on port ${port}`));
Enter fullscreen mode Exit fullscreen mode

2. Create helpers

  • Generate a hash of a specified length:
const getRandomHash = (len) => crypto.createHash('shake256', { outputLength: len }).update(crypto.randomBytes(20).toString('hex')).digest('hex');
Enter fullscreen mode Exit fullscreen mode
  • Calculate amount of possible combinations 26 [a-z] + 10 [0-9] ^ len + 1 (getRandomHash is in bytes so we add 1):
const possibleHashCombinations = (len) => (26 + 10) ** (len + 1);
Enter fullscreen mode Exit fullscreen mode

3. Design data structure to keep track of the shortened URLs

const hashes = new Set();
const urlHashes = {};
Enter fullscreen mode Exit fullscreen mode

Example data:

Set(5) { 'google', 'a3', '76', '60', 'cc' }
{ 'http://google.com': [ 'google', 'a3', '76', '60', 'cc' ],
  'https://dev.to/: [ 'ge', 'dev' ] }
Enter fullscreen mode Exit fullscreen mode

alternative approach: instead of having the key be the URL have the key be the hash, this will greatly impact our memory usage but also will greatly increase performance.


4. Design two endpoints

  • POST /shorten - shorten the received URL given a key or by generating a random hash

Generate hashes until one is unique or execution has timed out after 10s:

setTimeout(() => { halt = true; }, 1000 * 10);
do {
    hash = getRandomHash(hashLength);
    if (halt) return res.sendStatus(500);
} while (hashes.has(hash) || hash === null);
hashes.add(hash);
urlHashes[url] = urlHashes[url] ? [...urlHashes[url], hash] : [hash];
Enter fullscreen mode Exit fullscreen mode

alternative approach: pre-generate hashes on server launch and immediately return the first free hash.

If all combinations are used up increase the hashLength and recalculate:

combinationsAvailable -= 1;
if (combinationsAvailable === 0) {
    hashLength += 1;
    combinationsAvailable = possibleHashCombinations(hashLength);
}
return res.send(`${domain}/${hash}`);
Enter fullscreen mode Exit fullscreen mode
  • GET /:hash - retrieve shortened URL and redirect
  const url = Object.keys(urlHashes).find((u) => urlHashes[u].includes(hash));
  return res.redirect(url);
Enter fullscreen mode Exit fullscreen mode

5. Deal with edge-cases

/shorten

  • HTTP 403 - no URL provided
  • HTTP 403 - key provided already exists
  • HTTP 403 - hash or key already exists
  • HTTP 500 - timed out while creating a unique hash

/:hash

  • HTTP 404 - the URL for the hash does not exist

6. Test the endpoints

### POST with a specified key
POST http://localhost:3000/shorten
content-type: application/json

{
    "url": "http://google.com",
    "key": "google"
}
Enter fullscreen mode Exit fullscreen mode

POST with specified key

### POST without a key
POST http://localhost:3000/shorten
content-type: application/json

{
    "url": "http://google.com"
}
Enter fullscreen mode Exit fullscreen mode

POST without a key

### GET existing URL
GET http://localhost:3000/google
Enter fullscreen mode Exit fullscreen mode

GET existing URL - partial response

### GET non-existent URL
GET http://localhost:3000/googlenonexistent
Enter fullscreen mode Exit fullscreen mode

GET non-existent URL


7. Expandability ideas:

  • Allow multiple URLs to be shortened at once
  • Combine it with a Discord bot to pass users shortened URLs in chat
  • On stopping the server create a backup of hashes and URLs and restore on launch
  • Add full testing suite i.e. JEST or MOCHA

Conclusion

We went through the steps of creating a preliminary version of the URL shortener service, how to approach the design, split the work into endpoints and decide on a data structure, creating a great starting point for more specialised use cases.



Please let me know what you think of this article since this is my first one! Is it too simple? too short? valuable? good for new engineers?

What else would be interesting in the future:

  • expanding this project?
  • creating clones of i.e. Leetcode or Youtube or Twitter..
  • focusing on actual Leetcode problems
  • improvement ideas for programming React, microservices?
  • system design work?

Any and all feedback is appreciated in these early stages of my article writing!

💖 💪 🙅 🚩
kacperturon
Kacper Turon

Posted on February 14, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related