Learn to Code via Tutorials on Repl.it!

← Back to all posts
Build your very own URL shortener 🔗🚀
h
jajoosam (692)

Build a tiny URL shortener, using a remote database

Demo ⏯️ Code 👨‍💻

Setting up a URL shortener is a lot of work - either you have to pay, or spend hours setting up your own server.

This is a guide to making your own URL shortener with repl.it - using express, and a remote database - all on node.js

🛠️ Getting our environment running

First up, fork the https://repl.it/@jajoosam/tyni-starter repl, so that you have a running project. Next, create a new file - .env

A .env file can store secrets for you, that no one else will be able to see. This is where we want to store our token for accessing the remote database.

📁 Making our database

We're going to be using jsonstore.io for storing all our URLs.

Head over to jsonstore.io/get-token - and copy the token you see - this is the secret we want to store in our .env file.

Open up your .env file, and set KEY to your token, like this 👇

KEY=yourTokenGoesHere

Remember to keep no whitespace, or your token might not be recognized right!

When you open index.js you'll see that I've already initialized the database, and a small web server for you. Now let's get to making our API so we can shorten them URLs 🚀

👨‍💻 The API

There are two parts to our URL shortener:

  1. Storing the short link, corresponding to the long URL - in our database.
  2. Redirecting visitors from the short link to the long link

Both of these are super simple to implement, thanks to the express server we're using - we'll just be collecting get requests for both of the tasks.

For adding links to our database, we have a special endpoint - requests to it have two parts: the long URL, and the short path.

app.get('/shorten', (req, res) => {
	db.write(req.query.key, {"url": req.query.url});
	res.status(200);
});

Adding this to our code lets us correspond the short path (key) to the long url, and then we finally send a successful response.

For the second task, we'll just be collecting the short path (key) from a request, finding the corresponding URL in our database, and then redirecting our visitor ⬇️

app.get('/:key', (req, res) => {
	db.read(req.params.key + "/url").then( (url) => {
		res.redirect(url);
	});
}); 

That's prety much it - we have a fully functional URL shortener 🤯 - check it out for yourself, open a new URL which looks like this 🔗

https://tyni.jajoosam.repl.co/shorten?key=yay&url=https://dsh.re/50854

Now, going to http://tyni.jajoosam.repl.co/yay will be nice to see 👇

Of course, you'll be replacing tyni.jajoosam in those URLs with your own repl!

✨ The Frontend

Our URL shortener does work, but it's tedious, having to type out a huge URL before shortening it - we can make the whole process much simpler with a simple frontend.

I've already created this - and gone for a neat and minimal look using wing.css

You just have to add code to send visitors to the hompage in the static folder 🏠

app.get('/', (req, res) => {
	res.sendFile("static/index.html", {root: __dirname});;
});

If you browse through the static folder, you'll find a simple HTML file with a form, CSS to style our page, and most importantly, JS to send requests to our URL shortening API.

The HTML is quite straightforward, we're asking for the long URL, and optionally a short path.

Open up script.js and you'll see the shorten() function.

Here's what the JS file does (I've also annotated the code with comments) 👇

🔍 Getting the path(key) and the long url from the form.

📝 Possibly generating a random 5 character hash as our path (in case there's no path entered)

🔗 Sending a get request to our API, with our key and url as parameters

🖥️ Displaying the shortened URL on our page

🌐 Getting our custom domain

Sure, our links are shorter - but we still don't have them on our own domain, and the repl.co links can be pretty long 👀

Luckily for us, the folks at repl.it recently allowed custom domains to be used! That means this project could be something you actually use all the time 😄

Check out dotcomboom's guide on using custom domains, it should only take a few minutes. It also teaches you about getting free domains 💸

Be sure to put down any questions or improvements down in the comments 💬 - and here's all the code for you to go over again 🤖

https://repl.it/@jajoosam/tyni

Commentshotnewtop
timmy_i_chen (1071)

Also, @dotcomboom your tutorial was referenced above :)

RossJames (266)

Really like this! Just a quick question, any way to delete the shortened url?

jajoosam (692)

@RossJames you can use store.delete(shortUrlPath) 😉

gabrielsroka (1)

@jajoosam,

Thanks for posting this -- it's great!

I had a few minor issues, so here's a heads-up to anyone else who wants to try it:
1. index.js uses DB_KEY but this post uses KEY. I actually changed mine to TOKEN to match jsonstore.io
2. I wasn't sure what the .env file should look like. Here's an example (replace it with your own TOKEN):
TOKEN=708ca41d57c35a9b8c059f9...
3. after forking, customize the baseUrl in script.js line 2, eg:

const baseUrl = "tyni.gabrielsroka.repl.co";

I also added some error checking (both server- and client-side) and a few, minor features. See my fork: repl.it/@gabrielsroka/tyni

jajoosam (692)

@gabrielsroka

Love the fork, it's super cool you added those error checking and status code features :)

re your points:
1. In the starter repl, I've used KEY itself - I hadn't in the demo instance, just changed that.

  1. Yes, this is something I've seen other people struggle with too. It'd be dope if forks would also clone the env, except it'd be unpopulated - so the key names and comments will remain, just without the actual values.
    @amasad: Feature suggestion :)

  2. The shortener should work fine even without the base URL, since it uses relative links.

Thank you for typing out the feedback 😄

gabrielsroka (1)

@jajoosam,

Thanks for the comments. The doc page https://repl.it/site/docs/repls/secret-keys has an example repl https://repl.it/@timmy_i_chen/python-dotenv-example made by @timmy_i_chen that uses a "template" file called env. After forking, the user is supposed to rename it to .env. This is a good approach.

Another idea would be to use comments in the env or .env, eg:

# Get a token by going to https://www.jsonstore.io and pasting it below
TOKEN=708ca41d57c35a9b8c059f9...

Do we know if .env supports comments? (eg using #)

timmy_i_chen (1071)

@gabrielsroka It does support comments using # :)

ThomasHagan (1)

Excellent tutorial. Can't wait to show my son!

timmy_i_chen (1071)

TIL about jsonstore. Nice!

retronbv (2)

jsonstore.io went down! now it doesnt work

AgastyaSandhuja (144)

@jajoosam This is really cool! But I do have a few problems...
1) I get this error even though I followed the instructions:

    at fetch (/home/runner/tyni-url-shortener/node_modules/jsonstore.io/index.js:54:20)
    at FetchStream.<anonymous> (/home/runner/tyni-url-shortener/node_modules/fetch/lib/fetch.js:428:13)
    at FetchStream.emit (events.js:198:13)
    at FetchStream.<anonymous> (/home/runner/tyni-url-shortener/node_modules/fetch/lib/fetch.js:321:22)
    at IncomingMessage.emit (events.js:203:15)
    at endReadableNT (_stream_readable.js:1145:12)
    at process._tickCallback (internal/process/next_tick.js:63:19)
(node:455) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:455) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
(node:455) UnhandledPromiseRejectionWarning: Error: Token is incorrect, unable to read data.
    at fetch (/home/runner/tyni-url-shortener/node_modules/jsonstore.io/index.js:54:20)
    at FetchStream.<anonymous> (/home/runner/tyni-url-shortener/node_modules/fetch/lib/fetch.js:428:13)
    at FetchStream.emit (events.js:198:13)
    at FetchStream.<anonymous> (/home/runner/tyni-url-shortener/node_modules/fetch/lib/fetch.js:321:22)
    at IncomingMessage.emit (events.js:203:15)
    at endReadableNT (_stream_readable.js:1145:12)
    at process._tickCallback (internal/process/next_tick.js:63:19)
(node:455) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)

2) When I get my output, it still says l.4ty2.fun/link even though I edited the code

Could you please help?

saiphp (0)

I learn a lot with this simple but incredible approach :)