Python course Β· Module 6: Async and FastAPI

FastAPI - modern asynchronous API

6 min read
In this lesson10

Welcome back! Darwin here with FastAPI - the most modern framework for building APIs in Python!

In the previous lesson you learned async/await. Now we'll use that knowledge with FastAPI - a framework that is 3-5x faster than Flask, has automatic validation, and generates documentation automatically!

Safari Analogy: Flask is a traditional safari tour - tried and true, reliable. FastAPI is a safari with drones - fast, modern, with GPS and automatic animal tracking!

What is FastAPI?

FastAPI is a modern, fast framework for building APIs created by Sebastian Ramirez in 2018.

Key features: Fast - comparable to Node.js/Go (thanks to Starlette) Async native - full support for async/await Type hints - uses type annotations Automatic validation - Pydantic out-of-the-box Auto docs - Swagger UI + ReDoc automatically Modern Python - Python 3.7+ required

Installing FastAPI

1pip install fastapi[all]  # Everything (Uvicorn, Pydantic, etc.)
2
3# Or minimally:
4pip install fastapi uvicorn[standard]

Uvicorn = ASGI server (like Gunicorn for WSGI)

Hello World FastAPI

1# main.py
2from fastapi import FastAPI
3
4app = FastAPI()
5
6@app.get("/")
7async def root():
8    return {"message": "Hello Safari!"}
9
10# Run: uvicorn main:app --reload

Run:

1uvicorn main:app --reload

Output:

1INFO: Uvicorn running on http://127.0.0.1:8000
2INFO: Application startup complete.

Open:

FastAPI vs Flask

FastAPIFlask
Async nativeSync (async possible, but difficult)
Type hints requiredOptional
Automatic validation (Pydantic)Manual (Flask-WTF)
Auto docs (Swagger/ReDoc)None
3-5x fasterSlower
Modern Python (3.7+)Older versions OK
API-firstWeb apps + API

When FastAPI? REST API, microservices, async I/O When Flask? Web apps with templates, smaller projects

Basic endpoints

1from fastapi import FastAPI
2
3app = FastAPI(title="Safari Database API", version="1.0.0")
4
5# GET - fetch data
6@app.get("/species")
7async def list_species():
8    return [
9        {"id": 1, "name": "Lion", "population": 120},
10        {"id": 2, "name": "Elephant", "population": 450}
11    ]
12
13# GET with path parameter
14@app.get("/species/{species_id}")
15async def get_species(species_id: int):
16    return {"id": species_id, "name": "Lion", "population": 120}
17
18# POST - add data
19@app.post("/species")
20async def create_species(name: str, population: int):
21    return {"id": 3, "name": name, "population": population, "status": "created"}
22
23# PUT - update
24@app.put("/species/{species_id}")
25async def update_species(species_id: int, name: str, population: int):
26    return {"id": species_id, "name": name, "population": population}
27
28# DELETE - remove
29@app.delete("/species/{species_id}")
30async def delete_species(species_id: int):
31    return {"id": species_id, "status": "deleted"}

Test:

1# GET
2curl http://localhost:8000/species
3
4# POST
5curl -X POST "http://localhost:8000/species?name=Cheetah&population=7100"

Query parameters - automatic validation

FastAPI automatically parses query params and validates types!

1from fastapi import FastAPI
2
3app = FastAPI()
4
5@app.get("/species")
6async def list_species(
7    skip: int = 0,
8    limit: int = 10,
9    habitat: str | None = None,
10    endangered: bool = False
11):
12    # FastAPI automatically:
13    # - parses ?skip=10&limit=5&habitat=savanna&endangered=true
14    # - converts types (str β†’ int, str β†’ bool)
15    # - validates (skip must be int!)
16
17    return {
18        "skip": skip,
19        "limit": limit,
20        "habitat": habitat,
21        "endangered": endangered,
22        "results": ["Lion", "Elephant"]
23    }

Test:

1/species                              β†’ skip=0, limit=10, habitat=None, endangered=False
2/species?skip=20&limit=5              β†’ skip=20, limit=5
3/species?habitat=savanna&endangered=1 β†’ habitat="savanna", endangered=True
4/species?skip=abc                     β†’ ERROR: validation error!

FastAPI automatically generates validation errors:

1{
2  "detail": [
3    {
4      "loc": ["query", "skip"],
5      "msg": "value is not a valid integer",
6      "type": "type_error.integer"
7    }
8  ]
9}

Response models - structured responses

1from fastapi import FastAPI
2from pydantic import BaseModel
3
4app = FastAPI()
5
6class Species(BaseModel):
7    id: int
8    name: str
9    scientific_name: str
10    population: int
11    endangered: bool = False
12
13@app.get("/species/{species_id}", response_model=Species)
14async def get_species(species_id: int):
15    return Species(
16        id=species_id,
17        name="Lion",
18        scientific_name="Panthera leo",
19        population=120,
20        endangered=True
21    )

Benefits of response_model:

  • Automatic serialization to JSON
  • Output validation
  • Documentation automatically shows the structure
  • Type safety

Full Safari API - example

1# main.py
2from fastapi import FastAPI, HTTPException
3from pydantic import BaseModel, Field
4from typing import List
5
6app = FastAPI(
7    title="Safari Database API",
8    description="API for managing African wildlife species",
9    version="1.0.0"
10)
11
12# Models
13class SpeciesBase(BaseModel):
14    name: str = Field(..., min_length=2, max_length=100)
15    scientific_name: str
16    population: int = Field(..., ge=0)
17    habitat: str
18
19class SpeciesCreate(SpeciesBase):
20    pass
21
22class Species(SpeciesBase):
23    id: int
24
25    class Config:
26        from_attributes = True
27
28# In-memory database
29species_db: dict[int, Species] = {
30    1: Species(id=1, name="Lion", scientific_name="Panthera leo", population=120, habitat="savanna"),
31    2: Species(id=2, name="Elephant", scientific_name="Loxodonta africana", population=450, habitat="savanna"),
32}
33next_id = 3
34
35# Endpoints
36@app.get("/")
37async def root():
38    return {"message": "Safari Database API", "version": "1.0.0"}
39
40@app.get("/species", response_model=List[Species])
41async def list_species(skip: int = 0, limit: int = 10, habitat: str | None = None):
42    """List all species with pagination"""
43    results = list(species_db.values())
44
45    if habitat:
46        results = [s for s in results if s.habitat == habitat]
47
48    return results[skip : skip + limit]
49
50@app.get("/species/{species_id}", response_model=Species)
51async def get_species(species_id: int):
52    """Get specific species by ID"""
53    if species_id not in species_db:
54        raise HTTPException(status_code=404, detail=f"Species {species_id} not found")
55
56    return species_db[species_id]
57
58@app.post("/species", response_model=Species, status_code=201)
59async def create_species(species: SpeciesCreate):
60    """Create new species"""
61    global next_id
62
63    new_species = Species(id=next_id, **species.dict())
64    species_db[next_id] = new_species
65    next_id += 1
66
67    return new_species
68
69@app.put("/species/{species_id}", response_model=Species)
70async def update_species(species_id: int, species: SpeciesCreate):
71    """Update existing species"""
72    if species_id not in species_db:
73        raise HTTPException(status_code=404, detail="Species not found")
74
75    updated = Species(id=species_id, **species.dict())
76    species_db[species_id] = updated
77
78    return updated
79
80@app.delete("/species/{species_id}")
81async def delete_species(species_id: int):
82    """Delete species"""
83    if species_id not in species_db:
84        raise HTTPException(status_code=404, detail="Species not found")
85
86    del species_db[species_id]
87    return {"status": "deleted", "id": species_id}

Run:

1uvicorn main:app --reload

Test the API:

1# List
2curl http://localhost:8000/species
3
4# Specific species
5curl http://localhost:8000/species/1
6
7# Add
8curl -X POST http://localhost:8000/species \
9  -H "Content-Type: application/json" \
10  -d '{"name":"Cheetah","scientific_name":"Acinonyx jubatus","population":7100,"habitat":"savanna"}'
11
12# Update
13curl -X PUT http://localhost:8000/species/1 \
14  -H "Content-Type: application/json" \
15  -d '{"name":"Lion","scientific_name":"Panthera leo","population":150,"habitat":"savanna"}'
16
17# Delete
18curl -X DELETE http://localhost:8000/species/3

Automatic documentation

FastAPI automatically generates interactive documentation!

Swagger UI: http://localhost:8000/docs

  • Interactive API testing
  • "Try it out" button for each endpoint
  • Automatic forms

ReDoc: http://localhost:8000/redoc

  • Readable documentation
  • Great for developers

OpenAPI JSON: http://localhost:8000/openapi.json

  • Raw OpenAPI 3.0 schema
  • For external tools

Summary

In this lesson you learned:

  • What FastAPI is and why to use it
  • Installing FastAPI + Uvicorn
  • Hello World and basic endpoints
  • GET, POST, PUT, DELETE
  • Path parameters and query parameters
  • Response models with Pydantic
  • HTTPException for errors
  • Automatic documentation (Swagger/ReDoc)
  • Full Safari Database API

Next lesson: Darwin will show you Pydantic v2 - a powerful data validation system with FastAPI!

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. How would you best describe FastAPI?

  2. 2. How do you install FastAPI along with an ASGI server?

These are 2 of 3 questions for this lesson. Solve the rest in the game.

Hands-on tasks in the game

  • Horizontal ordering

    How do you create a FastAPI application instance?

  • Vertical ordering

    Arrange a minimal FastAPI application:

  • Click in order

    Arrange the command to start the uvicorn server:

  • Code editor

    Write an async GET /species endpoint that returns a list of dictionaries with species.

Useful articles