DEV Community

Kacper Turon
Kacper Turon

Posted on

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!

Top comments (6)

Collapse
 
kacperturon profile image
Kacper Turon

so

  1. you'd want to pass key to make custom url instead of getting a random hash, something more human-readable i.e. xxx.com/vid-about-dogs -> youtube.com/90j09dajojsa
  2. This point is debatable, but the intention is that your url hashes are personal, so if this were expanded you could have your set of hashes which you could delete for privacy reasons or change its hash and you would not want random people to be changing where your hash points to
Collapse
 
vairasza profile image
Michael Bierschneider

good job on your first article.
a few ideas:

  • i would return status 400 if parameter url is missing. 403 is about authorisation.
  • i think cycling through free hashes every time is a waste of time. and theoretical timeouts for such a simple function is bad. keeping a map of pregenerated hashes on startup and just picking the next free is an optimisation.
  • rather than „recreating“ what others have done multiple times (we have seen a lot of basic url shortener guides), i think that altering your article would be interesting. platforms such as bitly do not only shorten urls but offer a lot more. maybe analyse a feature and interpret it differently. compare approaches between different url shortener apps. your design/thinking process would be more appealing to read and encouraging for discussion.
Collapse
 
kacperturon profile image
Kacper Turon

Thanks for the comment, I agree with 1 & 3, 2 though I have mentioned as 'alternative approach' on the last point, I am learning how to write articles so I am trying to weigh that in when it comes to the idea of the article!

Collapse
 
richardrichardson profile image
RichardRichardson

It's not quite complicated.

Collapse
 
kacperturon profile image
Kacper Turon

Thanks, speaking of: I am wondering on more complex projects how much detail should be provided / how high-level should the article be

 
kacperturon profile image
Kacper Turon

True, my response was more about the reasoning behind it and not necessarily the article's implementation.