Build your very own URL shortener šš
Build a tiny URL shortener, using a remote database
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:
- Storing the short link, corresponding to the long URL - in our database.
- 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 š¤
Also, @dotcomboom your tutorial was referenced above :)
jsonstore.io went down! now it doesnt work
Really like this! Just a quick question, any way to delete the shortened url?
@RossJames you can use store.delete(shortUrlPath)
š
The JSONStore domain seems to have expired, this is what I see:
And no, changing the URL protocol to
https
doesn't fix it.
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:
index.js
usesDB_KEY
but this post usesKEY
. I actually changed mine toTOKEN
to match jsonstore.io- I wasn't sure what the
.env
file should look like. Here's an example (replace it with your ownTOKEN
):
TOKEN=708ca41d57c35a9b8c059f9...
- after forking, customize the
baseUrl
inscript.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
Love the fork, it's super cool you added those error checking and status code features :)
re your points:
-
In the starter repl, I've used
KEY
itself - I hadn't in the demo instance, just changed that. -
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 :) -
The shortener should work fine even without the base URL, since it uses relative links.
Thank you for typing out the feedback š
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 #
)
@gabrielsroka It does support comments using #
:)
Excellent tutorial. Can't wait to show my son!
TIL about jsonstore. Nice!
json store is dead
Id love to see a updated version using something like jsonbin or similar software! Considering jsonstore went out of business!
@jajoosam This is really cool! But I do have a few problems...
- 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)
- When I get my output, it still says l.4ty2.fun/link even though I edited the code
Could you please help?
I learn a lot with this simple but incredible approach :)
Very cool!
This is awesome!