Everyone Can Learn Program
中文 Start learning
Specialty · BackendLesson 1 of 6Free to read

Routing · Backend Development

What does a web server actually do with a request?

Course contents

Why you need it

Every app you use talks to a backend: a program that receives requests, looks things up and sends answers. Flask, FastAPI, Express and Spring look different, but underneath they all do the same three things. Once you see them, any backend framework becomes easy to read.

The intuition

A request arrives with a method and a path: GET /users/42. The server:

  1. Routes it: finds the handler whose method and path pattern match. /users/<id> matches /users/42 with id = "42".
  2. Runs the handler, which does the work.
  3. Responds with a status code and a body (usually JSON).
CodeMeans
200 OKhere's what you asked for
201 CreatedI made the new thing
400 Bad Requestyour input is wrong
401 / 403who are you? / you may not
404 Not Foundno such thing
405 Method Not Allowedthat path exists, but not with this method
500 Internal Server Errorthe server broke

In Flask the same route looks like this; the framework does the matching for you:

@app.get("/users/<id>")
def get_user(id):
    return {"id": int(id)}

In this course we write the matching ourselves, as plain functions that return (status, body).

Definition

Route

A method plus a path pattern, connected to the handler that serves it: GET /users/<id> → get_user.

Handler

The function that does the work for one route and returns the response.

Status code

A three-digit number at the start of every response. 2xx success, 4xx the client's mistake, 5xx the server's.

Examples

One handler

Try it yourself

A handler looks something up and picks a status code.

USERS = {1: "Ada", 2: "Linus"}

def get_user(user_id):
    if user_id not in USERS:
        return 404, {"error": "no such user"}
    return 200, {"id": user_id, "name": USERS[user_id]}

print(get_user(1))
print(get_user(7))
Output
(200, {'id': 1, 'name': 'Ada'})
(404, {'error': 'no such user'})
Step by step
  1. L4Always handle the missing case first.
  2. L8Found: 200 and the user as a dictionary (it becomes JSON).(200, {'id': 1, 'name': 'Ada'})
  3. L9Not found: 404 with an error message.(404, {'error': 'no such user'})

Matching a path pattern

Try it yourself

Split both into parts and compare them one by one; <name> parts capture a value.

def match(pattern, path):
    want = pattern.strip("/").split("/")
    got = path.strip("/").split("/")
    if len(want) != len(got):
        return None
    params = {}
    for w, g in zip(want, got):
        if w.startswith("<") and w.endswith(">"):
            params[w[1:-1]] = g
        elif w != g:
            return None
    return params

print(match("/users/<id>", "/users/42"))
print(match("/users/<id>", "/posts/42"))
print(match("/users/<id>/posts", "/users/7/posts"))
print(match("/users/<id>", "/users/7/posts"))
Output
{'id': '42'}
None
{'id': '7'}
None
Step by step
  1. L2"/users/<id>" becomes ["users", "<id>"].
  2. L4Different number of parts: can't match.
  3. L8A <name> part matches anything and remembers it.
  4. L10A fixed part must be equal.
  5. L14id is captured as text: "42".{'id': '42'}

A tiny router

Try it yourself

A list of routes, and a loop that finds the first match.

def match(pattern, path):
    want = pattern.strip("/").split("/")
    got = path.strip("/").split("/")
    if len(want) != len(got):
        return None
    params = {}
    for w, g in zip(want, got):
        if w.startswith("<") and w.endswith(">"):
            params[w[1:-1]] = g
        elif w != g:
            return None
    return params

def health(params):
    return 200, {"ok": True}

def get_user(params):
    return 200, {"id": int(params["id"])}

ROUTES = [
    ("GET", "/health", health),
    ("GET", "/users/<id>", get_user),
]

def handle(method, path):
    for m, pattern, handler in ROUTES:
        params = match(pattern, path)
        if params is not None and m == method:
            return handler(params)
    return 404, {"error": "not found"}

print(handle("GET", "/health"))
print(handle("GET", "/users/5"))
print(handle("GET", "/nope"))
Output
(200, {'ok': True})
(200, {'id': 5})
(404, {'error': 'not found'})
Step by step
  1. L20Each route: method, pattern, handler.
  2. L26Try each route in order.
  3. L30Nothing matched.

Common mistakes

200 for everything

If errors come back as 200 with {"error": …}, clients, caches and monitoring all think it worked. Use the status code that matches what happened.

Forgetting path params are text

In /users/42, id arrives as the string "42". Convert it with int(), and answer 400 or 404 if that fails.

Order of routes

The first matching route wins. Put /users/me before /users/<id>, or "me" will be treated as an id.

Quick quiz

1. A client sends POST /orders and the order is saved. Which status fits best?

  1. A201 Created
  2. B200 OK
  3. C204 No Content
Show the answer

Answer: A

201 says a new resource was created; the body usually contains it, with its new id.

2. What does this print?

def match(pattern, path):
    want = pattern.strip("/").split("/")
    got = path.strip("/").split("/")
    if len(want) != len(got):
        return None
    params = {}
    for w, g in zip(want, got):
        if w.startswith("<") and w.endswith(">"):
            params[w[1:-1]] = g
        elif w != g:
            return None
    return params

print(match("/shop/<item>/reviews", "/shop/lamp/reviews"))
print(match("/shop/<item>", "/shop"))
  1. A
    {'item': 'lamp'}
    None
  2. B
    {'item': 'lamp'}
    {}
  3. C
    {'item': 'reviews'}
    None
Show the answer

Answer: A

lamp lands in <item>. "/shop" has one part and the pattern has two, so no match.

Practice

3 exercises in three steps: predict what a program prints, change a program to meet a new requirement, then write one from scratch. Your code runs in the browser and is checked the moment you submit.

  1. PredictRoute the requests

    Type exactly what this prints.

  2. Modify405, not 404

    When the path exists but the method doesn't, the server should say 405 {"error": "method not allowed"}, not 404. Change handle so that:

    • path and method match → the handler's response
    • the path matches some route, but no route with this method → 405
    • no route has this path → 404
  3. WriteTyped path parameters

    Write match(pattern, path) like the one in the examples, plus one feature: a part written <int:name> only matches digits, and its value is converted to an int.

    match("/users/<int:id>", "/users/42")   # {'id': 42}
    match("/users/<int:id>", "/users/me")   # None
    match("/tags/<name>", "/tags/python")   # {'name': 'python'}

Practise this lesson

Open the interactive lesson to edit and run these examples and solve the exercises with instant feedback. Free to start, no sign-up.

Terms in this lesson

TermChineseDefinition
Backend后端The server side of an app: it handles requests and stores data.
Route路由A method and path pattern connected to a handler.
Status code状态码The number that says how a request went: 200, 404, 500…