FastAPI Learning Guide

FastAPI is a modern Python web framework that makes it easy to build fast and scalable APIs. It is built on Python’s type hints and integrates asynchronous programming seamlessly. In this guide, you will build a solid foundation in FastAPI by covering its core concepts and creating your first API.

Introduction to FastAPI

What is FastAPI?

Fastapi

FastAPI is a modern, high-performance, web framework for building APIs with Python. It was created by Sebastián Ramírez in Dec. 2018 and is built on top of Starlette (for the web parts) and Pydantic (for data validation). FastAPI’s main selling points are speed, ease of use, and automatic generation of OpenAPI documentation.

FastAPI is designed to be used for building RESTful APIs with minimal effort while offering automatic data validation, serialization, and easy integration with Python’s type hints. It leverages Python’s asynchronous capabilities, ensuring that applications can handle many requests concurrently, making it perfect for building high-performance APIs and microservices

Latest Version

As of March 1, 2025, the latest version of FastAPI is 0.115.11.

Key Features of FastAPI

FastAPI is a high-performance web framework for building APIs. Its key features include:

  • High Performance: FastAPI is one of the fastest web frameworks available. It is based on Starlette, an asynchronous framework, and supports asynchronous Python functions (i.e., async and await). It is built to handle requests efficiently and supports synchronous and asynchronous processing.
  • Ease of Use: FastAPI is user-friendly and intuitive. It uses Python’s type hints to provide automatic validation, serialization, and documentation generation. This makes it a highly productive framework for developers who want to build APIs without unnecessary boilerplate code.
  • FastAPI is fully compatible with well-known standards of APIs, namely OpenAPI and JSON schema.
  • Automatic Interactive API Documentation: One of the standout features of FastAPI is its automatic API documentation, generated using the OpenAPI standard. The two main documentation interfaces it provides are:
    • Swagger UI: A highly interactive UI to test the API.
    • ReDoc: A clean, fast documentation page.
  • Data Validation with Pydantic: FastAPI automatically validates incoming data using Pydantic models. Pydantic is a data validation and settings management library that ensures incoming JSON data conforms to the defined schema. This reduces the amount of manual validation code needed, leading to fewer bugs and easier maintenance.
  • Type Hints and Autocompletion: FastAPI relies heavily on Python’s type hinting system, making it easier to work with and ensuring that code is less error-prone. With type hints, FastAPI provides automatic request and response validation, which also helps editors provide autocompletion and type checking.
  • Asynchronous Support: FastAPI fully supports asynchronous programming, enabling the framework to handle many requests concurrently and efficiently. This is essential when building APIs that handle numerous simultaneous requests, such as in high-traffic applications or microservices.
  • Dependency Injection System: FastAPI has a built-in dependency injection system, which allows you to declare dependencies for your functions (such as database connections, external APIs, etc.). This leads to more modular and testable code.
  • Security Features: FastAPI includes features such as OAuth2 password flow, HTTP basic authentication, and more, allowing for secure API development with minimal configuration.
  • Support for REST and GraphQL: While FastAPI is primarily designed for RESTful APIs, it can also be used with GraphQL with the help of external libraries. This gives developers the flexibility to choose the best architecture for their projects.

Why Choose FastAPI?

  • Speed: One of the most compelling reasons to choose FastAPI is its speed. According to benchmark tests, FastAPI is one of the fastest Python web frameworks, often outperforming frameworks like Flask and Django. This speed advantage is due to its asynchronous architecture and its use of modern Python features like asyncio and async/await.
  • Developer Productivity: FastAPI’s use of type hints, automatic validation, and integrated documentation greatly improves developer productivity. You don’t need to write a lot of boilerplate code, and you get real-time feedback on the structure of your requests and responses. With FastAPI, you spend less time on documentation and more time on writing the core business logic of your application.
  • Automatic Interactive API Docs: FastAPI’s ability to generate interactive API documentation automatically is a game-changer. The auto-generated Swagger UI and ReDoc interfaces allow developers and even non-developers to quickly test API endpoints. This is especially useful during development and debugging.
  • Seamless Integration with Modern Python Libraries: FastAPI integrates well with popular Python libraries, such as SQLAlchemy (for databases), Celery (for task queues), and OAuth2 (for authentication). Additionally, it works well with Python’s async/await syntax, enabling developers to handle high numbers of concurrent requests efficiently.
  • Easy Testing: FastAPI has built-in support for testing, making it easy to write unit tests and integration tests for your APIs. You can use standard testing libraries like pytest and integrate them seamlessly into FastAPI’s ecosystem.

Prerequisites

To follow along, you’ll need:

  • Python 3.7 or higher.
  • Familiarity with Python basics and type hints.

Installing FastAPI

Install FastAPI and a lightweight ASGI server, Uvicorn:

pip install fastapi uvicorn
Python

FastAPI doesn’t come with any built-in server application. To run the FastAPI app, you need an ASGI server called uvicorn

Your First FastAPI App

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"message": "Welcome to FastAPI!"}

#Example request: GET /items/1?q=books
@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
    return {"item_id": item_id, "q": q}
Python

Run the server:

uvicorn main:app --reload
Python

  • Open http://127.0.0.1:8000 to see your API in action.
  • Explore the auto-generated documentation:
    • Swagger http://127.0.0.1:8000/docs.
    • ReDoc: http://127.0.0.1:8000/redoc

Understanding Path and Query Parameters

Path Parameters

Path parameters allow you to capture dynamic parts of the URL.

@app.get("/users/{user_id}")
def get_user(user_id: int):
    return {"user_id": user_id}
Python

Example request: GET /users/42

Example response:

{
  "user_id": 42
}
Python

Query Parameters

Query parameters are specified after a ? in the URL.

@app.get("/search")
def search_items(q: str, limit: int = 10):
    return {"query": q, "limit": limit}
Python

  • Example request: GET /search?q=books&limit=5
  • If no limit is provided, it defaults to 10.

Example response:

{
  "query": "books",
  "limit": 5
}
Python

Combining Path and Query Parameters

@app.get("/products/{product_id}")
def get_product(product_id: int, detail: bool = False):
    return {"product_id": product_id, "detail": detail}
Python

Example request: GET /products/123?detail=true

Example response:

{
  "product_id": 123,
  "detail": true
}
Python

Request Bodies and Response Models

Request Bodies

FastAPI uses Pydantic models to validate and parse request bodies.

Define a Pydantic model:

from pydantic import BaseModel

class Item(BaseModel):
    name: str
    price: float
    is_offer: bool = None
Python

Create a POST endpoint:

@app.post("/items/")
def create_item(item: Item):
    return {"item_name": item.name, "item_price": item.price}
Python

Example request body:

{
  "name": "Laptop",
  "price": 1200.50
}
Python

Example response:

{
  "item_name": "Laptop",
  "item_price": 1200.5
}
Python

Response Models

You can define the structure of the response using Pydantic models:

from pydantic import BaseModel

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

@app.get("/products/{product_id}", response_model=Product)
def read_product(product_id: int):
    return {"name": "Phone", "price": 699.99}
Python

Dependency Injection

FastAPI’s dependency injection system allows you to define dependencies (e.g., database connections, external APIs) that can be injected into your route handlers. Here’s an example:

Example 1

from fastapi import FastAPI, Depends

app = FastAPI()

# Dependency function
def get_query_param(q: str = "default_value"):
    return {"query_param": q}

# Route using dependency injection
@app.get("/items/")
def read_items(query: dict = Depends(get_query_param)):
    return query
Python

In this example, the get_query_param function is a dependency that FastAPI will automatically call before invoking the route handler. This modular approach makes the application easier to maintain and test.

Explanation:

  • get_query_param: This is a dependency function that returns a dictionary containing a query parameter.
  • Depends(get_query_param): The Depends function tells FastAPI to call get_query_param and pass the result into read_items.
  • When calling /items/?q=FastAPI, FastAPI will:
    • Call get_query_param(“FastAPI”)
    • Pass the return value {“query_param”: “FastAPI”} into read_items
    • Return this as the response

Example 2: Database Dependency Injection

A common use case of DI is injecting a database session into route functions.

from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session
from database import SessionLocal  # Assume this is a database session creator

app = FastAPI()

# Dependency to get a database session
def get_db():
    db = SessionLocal()  # Create a new database session
    try:
        yield db  # Provide the session to the route
    finally:
        db.close()  # Close the session after the request

# Route using database session as a dependency
@app.get("/users/")
def read_users(db: Session = Depends(get_db)):
    users = db.query(User).all()  # Query the database
    return users
Python

Explanation:

  • get_db():
    • Creates a database session.
    • Uses yield to provide it to the request.
    • Closes the session after the request is complete (ensuring no resource leaks).
  • Depends(get_db):
    • Injects the database session into the read_users function.
    • Now, db can be used to query the database.

This ensures every request gets a fresh database session and automatically cleans up after the request.

Example 3: Authentication Dependency

Let’s create an authentication dependency that checks if a user is authorized before executing the request.

from fastapi import FastAPI, Depends, HTTPException, Header

app = FastAPI()

# Dependency for authentication
def verify_token(token: str = Header(None)):
    if token != "supersecrettoken":
        raise HTTPException(status_code=401, detail="Unauthorized")
    return {"user": "authenticated"}

# Route using authentication dependency
@app.get("/protected/")
def protected_route(user: dict = Depends(verify_token)):
    return {"message": "Access granted", "user": user}
Python

Explanation:

  • verify_token():
    • Retrieves the token from the request headers.
    • If the token is incorrect, it raises a 401 Unauthorized error.
    • Otherwise, it returns a dictionary { “user”: “authenticated” }.
  • Depends(verify_token):
    • Ensures that only authorized users can access /protected/.
    • Asynchronous Endpoints

Example 4: Multiple Dependencies in One Route

You can use multiple dependencies in a single route.

@app.get("/secure-items/")
def get_secure_items(
    query: dict = Depends(get_query_param),
    user: dict = Depends(verify_token)
):
    return {"query": query, "user": user}
Python

Explanation:

  • The route depends on both get_query_param() and verify_token().
  • FastAPI calls each dependency before executing get_secure_items().
  • If verify_token() fails, FastAPI returns 401 Unauthorized without calling get_query_param().

Key Takeaways on FastAPI Dependency Injection

  • Reusability: You define logic once and use it across multiple endpoints.
  • Encapsulation: Keeps code clean and modular by separating concerns.
  • Automatic Execution: FastAPI calls dependency functions automatically before executing routes.
  • Security and Validation: This can be used for authentication, rate limiting, and request validation.
  • Testability: This makes testing easier since dependencies can be mocked or replaced

Background Tasks

FastAPI provides a way to run background tasks asynchronously, which is useful for long-running operations like sending emails or processing images. Here’s how to use background tasks:

from fastapi import BackgroundTasks

def write_log(message: str):
    with open("log.txt", mode="a") as log:
        log.write(message)

@app.get("/send-email/")
async def send_email(background_tasks: BackgroundTasks):
    background_tasks.add_task(write_log, "Email sent")
    return {"message": "Email sent in the background"}
Python

In this example, the task of writing a log is run in the background while the response is sent to the client.

async def and def

The difference between async def and def lies in how they handle function execution. While a regular function (def) is synchronous and blocks the code until it finishes executing, an asynchronous function (async def) allows the program to run other tasks concurrently, making it more efficient for I/O-bound operations.

Here’s an example to illustrate the difference between async def and def:

Example 1: Using def (Synchronous)

import time

def sync_function():
    print("Start sync function")
    time.sleep(3)  # Simulate a time-consuming operation (e.g., I/O)
    print("End sync function")

def main_sync():
    sync_function()
    sync_function()

if __name__ == "__main__":
    start = time.time()
    main_sync()
    end = time.time()
    print(f"Sync execution time: {end - start} seconds")
Python

Explanation:

  • The sync_function() uses time.sleep(3) to simulate a blocking operation.
  • The main_sync() calls sync_function() twice. Since sync_function() is synchronous, each call blocks the execution for 3 seconds, resulting in a total of 6 seconds of wait time.
  • When you run the code, it will print “Start sync function”, wait for 3 seconds, then print “End sync function”, and repeat for the second call.

Example 2: Using async def (Asynchronous)

import asyncio

async def async_function():
    print("Start async function")
    await asyncio.sleep(3)  # Simulate a time-consuming operation (non-blocking)
    print("End async function")

async def main_async():
    await asyncio.gather(
        async_function(),  # Start the first async function
        async_function()   # Start the second async function
    )

if __name__ == "__main__":
    start = time.time()
    asyncio.run(main_async())
    end =.time()
    print(f"Async execution time: {end - start} seconds")
Python

Explanation:

  • The async_function() uses await asyncio.sleep(3) to simulate a time-consuming, non-blocking operation.
  • The main_async() function runs both async_function() calls concurrently using asyncio.gather(). This allows both tasks to run at the same time, instead of waiting for one to finish before starting the other.
  • As a result, the total execution time will be approximately 3 seconds, not 6 seconds, because both tasks are running concurrently.

Output Comparison:

Sync Execution:

Start sync function
End sync function
Start sync function
End sync function
Sync execution time: 6.003092050552368 seconds
Python

Async Execution:

Start async function
Start async function
End async function
End async function
Async execution time: 3.003455877304077 seconds
Python

Key Differences:

  • Blocking vs Non-blocking:
    • def (synchronous) blocks the execution of the program, waiting for each task to finish before moving to the next one.
    • async def (asynchronous) allows tasks to run concurrently, not blocking the execution and improving efficiency for I/O-bound tasks.
  • Execution Time:
    • With synchronous functions, the total time depends on the sum of each task’s time (e.g., 3 + 3 = 6 seconds).
    • With asynchronous functions, the total time is the longest time for any one task (e.g., 3 seconds).

This demonstrates how asynchronous programming in Python can lead to more efficient handling of time-consuming operations, particularly when multiple tasks can be run concurrently, such as when making network requests or handling large-scale I/O operations.

Conclusion

FastAPI is a modern, efficient web framework that provides a fast and easy way to build APIs with Python. It excels in performance, usability, and automatic documentation generation. Whether you’re building a simple RESTful API or a complex microservice, FastAPI’s asynchronous capabilities, type-safe validation, and developer-friendly features make it an ideal choice.

By leveraging Python’s latest features and integrating seamlessly with popular libraries, FastAPI offers a smooth development experience. Its automatic OpenAPI documentation and interactive interfaces further enhance the developer and user experience.

With its combination of performance, ease of use, and powerful features, FastAPI is quickly becoming one of the go-to frameworks for building modern APIs. Whether you’re a beginner or an experienced developer, FastAPI provides everything you need to create high-performance APIs with minimal effort.

Leave a Comment