Skip to content
Programmeren in Python, leer de nieuwste technieken
Programmeren in PythonProgrammeren in Python
  • Home
  • Blog
  • Documentatie
  • Cursussen
Programmeren in PythonProgrammeren in Python
  • Home
  • Blog
  • Documentatie
  • Cursussen

Introductie

4
  • 1.1 Wat is Python?
  • 1.2 Python installatie
  • 1.2.1 Python installeren voor MacOS
  • 1.3 PyCharm Installatie

Basisprogrammering

6
  • 2.1 Python Basis: Variabelen, Datatypes en Operatoren
  • 2.2 Python if else (en elif)
  • 2.3 Python list (Array, Lijsten)
  • 2.4 Python Lussen: for loop, while loop, break, continue
  • 2.5 Python Functies: Definities, Parameters en Terugkeerwaarden
  • 2.6 Foutafhandeling: try, except, else, finally

Datatypes

13
  • 3.1 Introductie tot Geavanceerde Datatypes
  • 3.2 Python Tuple
  • 3.3 Python Set
  • 3.4 Python Dictionary
  • 3.5 Werken met Strings en String-methoden
  • 3.6 Collections Module: Krachtige Tools in Python
  • 3.7 Iterators en Generators
  • 3.8 List Comprehensions
  • 3.9 Geavanceerde Sortering
  • 3.10 Werken met Multi-dimensionale Data
  • 3.11 Typing en Datatypes
  • 3.12 Itertools voor Geavanceerde Iteraties
  • 3.13 Data Conversies

Modules

8
  • 4.1 Wat zijn Modules en Waarom zijn ze Belangrijk?
  • 4.2 Werken met Ingebouwde Modules
  • 4.3 Installeren en Gebruiken van Externe Pakketten
  • 4.4 Eigen Modules Maken
  • 4.5 Introductie tot Pakketten
  • 4.6 Importeren en Namespaces Begrijpen
  • 4.7 Geavanceerd: Relatief Importeren
  • 4.8 Organiseren van Grotere Projecten

Data Analyse

3
  • 5.1 Python Dataframe en Data Opschonen met Pandas
  • 5.2 Python Pandas Basisstatistieken en Data-analyse
  • 5.3 Python Numpy (NpArray): De Kracht van Numerieke Berekeningen

Webontwikkeling

6
  • 7.1 Inleiding tot Webontwikkeling
  • 7.2 HTTP-Verzoeken met requests: Communiceren met het Web
  • 7.3 Webscraping met BeautifulSoup: Data van het Web Halen als er geen APIs zijn
  • 7.4 Python Flask, een webserver & API tutorial
  • 7.5 WSGI & WebOb
  • 7.6 FastAPI tutorial (Nederlands) – API bouwen met Python + Uvicorn

Deploy

1
  • Deploy met Supervisor op Ubuntu

Object Georiënteerd Programmeren

8
  • Wat is Objectgeoriënteerd Programmeren (OOP) in Python?
  • Python class & object maken
  • self en __init__ uitgelegd (met voorbeelden)
  • Attributen afschermen: @property (getters/setters)
  • Overerving in Python + super() (en wanneer je het beter níet doet)
  • Dunder methods: __str__, __repr__, vergelijken (en waarom dit je OOP-code “Pythonic” maakt)
  • @dataclass: snelle nette classes (minder boilerplate, meer duidelijkheid)
  • OOP in de praktijk: design & structuur
View Categories
  • Home
  • Documentatie
  • Webontwikkeling
  • 7.6 FastAPI tutorial (Nederlands) – API bouwen met Python + Uvicorn

7.6 FastAPI tutorial (Nederlands) – API bouwen met Python + Uvicorn

33 minuten leestijd

REST-basis: hoe je je API “denkt” #

Eerst even een algemene uitleg over RESTul API’s. Een modern principe wat eigenlijk voortborduurt op conventies rondom HTTP methodes en de wijze waarop applicatie staat (data) in je back-end wordt verwerkt. REST staat dan ook voor REpresentational State Transfer.

Een REST API draait om resources (dingen) en representaties (hoe die dingen worden weergegeven).

Resources (zelfstandige naamwoorden)

  • /items = collectie items
  • /items/{id} = één item

HTTP methods (werkwoorden)

  • GET /items → lijst (safe, idempotent)
  • GET /items/{id} → detail (safe, idempotent)
  • POST /items → nieuw item (niet idempotent)
  • PUT /items/{id} → vervang item (idempotent)
  • PATCH /items/{id} → gedeeltelijk updaten (meestal niet strikt idempotent)
  • DELETE /items/{id} → verwijderen (idempotent)

Hierin zie je de term idempotent. Wat in latijn letterlijk betekent: ‘zelfde kracht’. Hiermee bedoelen we in de IT dat een operatie dezelfde uitkomst introduceert. Dus als je herhaaldelijk een idempotent endpoint aanroept, met dezelfde invoer, is de uitkomst altijd hetzelfde. Met safe bedoelen we hiernaast ook nog dat het geen wijzigingen zal gaan introduceren in de data.

Statuscodes (contract)

  • 200 OK (succes), 201 Created (na POST), 204 No Content (bij delete zonder body)
  • 400 Bad Request (request klopt niet), 401 Unauthorized (niet ingelogd), 403 Forbidden (wel ingelogd, geen rechten)
  • 404 Not Found, 409 Conflict (bv. dubbele unieke waarde)
  • 422 Unprocessable Entity (validatie faalt — FastAPI gebruikt dit vaak voor schema-validatie)
  • 500 e.v. (server fout)

Representatie / content-type

  • Meestal JSON (application/json)
  • Soms tekst (text/plain), HTML, CSV, bytes, streaming

FastAPI maakt dit “REST-denken” makkelijk omdat je types en schema’s (Pydantic) direct je contract laten zijn.

FastAPI bouwstenen #

1. De app: jouw HTTP-server “configuratie” #

De kern is een FastAPI() instantie. Hier stel je basisgedrag in: titel/versie, docs, default response type, etc.

from fastapi import FastAPI

app = FastAPI(
    title="Mijn API",
    version="1.0.0",
)
Python

Wat je vrijwel altijd wil instellen:

  • title, version (komt in OpenAPI / docs)
  • in productie vaak docs uit (of achter auth)
  • default response class (optioneel, later)
2. Path operations: endpoints (routes) + handler-functie #

Een endpoint is: HTTP method + path + handler (de functie).

@app.get("/health")
def health():
    return {"status": "ok"}
Python

3. HTTP methods: wat kan je toepassen en hoe ziet dat eruit? #

De meest gebruikte methods (REST-achtig)

In FastAPI definieer je ze met decorators: @app.get, @app.post, etc.

GET — lees data (safe, idempotent)

@app.get("/items")
def list_items():
    return [{"id": 1, "name": "Pen"}]
Python

POST — maak nieuw resource (meestal niet idempotent)

from fastapi import status

@app.post("/items", status_code=status.HTTP_201_CREATED)
def create_item():
    return {"id": 2, "name": "Boek"}
Python

PUT — vervang volledig (idempotent)

@app.put("/items/{item_id}")
def replace_item(item_id: int):
    return {"id": item_id, "name": "Nieuw (volledig)"}
Python

PATCH — update gedeeltelijk

@app.patch("/items/{item_id}")
def update_item(item_id: int):
    return {"id": item_id, "name": "Alleen naam aangepast"}
Python

DELETE — verwijder (idempotent)

from fastapi import Response

@app.delete("/items/{item_id}", status_code=204)
def delete_item(item_id: int):
    return Response(status_code=204)
Python

Minder gebruikte maar wél relevant

HEAD — zoals GET maar zonder body (handig voor checks/caching)

FastAPI heeft @app.head(...):

@app.head("/items/{item_id}")
def head_item(item_id: int):
    # Je geeft alleen headers/status terug
    return
Python

OPTIONS — vooral voor CORS “preflight”

Meestal laat je dit door CORS middleware afhandelen, maar het bestaat.

4. Parameters: data uit de request halen (path, query, headers, cookies, body) #

FastAPI bepaalt de bron van data o.b.v. hoe je het declareert.

(A) Path parameters (onderdeel van de URL)

from fastapi import Path

@app.get("/items/{item_id}")
def get_item(item_id: int = Path(..., ge=1)):
    return {"id": item_id}
Python

(B) Query parameters (na ?)

from fastapi import Query

@app.get("/items")
def list_items(
    q: str | None = None,
    limit: int = Query(20, ge=1, le=100),
    offset: int = Query(0, ge=0),
):
    return {"q": q, "limit": limit, "offset": offset}
Python

Wat je meestal nodig hebt in lijst-endpoints:

  • limit, offset (of page/page_size)
  • q (search)
  • sort (bijv. created_at:desc)
  • filters (status=active, category=...)

(C) Headers

Headers haal je op met Header():

from fastapi import Header

@app.get("/debug/headers")
def debug_headers(
    user_agent: str | None = Header(default=None),
    accept: str | None = Header(default=None),
):
    return {"user_agent": user_agent, "accept": accept}
Python

(D) Cookies

from fastapi import Cookie

@app.get("/prefs")
def prefs(theme: str | None = Cookie(default=None)):
    return {"theme": theme}
Python

(E) Body (JSON) — met Pydantic model

Als je een Pydantic model als parameter gebruikt, is dat request body. Zie hiervoor ook hoofdstuk 1.6 over FastApi in combinatie met Pydantic.

from pydantic import BaseModel, Field

class ItemCreate(BaseModel):
    name: str = Field(min_length=1, max_length=50)
    price: float = Field(gt=0)

@app.post("/items")
def create_item(payload: ItemCreate):
    return {"id": 1, **payload.model_dump()}
Python

(F) Form-data en file uploads (veel voorkomend in echte API’s)

from fastapi import Form, UploadFile, File

@app.post("/upload")
async def upload(
    description: str = Form(...),
    file: UploadFile = File(...),
):
    contents = await file.read()
    return {"filename": file.filename, "bytes": len(contents), "description": description}
Python

(G) Volledige Request object (als je alles zelf wil)

from fastapi import Request

@app.post("/raw")
async def raw(request: Request):
    body = await request.body()
    return {
        "method": request.method,
        "headers": dict(request.headers),
        "cookies": request.cookies,
        "body_len": len(body),
    }
Python

5. Response bouwen: statuscodes, headers, cookies, body-format #

Een HTTP response bestaat grofweg uit:

  1. Statuscode (wat is er gebeurd?)
  2. Headers (metadata: hoe moet de client dit interpreteren? caching? rate limits? etc.)
  3. Body (de representatie: JSON/tekst/CSV/bytes/stream)

FastAPI laat je dit op verschillende niveaus beïnvloeden: via return value, decorators (status_code), Response object, of specifieke Response-classes.

A) Statuscodes: de meest voorkomende + betekenis #

2xx Succes

  • 200 OK — succesvolle request (meestal bij GET, soms bij PUT/PATCH)
  • 201 Created — resource aangemaakt (bij POST); vaak met Location header naar de nieuwe resource
  • 202 Accepted — request geaccepteerd, maar verwerking gebeurt later (async / background job)
  • 204 No Content — succes, maar geen body (typisch bij DELETE of PUT zonder response-body)

3xx Redirects (minder in pure API’s)

  • 301 Moved Permanently — permanente redirect (bv. version prefix aangepast)
  • 302 Found — tijdelijke redirect
  • 304 Not Modified — caching: client mag cached versie gebruiken (ETag/If-None-Match)

4xx Client errors (request klopt niet / geen rechten)

  • 400 Bad Request — ongeldige request (bv. semantisch fout; ontbrekende combinatie van velden)
  • 401 Unauthorized — niet geauthenticeerd (geen/ongeldige credentials)
  • 403 Forbidden — wel geauthenticeerd, maar niet bevoegd
  • 404 Not Found — resource bestaat niet
  • 409 Conflict — conflict met huidige state (bv. duplicate unique key / race condition)
  • 415 Unsupported Media Type — verkeerde Content-Type (bv. client stuurt XML maar API accepteert JSON)
  • 422 Unprocessable Entity — validatie faalt (FastAPI gebruikt dit standaard bij schema-validatie)

5xx Server errors

  • 500 Internal Server Error — onverwachte fout
  • 502 Bad Gateway — upstream fout (bij reverse proxy / gateway)
  • 503 Service Unavailable — tijdelijk niet beschikbaar (maintenance/overload)
  • 504 Gateway Timeout — upstream reageert te laat

Praktische vuistregel:

  • Gebruik 201 + Location bij “create”.
  • Gebruik 204 wanneer je bewust geen body teruggeeft.
  • Gebruik 422 voor schema-validatie, 400 voor “business rule” fouten (bv. ongeldige combinatie).

Statuscodes in FastAPI instellen

1) Via decorator (status_code=...)

from fastapi import FastAPI, status

app = FastAPI()

@app.post("/items", status_code=status.HTTP_201_CREATED)
def create_item():
    return {"id": 123}
Python

2) Via Response(status_code=...) (handig voor 204)

from fastapi import Response

@app.delete("/items/{item_id}")
def delete_item(item_id: int):
    # delete logic...
    return Response(status_code=204)
Python

3) Via exception (fouten)

from fastapi import HTTPException

@app.get("/items/{item_id}")
def get_item(item_id: int):
    if item_id != 1:
        raise HTTPException(status_code=404, detail="Item niet gevonden")
    return {"id": 1}
Python

B) Headers: wat zijn het, welke gebruik je vaak, en hoe zet/lees je ze? #

Headers zijn metadata. Ze sturen hoe clients de response verwerken (caching, content type, auth, debugging, rate-limits).

Veel voorkomende response headers (API context)

Content / representatie

  • Content-Type — welk formaat is de body (bv. application/json, text/csv)
  • Content-Length — grootte van body (meestal automatisch)
  • Content-Disposition — “download this as file” (bv. bij exports)

Identificatie & tracing

  • X-Request-Id — unieke id per request (handig voor logs/support)
  • ETag — caching/conditional requests

Caching

  • Cache-Control — no-store, max-age=..., etc.
  • Last-Modified — voor caching

Rate limiting (als je dat implementeert)

  • Retry-After — wanneer opnieuw proberen
  • X-RateLimit-Limit / Remaining / Reset — informatief (conventie)

Security

  • WWW-Authenticate — bij 401 (bv. bearer realm)
  • Strict-Transport-Security — als je HTTPS afdwingt (meestal op proxy niveau)
  • Access-Control-Allow-Origin — CORS (meestal middleware/proxy)

Headers zetten in FastAPI

1) Simpel: via Response object

from fastapi import FastAPI, Response

app = FastAPI()

@app.get("/with-header")
def with_header(response: Response):
    response.headers["X-Request-Id"] = "abc-123"
    response.headers["Cache-Control"] = "no-store"
    return {"ok": True}
Python

2) Location header (REST best practice bij 201 Created)

from fastapi import status

@app.post("/items", status_code=status.HTTP_201_CREATED)
def create_item(response: Response):
    new_id = 123
    response.headers["Location"] = f"/items/{new_id}"
    return {"id": new_id}
Python

3) Download/export: Content-Disposition

from fastapi import Response

@app.get("/export")
def export():
    csv = "id,name\n1,Pen\n"
    headers = {
        "Content-Disposition": 'attachment; filename="items.csv"'
    }
    return Response(content=csv, media_type="text/csv", headers=headers)
Python

Tip: Veel security headers zet je vaak in je reverse proxy (Nginx/Caddy), maar X-Request-Id, Cache-Control, Location zijn heel API-typisch.

C) Cookies: hoe werken ze + HttpOnly & Secure goed uitgelegd #

Een cookie is een klein stukje data dat de server aan de client meegeeft (via Set-Cookie header). De client stuurt ‘m later terug via Cookie header.

Wat staat er in een cookie?

  • name=value (bv. session=abc123)
  • attributes (belangrijk voor veiligheid/gedrag):
    • HttpOnly — cookie is niet toegankelijk via JavaScript (document.cookie).
      ✅ helpt tegen diefstal via XSS (maar lost XSS niet op).
    • Secure — cookie wordt alleen over HTTPS verstuurd.
      ✅ verplicht in productie.
    • SameSite — hoe cookie meegaat bij cross-site requests:
      • Lax (vaak default/veilig)
      • Strict (streng; kan login flows breken)
      • None (alleen met Secure; nodig bij sommige cross-site scenarios)
    • Path — voor welk pad geldt de cookie (bv. /)
    • Domain — voor welk domein/subdomein
    • Max-Age / Expires — levensduur (session cookie vs persistent)

Cookies zetten in FastAPI

from fastapi import FastAPI, Response

app = FastAPI()

@app.post("/login")
def login(response: Response):
    response.set_cookie(
        key="session",
        value="abc123",
        httponly=True,   # niet leesbaar door JS
        secure=True,     # alleen over HTTPS
        samesite="lax",  # goede default voor veel apps
        max_age=60 * 60, # 1 uur
        path="/",
    )
    return {"logged_in": True}
Python

Cookie verwijderen (logout)

Je delete meestal door dezelfde cookie “expired” terug te sturen.

@app.post("/logout")
def logout(response: Response):
    response.delete_cookie(key="session", path="/")
    return {"logged_out": True}
Python

Praktische security-notes:

  • Voor API-auth is Bearer token vaak handiger, maar cookies zijn prima bij browser-apps.
  • Als je cookies cross-site nodig hebt: SameSite=None + Secure=True is vereist.
D) Body-formats: welke zijn gebruikelijk en hoe return je ze? #

De body is de representatie van je resource. In API’s zie je vooral:

1) JSON (meest voorkomend)

Gebruik: standaard REST responses, request/response modellen.

@app.get("/json")
def json():
    return {"id": 1, "name": "Pen"}
Python

FastAPI maakt hier automatisch application/json van.

JSON met response_model (contract + filtering)

from pydantic import BaseModel

class ItemOut(BaseModel):
    id: int
    name: str

@app.get("/items/{item_id}", response_model=ItemOut)
def get_item(item_id: int):
    return {"id": item_id, "name": "Pen", "internal": "wordt niet getoond"}
Python

2) Plain text (text/plain)

Gebruik: simpele health checks, debugging endpoints, of wanneer tekst echt het product is.

from fastapi.responses import PlainTextResponse

@app.get("/text", response_class=PlainTextResponse)
def text():
    return "OK"
Python

3) HTML (text/html)

Gebruik: vaak alleen voor docs/landing of server-side views.

from fastapi.responses import HTMLResponse

@app.get("/html", response_class=HTMLResponse)
def html():
    return "<b>Hallo</b>"
Python

4) CSV (text/csv)

Gebruik: exports, reporting.

from fastapi import Response

@app.get("/items.csv")
def items_csv():
    csv = "id,name\n1,Pen\n2,Boek\n"
    return Response(content=csv, media_type="text/csv")
Python

Met download header (handig):

@app.get("/items-export")
def items_export():
    csv = "id,name\n1,Pen\n"
    headers = {"Content-Disposition": 'attachment; filename="items.csv"'}
    return Response(content=csv, media_type="text/csv", headers=headers)
Python

5) Bytes / binary (application/octet-stream)

Gebruik: bestanden, images, zip.

from fastapi import Response

@app.get("/binary")
def binary():
    data = b"\x00\x01\x02"
    return Response(content=data, media_type="application/octet-stream")
Python

6) Streaming responses (grote bestanden/data)

Gebruik: grote exports (niet alles in RAM).

from fastapi.responses import StreamingResponse

def iter_csv():
    yield "id,name\n"
    for i in range(1, 1000):
        yield f"{i},Item{i}\n"

@app.get("/stream.csv")
def stream_csv():
    return StreamingResponse(iter_csv(), media_type="text/csv")
Python

7) Bestanden (download) — FileResponse

Gebruik: serve een bestand vanaf disk.

from fastapi.responses import FileResponse

@app.get("/download")
def download():
    return FileResponse("report.pdf", filename="report.pdf")
Python

6. Validatie en “API contract”: Pydantic modellen #

In een REST API is je belangrijkste “product” eigenlijk het contract:

  • Welke input accepteer je?
  • Welke output garandeer je?
  • Welke fouten (met welke shape) kan een client verwachten?

In FastAPI leg je dat contract vast met Pydantic modellen (BaseModel). FastAPI gebruikt die modellen voor:

  • validatie van request data
  • documentatie (OpenAPI / Swagger)
  • serialisatie en filtering van responses via response_model

Als request-data ongeldig is, raised FastAPI intern een RequestValidationError (en je krijgt typisch een 422).

1) Het basispatroon: meerdere modellen per resource (Create/Update/Out/InDB) #

Een veelgemaakte fout is “één model voor alles”. In praktijk wil je verschillende modellen omdat:

  • input voor create ≠ output voor read
  • update heeft vaak optionele velden (PATCH)
  • database bevat velden die je niet wilt exposen (bv. hashed_password, interne flags)

Aanbevolen set per resource

  • ItemCreate → request body bij POST
  • ItemUpdate → request body bij PATCH / PUT (vaak optioneel)
  • ItemOut → response body naar de client
  • ItemInDB (optioneel) → interne representatie (bv. inclusief created_at/updated_at, interne velden)

Voorbeeld:

from datetime import datetime
from pydantic import BaseModel, Field, ConfigDict

class ItemBase(BaseModel):
    name: str = Field(min_length=1, max_length=50, description="Naam van het item")
    price: float = Field(gt=0, description="Prijs moet > 0 zijn")

class ItemCreate(ItemBase):
    pass

class ItemUpdate(BaseModel):
    # PATCH: alles optioneel, alleen wat de client stuurt pas je aan
    name: str | None = Field(default=None, min_length=1, max_length=50)
    price: float | None = Field(default=None, gt=0)

class ItemOut(ItemBase):
    id: int
    created_at: datetime

class ItemInDB(ItemOut):
    # interne velden die niet naar buiten mogen
    model_config = ConfigDict(extra="ignore")
    internal_notes: str | None = None
Python

2) Validatie met Field constraints + metadata (docs) #

Met Field() voeg je constraints toe (validatie) én metadata (OpenAPI docs).

Veelgebruikte constraints

  • Strings: min_length, max_length, pattern
  • Numbers: gt, ge, lt, le
  • Arrays: min_length/max_length (lijst-lengte)
  • Optional: field: type | None = None

Voorbeeld (met voorbeelden voor docs):

from pydantic import BaseModel, Field

class ItemCreate(BaseModel):
    name: str = Field(
        min_length=1,
        max_length=50,
        description="Naam zoals die zichtbaar is voor de gebruiker",
        examples=["Pen", "Notitieboek"]
    )
    price: float = Field(
        gt=0,
        description="Prijs in euro’s",
        examples=[1.99, 12.5]
    )
Python

FastAPI kan deze examples opnemen in het schema en laten zien in Swagger UI.

Tip voor “mooie docs”: voeg description + examples toe aan de velden die clients vaak verkeerd invullen.

3) Input vs output contract: response_model (en hoe je data lekken voorkomt) #

In FastAPI zet je je response contract expliciet met response_model=....
FastAPI gebruikt dat om de output te documenteren, valideren en filteren.

from fastapi import FastAPI
from datetime import datetime

app = FastAPI()

FAKE_DB = {1: {"id": 1, "name": "Pen", "price": 1.5, "created_at": datetime.utcnow(), "internal_notes": "…" }}

@app.get("/items/{item_id}", response_model=ItemOut)
def get_item(item_id: int):
    return FAKE_DB[item_id]  # internal_notes wordt weggefilterd door response_model
Python

Waarom dit belangrijk is: zelfs als je per ongeluk extra velden teruggeeft, dwingt response_model af wat er “naar buiten” mag.

Response “opschonen”: exclude opties

FastAPI/routers ondersteunen opties als:

  • response_model_exclude_unset
  • response_model_exclude_none
  • response_model_exclude_defaults

Voorbeeld: alleen velden sturen die echt gevuld zijn:

from fastapi import APIRouter

router = APIRouter()

@router.get("/items/{item_id}", response_model=ItemOut, response_model_exclude_none=True)
def get_item(item_id: int):
    ...
Python

4) PATCH/PUT correct implementeren met exclude_unset #

Bij PATCH wil je alleen de velden toepassen die de client echt heeft gestuurd.

Pydantic bewaart welke velden “set” zijn; met exclude_unset=True dump je alleen die.

from fastapi import HTTPException

@app.patch("/items/{item_id}", response_model=ItemOut)
def patch_item(item_id: int, payload: ItemUpdate):
    item = FAKE_DB.get(item_id)
    if not item:
        raise HTTPException(404, "Item niet gevonden")

    updates = payload.model_dump(exclude_unset=True)  # alleen meegestuurde velden
    item.update(updates)
    return item
Python

Belangrijk verschil:

  • exclude_unset=True → velden die niet meegestuurd zijn vallen weg
  • exclude_none=True → velden met waarde None vallen weg (maar soms wil je juist null kunnen sturen!)

5) Striktheid: wat doe je met “extra” velden? #

Standaard is Pydantic vaak tolerant (extra velden kunnen genegeerd worden). In API’s wil je vaak strikter, zodat clients geen rommel sturen.

Met ConfigDict(extra='forbid') kun je “extra velden” afkeuren.

from pydantic import BaseModel, ConfigDict

class StrictItemCreate(BaseModel):
    model_config = ConfigDict(extra="forbid")  # extra keys => validatiefout
    name: str
    price: float
Python

Praktijkadvies:

  • Public API: vaak forbid (hard contract)
  • Interne API: soms ignore (meer tolerant)
6) Casing & field aliases: camelCase input, snake_case code #

Veel frontends sturen camelCase (createdAt), terwijl Python vaak snake_case (created_at) gebruikt.

Pydantic ondersteunt aliases (per field of via generator).

Per field alias

from pydantic import BaseModel, Field

class ItemOut(BaseModel):
    id: int
    created_at: str = Field(serialization_alias="createdAt")
Python

Automatisch aliases genereren

from pydantic import BaseModel, ConfigDict
from pydantic.alias_generators import to_camel

class ItemOut(BaseModel):
    model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)

    id: int
    created_at: str
Python

  • alias_generator kan zowel validatie als serialisatie beïnvloeden.
  • Aliases zijn “alternatieve veldnamen” voor (de)serialisatie/(de)serialisatie.
7) Nested modellen: realistische payloads #

Echte API’s hebben vaak nested data: bijv. item + tags, of een paginated response.

from pydantic import BaseModel, Field
from datetime import datetime

class TagOut(BaseModel):
    id: int
    label: str

class ItemOut(BaseModel):
    id: int
    name: str
    price: float
    created_at: datetime
    tags: list[TagOut] = Field(default_factory=list)
Python

Waarom nested models fijn zijn:

  • validatie op elk niveau
  • OpenAPI schema is automatisch correct en compleet
8) Custom validators: business rules (field + model-level) #

Constraints in Field() zijn “basis”. Voor business rules heb je validators.

Pydantic v2 gebruikt @field_validator en @model_validator.

Field validator (één veld)

from pydantic import BaseModel, field_validator

class ItemCreate(BaseModel):
    name: str
    price: float

    @field_validator("name")
    @classmethod
    def no_whitespace_only(cls, v: str) -> str:
        if not v.strip():
            raise ValueError("name mag niet leeg/whitespace zijn")
        return v
Python

Model validator (combinatie van velden)

Bijvoorbeeld: “als price=0 dan moet is_free=True” (of andersom).

from pydantic import BaseModel, model_validator

class ItemCreate(BaseModel):
    name: str
    price: float
    is_free: bool = False

    @model_validator(mode="after")
    def check_free_logic(self):
        if self.is_free and self.price != 0:
            raise ValueError("Als is_free=true, dan moet price=0 zijn")
        return self
Python

Effect in FastAPI: invalidatie → 422 met details (loc/msg/type). FastAPI raiset dan een RequestValidationError.

9) Serialisatie: model_dump() (wat stuur je echt over de wire) #

Pydantic v2 gebruikt model_dump() om naar dict/JSON-ready data te gaan. Je hebt vaak nodig:

  • exclude_unset=True (PATCH)
  • exclude_none=True (geen nulls)
  • by_alias=True (camelCase output)
  • include / exclude (veldselectie)
payload_dict = payload.model_dump(exclude_unset=True)     # PATCH
out_dict = item_model.model_dump(by_alias=True)          # camelCase output
clean = item_model.model_dump(exclude_none=True)         # nulls weg
Python

7. Dependencies: hergebruik voor DB, auth, config en headers #

Dependencies zijn hét mechanisme waarmee je in FastAPI alles wat “rondom je endpoint” gebeurt netjes hergebruikt: database-sessies, auth, config, request-context (headers/cookies), feature flags, rate limits, enz.

De kern-gedachte: een dependency is gewoon een (async) functie of callable die FastAPI vóór je endpoint uitvoert. Die dependency mag dezelfde soorten parameters ontvangen als een endpoint (Query, Path, Header, Cookie, Body, Request, Response, etc.).


1) Waarom dependencies #

Zonder dependencies eindig je vaak met herhaalde code in elk endpoint (auth check, DB open/close, common query params, etc.).

Met dependencies krijg je:

  • Hergebruik: één plek voor “common logic”
  • Compositie: dependency kan weer andere dependencies gebruiken (“sub-dependencies”)
  • Lifecycle/cleanup: resources openen/sluiten met yield
  • Per-request caching: dezelfde dependency wordt standaard maar één keer per request uitgevoerd (tenzij je dat uitzet)
  • Testbaarheid: dependencies kun je overriden in tests met app.dependency_overrides
2) Hoe dependency injection werkt in FastAPI #

2.1 “Ik wil de return value gebruiken” → als parameter met Depends

from typing import Annotated
from fastapi import Depends, FastAPI

app = FastAPI()

def get_tenant_id() -> str:
    return "tenant-123"

@app.get("/me")
def me(tenant_id: Annotated[str, Depends(get_tenant_id)]):
    return {"tenant_id": tenant_id}
Python

  • FastAPI roept get_tenant_id() aan.
  • De return value wordt in tenant_id geïnjecteerd

2.2 “Ik wil alleen een check uitvoeren, geen value” → dependency op decorator/route-niveau

Handig voor dingen als: “moet ingelogd zijn”, “rate limit”, “moet admin zijn”.

from fastapi import Depends, HTTPException, status

def require_feature_flag():
    enabled = True
    if not enabled:
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Feature disabled")

@app.get("/secret", dependencies=[Depends(require_feature_flag)])
def secret():
    return {"ok": True}
Python

3) Waar kun je dependencies toepassen (en in welke volgorde draaien ze)? #

Je kunt dependencies hangen aan:

  1. Router-niveau (geldt voor alle endpoints in die router)
  2. Decorator-niveau (voor één endpoint, zonder parameter)
  3. Parameter-niveau (voor één endpoint, mét return value)

FastAPI beschrijft ook de volgorde: router dependencies eerst, daarna decorator dependencies, daarna parameter dependencies.

Router voorbeeld:

from fastapi import APIRouter, Depends

router = APIRouter(
    prefix="/items",
    dependencies=[Depends(require_feature_flag)],
)

@router.get("")
def list_items():
    return []
Python

4) Per-request caching (en wanneer je use_cache=False nodig hebt) #

Als je dezelfde dependency meerdere keren in dezelfde request-chain gebruikt, cache’t FastAPI de uitkomst automatisch (per request).

Wanneer is dit handig?

  • get_settings() wil je niet telkens opnieuw bouwen
  • get_current_user() wil je niet twee keer token-decoderen

Wanneer wil je use_cache=False?

  • Als je echt meerdere keren wil uitvoeren (zeldzaam; meestal voor side effects)
from fastapi import Depends

def expensive():
    print("runs")
    return 1

def needs_twice(a=Depends(expensive), b=Depends(expensive, use_cache=False)):
    return a, b
Python

5) Veelvoorkomende dependency-patronen die bijna elke API nodig heeft #

5.1 Common query parameters (pagination/filter/search) als dependency

Je wil niet in elk list-endpoint dezelfde limit/offset/q/sort opnieuw typen.

from typing import Annotated
from fastapi import Depends, Query

def common_list_params(
    q: str | None = None,
    limit: int = Query(20, ge=1, le=100),
    offset: int = Query(0, ge=0),
    sort: str | None = None,
):
    return {"q": q, "limit": limit, "offset": offset, "sort": sort}

@app.get("/items")
def list_items(params: Annotated[dict, Depends(common_list_params)]):
    return {"params": params, "data": []}
Python

5.2 Request context dependency (headers/cookies lezen) + response headers zetten

Een heel realistisch patroon: request-id / correlatie-id.

import uuid
from typing import Annotated
from fastapi import Depends, Header, Response

def request_context(
    response: Response,
    request_id: Annotated[str | None, Header(alias="X-Request-Id")] = None,
    session: Annotated[str | None, Header(alias="X-Session-Id")] = None,
):
    rid = request_id or str(uuid.uuid4())
    # zet response header zodat client/logs altijd dezelfde id hebben
    response.headers["X-Request-Id"] = rid
    return {"request_id": rid, "session": session}

@app.get("/debug")
def debug(ctx: Annotated[dict, Depends(request_context)]):
    return {"ctx": ctx}
Python

5.3 Database session dependency (openen/sluiten met yield) #

Dit is hét klassieke dependency-voorbeeld: DB session/pool openen, daarna automatisch sluiten.

FastAPI ondersteunt “dependencies with yield” voor cleanup.

def get_db():
    db = open_db_session()  # pseudo
    try:
        yield db
    finally:
        db.close()
Python

Gebruik:

from typing import Annotated
from fastapi import Depends

@app.get("/items")
def list_items(db: Annotated[object, Depends(get_db)]):
    return db.query_items()  # pseudo
Python

Best practice

  • Nooit “global session” hergebruiken tussen requests
  • Gebruik yield zodat cleanup altijd gebeurt (ook bij errors)
8. Auth: current user + autorisatie (roles/permissions) als dependencies #

Auth komt bijna altijd in 2 lagen:

  1. Authenticatie: “wie is dit?” (get_current_user)
  2. Autorisatie: “mag deze user dit?” (require_admin, require_scope, …)
(A) get_current_user (token uit header) #
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials

bearer = HTTPBearer(auto_error=False)

def get_current_user(
    creds: HTTPAuthorizationCredentials | None = Depends(bearer)
):
    if not creds:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Missing bearer token")

    token = creds.credentials
    # TODO: validate token (JWT / DB lookup)
    if token != "demo":
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Invalid token")

    return {"user_id": 123, "roles": ["user"]}
Python

(B) require_admin (autorisatie) #
def require_admin(user=Depends(get_current_user)):
    if "admin" not in user["roles"]:
        raise HTTPException(status_code=403, detail="Admin required")
    return user

@app.delete("/items/{item_id}")
def delete_item(item_id: int, admin=Depends(require_admin)):
    return {"deleted": item_id}
Python

Voor uitgebreidere OAuth2/scopes gebruikt FastAPI Security() en scopes in dependencies.

9. “Service clients” als dependency (HTTP client, cache client, etc.) #

Als je endpoints externe services aanroepen, wil je:

  • één client maken (per request of per app-lifecycle)
  • timeouts instellen
  • eenvoudig kunnen mocken in tests

Een eenvoudig patroon (per request):

import httpx

async def get_http_client():
    async with httpx.AsyncClient(timeout=5) as client:
        yield client

@app.get("/external")
async def external(client=Depends(get_http_client)):
    r = await client.get("https://example.com")
    return {"status": r.status_code}
Python

(Nog beter maar geavanceerder: client op app-lifespan aanmaken en hergebruiken.)

10. Async vs sync dependencies (en performance) #
  • Dependencies mogen def of async def zijn.
  • Kies async als je I/O doet (DB/HTTP/files).
  • Hou dependencies lichtgewicht: elke request voert ze uit (tenzij cached).

Speed tip: centraliseer zware work (zoals token decode/DB lookup) in één dependency en hergebruik de result (caching helpt).

11. Routers: opsplitsen in files + tags + prefixes #

Routers (APIRouter) gebruik je om endpoints per domein (items/users/…) te groeperen, met gedeelde prefixes, tags en (optioneel) dependencies. Daarna voeg je ze toe aan je app met include_router().

Aanbevolen structuur (meest gebruikt)

app/
  main.py
  api/
    v1/
      router.py
      endpoints/
        items.py
        users.py
Python

1) Domeinrouter in eigen file #

app/api/v1/endpoints/items.py

from fastapi import APIRouter

router = APIRouter(prefix="/items", tags=["items"])

@router.get("")
def list_items():
    return []

@router.get("/{item_id}")
def get_item(item_id: int):
    return {"id": item_id}
Python

2) Versie-router die domeinen bundelt #

app/api/v1/router.py

from fastapi import APIRouter
from app.api.v1.endpoints import items, users

router = APIRouter()
router.include_router(items.router)
router.include_router(users.router)
Python

3) Main app include’t versie-router (versioning) #

app/main.py

from fastapi import FastAPI
from app.api.v1.router import router as v1_router

app = FastAPI()
app.include_router(v1_router, prefix="/api/v1")
Python

Resultaat:

  • GET /api/v1/items
  • GET /api/v1/items/{item_id}
Tags & prefixes (regels van duim) #
  • Prefix: zet versioning in main.py (/api/v1), en domeinpaden in de domeinrouter (/items).
  • Tags: 1 tag per domeinrouter (items, users) voor nette /docs.
Router-level dependencies (handig) #

Alles in één router achter auth:

from fastapi import Depends, APIRouter
from app.core.security import require_user

router = APIRouter(
    prefix="/items",
    tags=["items"],
    dependencies=[Depends(require_user)],
)
Python

12. Uvicorn voor FastAPI #
1) Wat is Uvicorn (en waarom heb je het nodig)? #

FastAPI is een ASGI webframework. Om een FastAPI-app “echt” HTTP requests te laten aannemen, heb je een ASGI server nodig — en Uvicorn is de meest gebruikte keuze.
Als je FastAPI installeert, krijg je (via de FastAPI CLI) Uvicorn standaard mee en kun je je app starten met fastapi run.

2) Installatie: wat installeer je in de praktijk? #

Aanrader: installeer de “standard” extras, omdat Uvicorn dan performance-helpers meeneemt (o.a. uvloop).

pip install "uvicorn[standard]"
# of (als je FastAPI ook installeert met extras):
pip install "fastapi[standard]"
Python

3) Uvicorn starten: het import string formaat #

Uvicorn verwacht je app als een import string: module:object.

Voorbeeld:

uvicorn main:app --host 0.0.0.0 --port 8000
Python

4) Development vs production: de 2 belangrijkste “modes” #

4.1 Development: auto-reload

Voor lokaal ontwikkelen:

uvicorn main:app --reload --port 8000
Python

4.2 Production: géén reload, wél meerdere processen (workers)

In productie wil je doorgaans meerdere processen om meerdere CPU cores te gebruiken (replicatie).

Uvicorn met workers:

uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
Python

  • --workers maakt meerdere worker-processen.
  • --reload en --workers zijn mutually exclusive (niet samen).
  • Default workers kan uit WEB_CONCURRENCY komen.

Updated on februari 6, 2026

What are your Feelings

7.5 WSGI & WebOb7.1 Inleiding tot Webontwikkeling
Inhoudsopgave
  • REST-basis: hoe je je API “denkt”
  • FastAPI bouwstenen
    • 1. De app: jouw HTTP-server “configuratie”
    • 2. Path operations: endpoints (routes) + handler-functie
    • 3. HTTP methods: wat kan je toepassen en hoe ziet dat eruit?
    • 4. Parameters: data uit de request halen (path, query, headers, cookies, body)
    • 5. Response bouwen: statuscodes, headers, cookies, body-format
      • A) Statuscodes: de meest voorkomende + betekenis
      • B) Headers: wat zijn het, welke gebruik je vaak, en hoe zet/lees je ze?
      • C) Cookies: hoe werken ze + HttpOnly & Secure goed uitgelegd
      • D) Body-formats: welke zijn gebruikelijk en hoe return je ze?
    • 6. Validatie en “API contract”: Pydantic modellen
      • 1) Het basispatroon: meerdere modellen per resource (Create/Update/Out/InDB)
      • 2) Validatie met Field constraints + metadata (docs)
      • 3) Input vs output contract: response_model (en hoe je data lekken voorkomt)
      • 4) PATCH/PUT correct implementeren met exclude_unset
      • 5) Striktheid: wat doe je met “extra” velden?
      • 6) Casing & field aliases: camelCase input, snake_case code
      • 7) Nested modellen: realistische payloads
      • 8) Custom validators: business rules (field + model-level)
      • 9) Serialisatie: model_dump() (wat stuur je echt over de wire)
    • 7. Dependencies: hergebruik voor DB, auth, config en headers
Programmeren in Python

Leer python op je eigen tempo met mooie interactieve hedendaagse voorbeelden.

© Copyright 2026 Programmeren in Python.
Sign inSign up

Sign in

Don’t have an account? Sign up
Lost your password?

Sign up

Already have an account? Sign in