DataLens

HTTP methods: GET and POST

Handle reading and sending data on the same URL, read posted form data, follow the Post/Redirect/Get pattern, and return the right status codes.

Level: Foundations · 9 min read

Practise this module

The questions for this lesson run in your browser and check your answer straight away.

The idea

So far every route answered GET: "give me this page". Websites also need to receive data: a new order, a contact message, a login. That's POST. The method is part of every request, and a route can treat each method differently.

MethodMeaningSafe to repeat?Example
GETread somethingyes, changes nothingview a product
POSTsend data to be processed / create somethingnoplace an order
PUT / PATCHreplace / update somethingPUT yes, PATCH it dependsupdate a customer's address
DELETEremove somethingyescancel a draft

The rule that matters most: GET must never change anything. Browsers prefetch links, search engines crawl them, users refresh pages. If GET /orders/41/cancel cancelled an order, a crawler could cancel your whole shop.

Allowing methods

Toko Kita's contact page has to do two things at the same URL: show the form when someone visits it (a GET), and receive the message when they press Send (a POST). By default a route only accepts GET, so you list the methods it should handle with methods=:

from flask import Flask, request

app = Flask(__name__)
MESSAGES = []


@app.route("/contact", methods=["GET", "POST"])
def contact():
    if request.method == "POST":
        MESSAGES.append(request.form["message"])
        return "Thanks, we got your message.", 201
    return "<form method='post'><textarea name='message'></textarea><button>Send</button></form>"


client = app.test_client()
print(client.get("/contact").status_code)
r = client.post("/contact", data={"message": "Do you ship to Medan?"})
print(r.status_code, r.get_data(as_text=True))
print(MESSAGES)
print(client.delete("/contact").status_code)
200
201 Thanks, we got your message.
['Do you ship to Medan?']
405

Separate functions per method

When the GET and POST logic grow, split them. Flask 2+ has shortcut decorators:

from flask import Flask, request

app = Flask(__name__)
ORDERS = []


@app.get("/orders")
def list_orders():
    return {"orders": ORDERS}


@app.post("/orders")
def create_order():
    product = request.form.get("product", "").strip()
    if not product:
        return {"error": "product is required"}, 400
    ORDERS.append(product)
    return {"created": product, "count": len(ORDERS)}, 201


client = app.test_client()
print(client.post("/orders", data={"product": "Ceramic Mug"}).get_json())
print(client.post("/orders", data={}).status_code)
print(client.get("/orders").get_json())
{'count': 1, 'created': 'Ceramic Mug'}
400
{'orders': ['Ceramic Mug']}

@app.get and @app.post mean @app.route(..., methods=["GET"]) and ["POST"]. Returning a dict sends JSON (more in the API module).

request.form["product"] raises an error (a 400 Bad Request) if the field is missing; request.form.get("product", "") returns a default instead, so you can show a friendly message. Validate everything that comes from a request: it's user input, and users (and attackers) can send anything.

Post/Redirect/Get

After a successful form POST, don't return a page directly. If the user refreshes, the browser asks "resubmit the form?", and a "yes" places the order twice. Instead, redirect to a GET page:

from flask import Flask, request, redirect, url_for

app = Flask(__name__)
ORDERS = []


@app.get("/")
def home():
    return f"{len(ORDERS)} orders so far"


@app.post("/orders")
def create_order():
    ORDERS.append(request.form["product"])
    return redirect(url_for("home"))


client = app.test_client()
r = client.post("/orders", data={"product": "Kettle"})
print(r.status_code, r.headers["Location"])
r = client.post("/orders", data={"product": "Mug"}, follow_redirects=True)
print(r.status_code, r.get_data(as_text=True))
302 /
200 2 orders so far

redirect() returns a 302 with a Location header; the browser then GETs that URL. Refreshing now just reloads the harmless GET page. This pattern is called Post/Redirect/Get (PRG), and nearly every form on the web uses it.

Key idea

GET reads and is safe to repeat. POST changes things, validates its input, and answers with a redirect (for forms) or a 201 (for APIs).

Status codes for writes

SituationStatus
created something201 Created
form handled, send the user on302 Found (redirect)
input missing or invalid400 Bad Request
method not supported on this URL405 Method Not Allowed (automatic)

Why this matters for clean code

Methods give each URL a clear contract: GET /orders lists, POST /orders creates. Splitting views per method keeps each function single-purpose, validation keeps bad data out, and PRG prevents duplicate orders. These are small habits that separate toy apps from ones that handle real customers.

Recap

Practise this module

The questions for this lesson run in your browser and check your answer straight away.

Next: Templates with Jinja2 →