Making a URL shortener in Python
by Ryan Yang
The finished code can be found here. To run it, you need to setup Flask. Once that's done, you can type flask run in your terminal to start the webserver
Intro
Intro
This tutorial assumes basic knowledge of Python3 (variables, functions, if-statements, dictionaries, import python modules).
You may have heard of services like bit.ly called URL shorteners whose job is to take a long link, and generate a shorter alias for it.
Here, we will write a URL shortener service in Python. This article is meant for students who have dabbled with Python and are exploring ways to apply that knowledge to real-world applications. We'll be touching on setting up a webserver using Flask, serving webpages, understanding http codes, and some basic software design fundamentals.
Ideally, you'll be able to see the process a developer goes through when writing software and learn enough from the system so you have some direction when you work on your own project!
Background info
Background info
If you want to dive straight into code, you can go straight to Installation and Setup. Otherwise, I would recommend you read an in-depth explanation of the design of our system first.
We are essentially developing this:
This is what our code will be doing in the back:
Except our "database" will just be a dictionary variable in memory.
Picking our tools
Picking our tools
In our design doc, we realized we needed a web application server. In Python, there are 2 main web application server libraries:Flask and Django.
We will be using Flask due to its simplicity but you may want to look at a more in-depth breakdown between the two tools when you're doing professional development.
Installation and Setup
Installation and Setup
We need to first install Python3 and Flask, and setup our boilerplate code. Go ahead and hop here first and follow these instructions until the Where do we go from here? section.
Ready to start coding
Ready to start coding
After following the steps from the link above, you should end up with this basic structure:
File Structure
url-shortenerapp.py
app.py
and when you run the command flask run in the terminal, you get this:
terminal
and when you open up http://127.0.0.1:5000/ in the browser (any browser of your choice), you should see something like this:
If you've made it this far, then that means you've set up Python and Flask correctly! The code that we just ran is pretty much the most barebones Flask application you can have.
Notice that we only wrote 5 lines of code but when we ran it, our code spun up an entire webserver that immediately started listening for requests on the "internet" and responded to a request when we accessed http://127.0.0.1:5000/ in the browser. If you knew what you were doing, this setup takes less than a minute. This is why we (and companies like Lyft and Netflix) reach for a library like Flask when we need to run a webserver.
One thing to note, companies like Lyft and Netflix likely aren't using Flask as a webserver for their main product, but rather for their internal tools. This is because Flask is valued for its simplicity and ability to prototype quickly. This makes Flask the tool of choice for educational tutorials (like this one), hackathons, and internal tooling. If you wanted a powerful and robust webserver expected to serve thousands of different webpages and millions of requests, you may want to reach for Django instead - but you should look at a more in-depth comparison between the tools first.
Writing the business logic
Writing the business logic
We're going to set the web server code aside for a quick second and work on the business logic. If you're not familiar with that term, business logic simply means the core set of code that solves the main problem our app tackles. (i.e. core algorithms)
Going back to the design document, our app is a URL shortener, but all of the setup we did with Flask actually has nothing to do with shortening URLs. In other words, despite being a vital part of our app (by being a medium for users to access our product and redirects short links), our Flask code doesn't actually do any link shortening. This is why our Flask code we wrote just now isn't considered "business logic" and should be separate from it.
We could write all of our code in one file - it would be okay since our codebase is going to be fairly small - but we should try to follow some good programming practices and splitting different pieces of logic into different files is vital to any codebase.
With that in mind, let's go ahead and create a new file in the same folder called business_logic.py and here we'll first list out all of the functions we expect to have.
File Structure
url-shortenerapp.pybusiness_logic.py
business_logic.py
Generating a short alias
Generating a short alias
Generating a short alias has 3 parts to it: Generating an unused random string (we can call this a hash), saving the <hash, long_link> pair, and returning the full aliased url. It would look something like this:
business_logic.py
Notice that in our link_mappings, we are storing the hash, rather than the entire url. This is because we know that every short url will be prepended with our server url - in this example it's https://short.com/. (don't actually go on this site, it's a fake domain i made up)
By not storing the whole link, not only do we save space in memory, we also solve another problem which is comparing URLS. https://short.com/d63hs, https://short.com/d63hs/, and http://short.com/d63hs are all "technically" the same URL but have different string values. However, we do know that they all have the same route: [d63hs] which is much easier to compare.
Get link from alias
Get link from alias
Getting the link is actually pretty simple, thankfully. Since we have a dictionary mapping of short alias id's to their original links, we just need to get the corresponding value of a key, like so:
Access dictionary value in Python with key
Our get_long_link function should end up looking like this:
business_logic.py
Finishing up business logic
Finishing up business logic
We can test our business_logic.py by adding this snippet at the end of our file:
business_logic.py
which simply says if we run this file by itself, execute those commands. Otherwise (like when we import it as a module for our Flask application), don't.
After running the file by itself in the terminal, we get this:
Understanding servers and web requests
Understanding servers and web requests
Now that we know our business logic works, we can now start attaching our Flask routes to our core algorithms. But first let's understand conceptually how our web server will work.
Our webserver is essentially a python program always running on a computer connected to the internet and it's constantly listening for requests. An example of a request is when a user tries to access our short link. When they access https://short.com/x1GZrT in their favorite browser, that browser is making a GET request (type of HTTP request) to our server that's hosted on https://short.com through the /x1GZrT route. Since we are writing the server, we're can return any type of data we want in our response.
In our app, we will return 2 different types of responses: First, we will return HTML code - essentially a website - to people who want to make their own short link. Second, we will return a URL Redirection response to people who are accessing short links.
Writing the Flask code
Writing the Flask code
Creating a web interface
Creating a web interface
I'm going to skip over the details of how to write a website for now (I have other tutorials on web dev). All you need to know is what the index.html code does. After the users type their long link into the textbox and submits it, their browser will send a POST request to our server at the /create endpoint through a form object. The way Flask serves websites is through a templating engine called Jinja and their tutorial on how to utilize templating is actually pretty simple so I would check that out as well. Once again, the purpose of this write up isn't to learn web development but to see how one can implement a service in Python.
To serve websites in Flask, we're going to use a function called render_template that takes in an HTML file and a list of variables. We'll embed variables later on but for now, we just want to return a simple website that has a text input and a shorten! button. By default, Flask will look for templates in the /templates folder in our filesystem. In addition to the HTML, we need to add some CSS styles so our website doesn't look THAT ugly. We'll put these in a /static folder and you can just copy and paste these until you're more familiar with front-end web development.
File Structure
url-shortenertemplatesindex.htmlstaticstyles.cssapp.pybusiness_logic.py
templates/index.html
static/styles.css
In our Flask code, instead of returning the string "Hello World!" as we had before, we return a pretty website!
app.py
I mentioned earlier that when someone submits their long link into our website, their browser will make a POST request to the /create endpoint. This request will contain form data in this format { link: "user_link_input_here" }. Thus, we can setup a route in our Flask server to handle this request like so:
app.py
Let's go through this line by line.
On line 7: we define the /create route and specify it'll handle POST requests
On line 9: we get the current url of the server. (right now it should be http://localhost:5000)
On line 10: we are getting the user input value from the form data.
On line 11: we invoke the function we wrote earlier in business_logic.py
On line 12: we return an HTML file again, except this time we pass in the generated alias into the template to be displayed
Since we're returning a different webpage, let's create result.html and define it like so:
File Structure
url-shortenertemplatesindex.htmlresult.htmlstaticstyles.cssapp.pybusiness_logic.py
templates/result.html
Redirecting
Redirecting
Now that we've handled how a user will generate a short link, let's go ahead and implement the redirection process.
app.py
Ok. What does this all do?
On line 4: We setup something called a "wildcard" route. It takes the text that would go there and stores it in a variable called alias.
I think seeing examples will help demonstrate how these wildcard routes work:
@app.route('/<alias>') | @app.route('/<alias>/there') | |
---|---|---|
will match: | / hey | / hey / there |
/ this-super-long-text | / boop / there | |
will NOT match: | / hey / there | / hey |
On line 6: Instead of returning HTML like we've done before, we will return a redirect response with a HTTP code 301. You can read the documentation for this function here
Final result
Final result
Our final result should look like this:
File Structure
url-shortenertemplatesindex.htmlresult.htmlstaticstyles.cssapp.pybusiness_logic.py
templates/result.html
templates/index.html
static/styles.css
app.py
business_logic.py