In this part of the series we’ll be talking about routing, URLs, requests and status codes in Flask. Let’s start with routing.
You will find the code for this part on Github.
Table of Contents
Static Routes
As far as routing is concerned, we’ve already had some experience with it in part 1. We created a simple route there:
@app.route('/')
def index():
return 'Hello World'
Let’s break it down. Turns out, in order to create a route, we need a couple elements:
1) To create a route we define a function that will be called when we try to reach a given endpoint.
2) We use the @app.route
decorator, to which we pass as an argument the endpoint. It always starts with a slash.
3) The method returns a string or some HTML that is rendered on the page.
Now that we know how to create new routes, let’s add another endpoint below:
@app.route('/help')
def help():
return '<h2>Help - learn how to use this page!</h2>'
This time the function should return some text formatted with HTML tags. If you run the app and navigate to the endpoint you defined, you will see the output:
We can add as many routes like that as we want. These are all static routes, which means they will render the same content each time. But what if we need dynamic content, which is often the case?
Dynamic Routes
In this section we’re going to discuss just the GET requests. We’re going to discuss other methods like POST, PUT and DELETE in a later section.
So, back to our question…
URL Processors
Well, if we need dynamic content, we can use URL processors by specifying dynamic variables in angled brackets. For example, we could use an id or name. We also add the variable as a parameter to the function. So, let’s create a route with an id:
@app.route('/show/<id>')
def show_id(id):
return f'The selected id is {id}. Recommended format: p_{id}.'
Now if we run the app, the content displayed in the page will depend on the id we enter:
URL processors accept any number of variables. Let’s create a route with three parameters:
@app.route('/introduce/<name>/<city>/<age>')
def introduce(name, city, age):
return f'This is {name} from {city}, aged {age}.'
This is what it looks like:
We have to remember, the variables are always considered strings, though. Have a look:
@app.route('/sum/<num1>/<num2>')
def add_numbers(num1, num2):
return f'{num1} + {num2} = {num1 + num2}'
This is what we get:
This is because with strings the plus operator performs concatenation instead of addition. If we want to treat the variables as numbers, we first have to typecast them to numbers:
@app.route('/sumcast/<num1>/<num2>')
def add_numbers_cast(num1, num2):
return f'{num1} + {num2} = {int(num1) + int(num2)}'
Now we get the expected result:
An alternative way to do it is to specify the type directly in the route by preceding the variable name by the name of the type and a colon. The route will then only work if we deliver parameters of that specified type. In our example, let’s specify the types of both variables to be int:
@app.route('/sumtype/<int:num1>/<int:num2>')
def add_numbers_type(num1, num2):
return f'{num1} + {num2} = {num1 + num2}'
Now it will work the same:
If we deliver non-integer parameters, an error will be thrown:
URL Parameters
Besides URL processors, we can use regular URL parameters. They are added directly in the URL after a question mark (?) and separated by the ampersand symbol (&) if there are more of them.
To use URL parameters, we have to import request from the flask module, so let’s add it at the top of the file:
from flask import Flask, request
Now we can create a route using URL parameters, for example:
@app.route('/concatenate')
def concatenate():
word1 = request.args['word1']
word2 = request.args['word2']
return f'{word1} {word2}'
Here we’re using request.args, which is a dictionary where all the URL parameters are stored. The function just reads the parameters from the URL and assigns them to two variables that we can then use any way we want, like for example here to concatenate the parameters and separate them with a blank space. Let’s run the app and use the route:
If one or more parameters are missing, we’ll get an error:
In order to avoid it, we should always check whether the parameters are present in the request.args dictionary and react accordingly:
@app.route('/concatenate_checked')
def concatenate_checked():
if 'word1' in request.args and 'word2' in request.args:
word1 = request.args['word1']
word2 = request.args['word2']
return f'{word1} {word2}'
else:
return 'One or more URL parameters are missing.'
If we now omit one or both parameters, we’ll get the message instead of the error:
If all parameters are delivered, it works as before.
Alternatively, we can retrieve data from the request.args dictionary using the get method. This method takes a second argument, which is the default value in case the key is missing. Let’s say, we want to replace each missing URL parameter with an empty string:
@app.route('/concatenate_with_get')
def concatenate_with_get():
word1 = request.args.get('word1', '')
word2 = request.args.get('word2', '')
return f'{word1} {word2}'
Now if we deliver just one parameter, the other one will be replaced by the default value:
GET, POST, PUT and DELETE Requests
Just a quick reminder. The four basic request methods are:
GET – used to retrieve data from the server,
POST – used to send data to the server,
PUT – used to update data on the server,
DELETE – used to delete data from the server.
This list is not complete, but this will do for our purposes.
As mentioned before, in all the routes above we were using just the GET requests. This is the default method, so we don’t have to specify it explicitly. To check it out, we can use the curl command. You can open a new terminal to do it. Make sure you have curl installed by using the curl --version
command and if not, install it first. So, let’s check out the /help
route:
PS D:\Projects\flask-series> curl http://127.0.0.1:5000/help
StatusCode : 200
StatusDescription : OK
Content : <h2>Help - learn how to use this page!</h2>
RawContent : HTTP/1.1 200 OK
Connection: close
Content-Length: 43
Content-Type: text/html; charset=utf-8
Date: Mon, 23 Dec 2024 18:31:37 GMT
Server: Werkzeug/3.1.3 Python/3.12.4
<h2>Help - learn how to use...
Forms : {}
Headers : {[Connection, close], [Content-Length, 43], [Content-Type, text/html; charset=utf-8], [Date, Mon, 23 Dec 2024 18:31:37 GMT]...}
Images : {}
InputFields : {}
Links : {}
ParsedHtml : mshtml.HTMLDocumentClass
RawContentLength : 43
As you can see, the status code is 200, which means OK.
And now let’s try to use the same route with a POST request. It shouldn’t be possible. To specify the method in the curl
command, we put its name after the -X
flag and before the URL:
PS D:\Projects\flask-series>curl -X POST http://127.0.0.1:5000/help
<!doctype html>
<html lang=en>
<title>405 Method Not Allowed</title>
<h1>Method Not Allowed</h1>
<p>The method is not allowed for the requested URL.</p>
As we can see, here we have the status code 405 – Method Not Allowed. To specify the allowed methods, we just have to add a methods parameter to the @app.route
decorator. Let’s define a new route that handles only POST requests:
@app.route('/help_post', methods=['POST'])
def help_post():
return 'Handled requests: POST'
Similarly, we can create a route that handles only GET requests. In this case, we don’t have to specify the methods argument because by default it contains the GET method, but we can if we want to:
@app.route('/help_get', methods=['GET'])
def help_get():
return 'Handled requests: GET'
We can add more methods to the list. If we want to handle both GET and POST requests, we just add these two methods:
@app.route('/help_get_post', methods=['GET', 'POST'])
def help_get_post():
return 'Handled requests: GET, POST'
And if we want to handle all four most popular request methods, we just add them all in:
@app.route('/help_get_post_put_delete', methods=['GET', 'POST', 'PUT', 'DELETE'])
def help_all_four():
return 'Handled requests: GET, POST, PUT, DELETE'
You can use curl to check these routes out.
If more than one method is handled in a route, we can easily differentiate between them in the function body to behave depending on which method is used:
@app.route('/help_diff', methods=['GET', 'POST', 'PUT', 'DELETE'])
def help_diff():
if request.method == 'GET':
return 'This is a GET request.'
elif request.method == 'POST':
return 'This is a POST request.'
elif request.method == 'PUT':
return 'This is a PUT request.'
elif request.method == 'DELETE':
return 'This is a DELETE request.'
HTTP Status Codes
Just like we saw before, we get an HTTP status code with each response. In the examples above we had the status code 200, which means OK, and the status code 405, which means Method Not Allowed. There are many other status codes out there. We can specify which status code should be returned by adding it as the second element returned by the function.
Let’s check it out in curl. Here’s another route with just the GET method specified:
@app.route('/help_default_status_code', methods=['GET'])
def help_default_status_code():
return 'Default status code'
We use the -I
flag to see the information we need. Here’s what we get:
PS D:\Projects\flask-series>curl -I http://127.0.0.1:5000/help_default_status_code
HTTP/1.1 200 OK
Server: Werkzeug/3.1.3 Python/3.12.4
Date: Mon, 23 Dec 2024 21:22:03 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 19
Connection: close
The 200 status code is the default one. But we can use a different one, like for example 202:
@app.route('/help_202', methods=['GET'])
def help_202():
return 'Status code: 202', 202
This time, the returned status code is 202 – ACCEPTED:
PS D:\Projects\flask-series>curl -I http://127.0.0.1:5000/help_202
HTTP/1.1 202 ACCEPTED
Server: Werkzeug/3.1.3 Python/3.12.4
Date: Mon, 23 Dec 2024 21:27:54 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 16
Connection: close
Custom Responses
The status code is part of the response. You can change it using the actual response object. This object has more elements that you can tweak. In order to tweak the response, we need to import the make_response
function that returns the response and then we can set the attributes on the response object to whatever we want.
So, let’s expand our import section:
from flask import Flask, request, make_response
And now let’s create another route with a custom response:
@app.route('/custom_response')
def custom_response():
response = make_response('This is a custom response.')
response.status_code = 201
response.headers['content-type'] = 'text/plain'
response.content_length = 500
return response
As you can see, the status, content type in the headers and content length were set manually. Here’s what we get using curl:
PS D:\Projects\flask-series>curl -I http://127.0.0.1:5000/custom_response
HTTP/1.1 201 CREATED
Server: Werkzeug/3.1.3 Python/3.12.4
Date: Mon, 23 Dec 2024 22:05:55 GMT
content-type: text/plain
Content-Length: 500
Connection: close
That’s it as far as basic routing in Flask is concerned. In the next part we’ll see how to render HTML using templates.