Flask GET & POST Requests
Master handling GET and POST requests in Flask with this hands-on Python web development lesson. Learn the core concepts, step-by-step implementation, and troubleshooting tips.
Focus: handle get and post requests in flask
You've built a few Flask routes that return static text or simple HTML, but every real web app needs to accept input from users—whether it's a search query, a form submission, or a login. The moment you try to handle user input, you run into the core distinction between GET and POST requests. In this lesson, you'll learn how to handle GET and POST requests in Flask, when to use each method, and how to write routes that respond correctly to both. By the end, you'll be able to build interactive forms and API endpoints with confidence.
The problem this lesson solves
Imagine you're building a simple contact form. When a user submits it, you need to send the data to your server, process it, and return a response. But where does the data go? How does Flask know what to do with it? If you don't understand the difference between GET and POST, you'll end up with URLs full of messy query strings or worse—you'll expose sensitive data in the browser's address bar.
Many beginners start by hardcoding URLs and passing data through the URL path, but that breaks down fast. You quickly hit issues like:
- URL length limits — Browsers and servers have limits on how long a URL can be (often 2048 characters), so you can't send large amounts of data via GET.
- Data exposure — GET requests put data in the URL, visible in history, logs, and bookmarks. That's bad for passwords, credit cards, or any sensitive input.
- Broken bookmarks — If a form action is wrong, users can't bookmark the result or share a link to a POST page without resubmitting.
- No way to distinguish actions — A route that only handles GET can't process form submissions at all.
This lesson is your step-by-step fix: you'll learn how to handle GET and POST requests in Flask so your routes become dynamic, interactive, and secure.
Core concept / mental model
Think of HTTP methods as verbs that describe what you want to do with a resource. The two you'll use most are GET and POST. Here's a simple analogy: GET is like asking a librarian for a book (you just want to read it), while POST is like handing the librarian a filled-out form to add a new book to the catalog (you're sending data to be processed).
In Flask, a route can be configured to accept one or more methods using the methods parameter in the @app.route() decorator. By default, Flask only allows GET. When a request comes in with a method that isn't allowed, Flask returns a 405 Method Not Allowed error.
The key to handling both is inside the route function: you check request.method to decide what to do. request is a global object that Flask gives you, containing all the data sent by the client—headers, form data, query strings, and JSON.
Here's the mental model:
- The client (browser or another app) sends an HTTP request with a method (GET, POST, PUT, DELETE, etc.) and a URL.
- Flask matches the URL to a route.
- If the method is allowed for that route, Flask calls your function.
- Inside the function, you inspect
request.methodandrequest.args(for GET) orrequest.form(for POST) to get the data. - You return a response—HTML, JSON, a redirect—whatever fits.
Think of request as the mailbag the postman hands you. GET data arrives as a stack of sticky notes on the front door (the URL), while POST data is a sealed envelope in the bag.
How it works step by step
Setting up a Flask app
Make sure Flask is installed. If not, install it with:
pip install flask
Create a file named app.py with the setup code below. This imports Flask and initializes the app object.
from flask import Flask, request, render_template_string
app = Flask(__name__)
Route with GET only
By default, a route only responds to GET requests. If someone sends a POST to that route, they'll get a 405 error. Test this with the example below.
@app.route('/')
def home():
return "<h1>Welcome!</h1>"
Route with POST only
To make a route accept POST, set methods=['POST']. If you send a GET request to this route, Flask will return 405.
@app.route('/submit', methods=['POST'])
def submit():
return "Form submitted!"
Route with both GET and POST
This is the heart of the lesson. Set methods=['GET', 'POST'] and use an if statement inside the function to branch based on request.method.
@app.route('/form', methods=['GET', 'POST'])
def form():
if request.method == 'POST':
# Process the form data (e.g., store it, send an email)
name = request.form.get('name')
return f"Hello, {name}! Your form was submitted."
# GET request: serve the HTML form
return render_template_string('''
<form method="post">
<label>Name: <input type="text" name="name"></label>
<button type="submit">Submit</button>
</form>
''')
Accessing GET data
When a GET request is sent to a route, any query parameters appear in request.args (an immutable dict). For example, visiting /search?q=flask gives you request.args.get('q').
Accessing POST data
For POST requests, data comes in request.form (for form-encoded bodies) or request.json (if the client sent JSON with Content-Type: application/json). Use request.form.get('fieldname') for form data.
Now you have the complete picture: the flow is request → route → method check → data access → response.
Hands-on walkthrough
Let's build a mini contact form with a route that handles both GET and POST, then test it with curl. Create a new file contact.py with the following code.
from flask import Flask, request, render_template_string
app = Flask(__name__)
@app.route('/contact', methods=['GET', 'POST'])
def contact():
if request.method == 'POST':
name = request.form.get('name', 'Anonymous')
message = request.form.get('message', '')
# In a real app, you'd save this or send an email
return f"<h2>Thanks, {name}!</h2><p>Your message: '{message}'</p>"
return render_template_string('''
<form method="post">
<label>Name: <input type="text" name="name"></label><br>
<label>Message: <textarea name="message"></textarea></label><br>
<button type="submit">Send</button>
</form>
''')
if __name__ == '__main__':
app.run(debug=True)
Run the app with python contact.py and open http://127.0.0.1:5000/contact in your browser. Fill out the form and submit it—you'll see the POST branch execute.
Test with curl
Open a second terminal and test both methods:
# GET request (will return the form HTML)
curl http://127.0.0.1:5000/contact
# POST request with form data
curl -X POST -d "name=Alice&message=Hello%20Flask" http://127.0.0.1:5000/contact
The second command sends a POST with -X POST and -d to pass form data. You should see the response: Thanks, Alice! Your message: 'Hello Flask'.
Example with GET parameters
Now add a search endpoint to see how GET data works:
@app.route('/search')
def search():
query = request.args.get('q', '')
if query:
return f"Searching for: {query}"
return "<p>No query provided. Try /search?q=flask</p>"
Restart the app and visit /search?q=python. You'll see Searching for: python. This demonstrates how GET data arrives in the URL.
Pro tip: Always use
request.args.get('key', default)orrequest.form.get('key', default)instead ofrequest.args['key'], because the missing-key version raises a400 Bad Requesterror when the key is absent. The.get()method lets you supply a fallback.
Compare options / when to choose what
| Method | When to use | Data location | Security | Browser behavior |
|---|---|---|---|---|
| GET | Retrieving data (search, filters, pagination) | URL query string | Not for sensitive data (exposed in history/logs) | Can be bookmarked, shared, refreshed |
| POST | Creating/updating resources, forms with sensitive data | Request body | Better—data not in URL | Cannot be bookmarked; browser warns on refresh resubmit |
- Choose GET when the request is idempotent (repeating it gives the same result) and not changing server state. Examples: fetching a blog post, searching products.
- Choose POST for any action that changes state—submitting a comment, logging in, uploading a file. Even if you could do it with GET, using POST prevents accidental re-submission and keeps data out of the URL.
Other methods like PUT and DELETE exist, but for most beginner projects, GET and POST cover 90% of your needs. When you build a REST API later, you'll learn when to use each of the four main verbs.
Troubleshooting & edge cases
405 Method Not Allowed
If you send a POST to a route that only accepts GET (or vice versa), Flask returns a 405 Method Not Allowed error. Fix by adding methods=['GET', 'POST'] to the route decorator, or using a separate route for each method.
400 Bad Request: KeyError
Using request.form['name'] without checking for existence raises a 400 Bad Request if the key is missing. Always use .get() with a default, as shown earlier.
Form data not showing up
If your form uses method="get", the data goes into request.args, not request.form. Double-check the form tag: use method="post" for POST data. Also ensure the route allows that method.
Sending JSON via POST
If your client sends JSON (e.g., from JavaScript), the data won't be in request.form. Use request.get_json() instead:
data = request.get_json()
name = data.get('name') if data else None
Edge case: missing content type in POST
Some HTTP clients omit the Content-Type header, causing request.form to be empty. Ensure your client sends application/x-www-form-urlencoded for forms or application/json for JSON.
What you learned & what's next
You now know how to handle GET and POST requests in Flask. Specifically, you learned:
- How to define routes that accept both methods using
methods=['GET', 'POST']. - How to branch on
request.methodto serve a form or process its submission in the same route. - How to access query parameters via
request.argsand form data viarequest.form. - When to use GET vs POST, and the trade-offs in security and URL length.
- How to troubleshoot common errors like 405s and missing form fields.
You also completed a hands-on exercise that runs locally and tested it with curl. This skill is foundational for the next lesson in this track, Handling Form Data with Flask-WTF, where you'll add validation and CSRF protection to your forms.
Next, try building a simple login page that accepts a username and password via POST. You'll be surprised how much you can already build with these three lines!
Practice recap
Now try this mini exercise: create a Flask route /comment that accepts both GET and POST. On GET, serve a simple form with a text field. On POST, read the comment from request.form and return it back, but if the field is empty, return a friendly error instead. Test with curl. This will reinforce your understanding of method branching and safe data access.
Common mistakes
- Forgetting to add
methods=['POST']to a route that needs to accept form submissions, resulting in a 405 error. - Using
request.argsfor POST data — form data lives inrequest.formand won't appear in the query string. - Accessing form fields with
request.form['name']instead of.get(), which raises a 400 error if the key is missing. - Putting sensitive data in a GET URL — it gets logged in browser history and server logs, so always use POST for passwords or personal info.
- Not handling JSON POST bodies — if the client sends JSON,
request.formis empty; userequest.get_json()instead.
Variations
- Use separate routes for GET and POST:
@app.route('/submit', methods=['GET'])for the form and@app.route('/submit', methods=['POST'])for processing, with a redirect after POST. - Leverage Flask's
render_templatewith separate HTML files instead ofrender_template_stringfor cleaner separation of concerns. - Use Flask-WTF or Django-style class-based views to handle validation and CSRF automatically, but for small apps the plain method check is enough.
Real-world use cases
- A contact form on a marketing site that receives user messages via POST and displays a thank-you page.
- A search endpoint on an e-commerce site that reads the query from GET parameters and returns product results.
- A login or signup API that accepts JSON via POST and returns a token or success message.
Key takeaways
- Flask routes accept GET by default; you must explicitly allow POST with
methods=['GET', 'POST']. - Use
request.methodinside a route to serve different responses for GET vs POST in the same URL. - GET data arrives in
request.args, POST data inrequest.form(orrequest.get_json()for JSON). - Always use
.get()with a default when reading request data to avoid 400 errors on missing keys. - Use GET for reading data and POST for any action that changes server state to keep URLs clean and secure.
- Testing with curl (
curl -X POST -d ...) is a fast way to verify your routes handle the right methods.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.