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:
- Add eslint for linting:
npm i -D eslint
npx init eslint
- If using VS Code add extension https://tinyurl.com/vs-rest-client for testing purposes
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}`));
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');
- 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);
3. Design data structure to keep track of the shortened URLs
const hashes = new Set();
const urlHashes = {};
Example data:
Set(5) { 'google', 'a3', '76', '60', 'cc' }
{ 'http://google.com': [ 'google', 'a3', '76', '60', 'cc' ],
'https://dev.to/: [ 'ge', 'dev' ] }
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];
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}`);
-
GET
/:hash
- retrieve shortened URL and redirect
const url = Object.keys(urlHashes).find((u) => urlHashes[u].includes(hash));
return res.redirect(url);
5. Deal with edge-cases
/shorten
-
HTTP 403
- no URL provided -
HTTP 403
-key
provided already exists -
HTTP 403
-hash
orkey
already exists -
HTTP 500
- timed out while creating a uniquehash
/: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"
}
### POST without a key
POST http://localhost:3000/shorten
content-type: application/json
{
"url": "http://google.com"
}
### GET existing URL
GET http://localhost:3000/google
### GET non-existent URL
GET http://localhost:3000/googlenonexistent
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)
so
good job on your first article.
a few ideas:
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!
It's not quite complicated.
Thanks, speaking of: I am wondering on more complex projects how much detail should be provided / how high-level should the article be
True, my response was more about the reasoning behind it and not necessarily the article's implementation.