Your First FastAPI App

Difficulty: Beginner

Building your first FastAPI application is straightforward once you understand the core concepts: the application instance, path operation decorators, and handler functions. Let us walk through building a simple but complete API step by step.

The foundation of every FastAPI app is the application instance, created with app = FastAPI(). This object is the main entry point that Uvicorn uses to serve your API. You can pass configuration options to it like title, description, version, and more. The app instance has methods corresponding to HTTP methods: app.get(), app.post(), app.put(), app.delete(), app.patch(), etc. These are used as decorators on your handler functions.

Path operation decorators like @app.get("/") define two things: the HTTP method (GET, POST, etc.) and the URL path. The decorated function is called a 'path operation function' or 'route handler.' When a request matches the HTTP method and path, FastAPI calls this function and returns its result as a JSON response. The function can be either synchronous (def) or asynchronous (async def) - FastAPI handles both correctly.

FastAPI automatically converts the return value of your handler function to JSON. You can return Python dictionaries, lists, Pydantic models, or any JSON-serializable object. If you return a dictionary like {"name": "Alice", "age": 30}, FastAPI serializes it to a JSON response with the appropriate Content-Type header. For more control over the response, you can use FastAPI's Response classes directly.

The request-response cycle in FastAPI follows a clear path: a client sends an HTTP request, Uvicorn receives it and passes it to FastAPI, FastAPI matches the request to a route based on the method and path, the route handler executes and returns a value, FastAPI serializes the return value to JSON, and the response is sent back to the client. If any step fails (like validation), FastAPI returns an appropriate error response.

FastAPI supports all standard HTTP methods. GET is used for retrieving data, POST for creating new resources, PUT for replacing existing resources entirely, PATCH for partial updates, and DELETE for removing resources. Each method has a corresponding decorator: @app.get(), @app.post(), @app.put(), @app.patch(), @app.delete(). You can also use @app.api_route() to handle multiple methods with a single function, though this is less common.

A practical first application might be a simple in-memory todo list API. This demonstrates CRUD (Create, Read, Update, Delete) operations without needing a database. You store items in a Python list or dictionary, define endpoints for each operation, and use Pydantic models for request validation.

Code examples

A Complete CRUD API

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI()

class TodoCreate(BaseModel):
    title: str
    completed: bool = False

class TodoResponse(BaseModel):
    id: int
    title: str
    completed: bool

todos: dict[int, dict] = {}
next_id = 1

@app.get("/todos", response_model=list[TodoResponse])
def list_todos():
    return list(todos.values())

@app.post("/todos", response_model=TodoResponse, status_code=201)
def create_todo(todo: TodoCreate):
    global next_id
    new_todo = {"id": next_id, "title": todo.title, "completed": todo.completed}
    todos[next_id] = new_todo
    next_id += 1
    return new_todo

@app.get("/todos/{todo_id}", response_model=TodoResponse)
def get_todo(todo_id: int):
    if todo_id not in todos:
        raise HTTPException(status_code=404, detail="Todo not found")
    return todos[todo_id]

@app.delete("/todos/{todo_id}")
def delete_todo(todo_id: int):
    if todo_id not in todos:
        raise HTTPException(status_code=404, detail="Todo not found")
    del todos[todo_id]
    return {"message": "Todo deleted"}

This demonstrates a full CRUD API with in-memory storage. POST creates items, GET retrieves them, and DELETE removes them. HTTPException is used to return 404 errors when items are not found. The response_model parameter ensures consistent response structure.

Sync vs Async Handlers

from fastapi import FastAPI
import asyncio

app = FastAPI()

# Synchronous handler - runs in a thread pool
@app.get("/sync")
def sync_endpoint():
    # Blocking operations are fine here
    # FastAPI runs this in a thread pool automatically
    return {"type": "synchronous"}

# Asynchronous handler - runs on the event loop
@app.get("/async")
async def async_endpoint():
    # Use await for async operations
    await asyncio.sleep(0.1)  # Simulating async I/O
    return {"type": "asynchronous"}

# Rule of thumb:
# Use 'async def' when calling async libraries (httpx, asyncpg, aiofiles)
# Use 'def' when calling sync libraries (requests, psycopg2, open())

FastAPI handles both sync and async handlers. Sync handlers (def) are run in a thread pool so they do not block the event loop. Async handlers (async def) run directly on the event loop. Use async when working with async libraries for better performance.

Multiple Response Types

from fastapi import FastAPI
from fastapi.responses import (
    JSONResponse,
    PlainTextResponse,
    HTMLResponse,
    RedirectResponse,
)

app = FastAPI()

@app.get("/json")
def json_response():
    # Default: returns JSON
    return {"format": "json"}

@app.get("/text", response_class=PlainTextResponse)
def text_response():
    return "This is plain text"

@app.get("/html", response_class=HTMLResponse)
def html_response():
    return "<h1>Hello from HTML</h1>"

@app.get("/redirect")
def redirect_response():
    return RedirectResponse(url="/json")

@app.get("/custom")
def custom_response():
    return JSONResponse(
        content={"custom": True},
        status_code=201,
        headers={"X-Custom-Header": "my-value"},
    )

While JSON is the default, FastAPI supports many response types. Use response_class to change the default, or return Response objects directly for full control over status codes, headers, and content type.

Key points

Concepts covered

Decorators, Route Handlers, JSON Responses, Path Operations, Request-Response Cycle