FastAPI Complete Course Module 1: Introduction to APIs & FastAPI

FastAPI Complete Course Module 1: Introduction to APIs & FastAPI

AI Reading

Quick summary of this article

This module introduces APIs and FastAPI, a modern Python framework for building high-performance web APIs. It explains that an API acts as a messenger between a client and a server, using REST principles and standard HTTP methods like GET, POST, PUT, PATCH, and DELETE. FastAPI stands out for its speed, automatic documentation generation, built-in data validation using Python type hints, and async support. The module includes a step-by-step tutorial to create a simple book management API and compares FastAPI with Flask and Django to help you choose the right tool for your project.

  • An API works like a waiter: it takes a request from a client (like a browser), communicates with the server, and returns a response.
  • REST APIs use standard HTTP methods (GET to read, POST to create, PUT/PATCH to update, DELETE to remove) and status codes (200 OK, 201 Created, 404 Not Found) to indicate results.
  • FastAPI automatically generates interactive documentation at /docs (Swagger UI) and /redoc, making testing and development faster.
  • FastAPI uses Pydantic for data validation and Starlette for web handling, ensuring requests are checked automatically and errors are returned clearly.
  • To start, install FastAPI and Uvicorn, create a Python file with route decorators like @app.get("/books/{book_id}"), and run uvicorn main:app --reload to launch the server.

Introduction

Welcome to the first module of your FastAPI journey! In this chapter, we will lay the foundation for everything you will build as a professional FastAPI developer. We start with the most fundamental concept: the API. Understanding APIs is not just about passing data; it is about understanding how modern software communicates. By the end of this module, you will not only know what an API is, but you will also have written your first FastAPI application and understood the request-response cycle that powers the web.

This chapter is designed for beginners who are serious about becoming job-ready. We will move step-by-step, from theory to practice, ensuring you grasp each concept before moving on. Let’s begin.

What is an API?

API stands for Application Programming Interface. In simple terms, an API is a messenger that takes a request, tells a system what you want, and then returns the response back to you. Think of it like a waiter in a restaurant: you (the client) tell the waiter (the API) what dish you want (the request), the waiter tells the kitchen (the server), and then brings your food (the response) back to you.

Real-World Analogy

Imagine you are booking a flight online. You visit a travel website (the client). The website needs to know if seats are available, the price, and the flight times. The website cannot directly access the airline’s database (that would be a security nightmare). Instead, it sends a request to the airline’s API. The API checks the database and sends back the information. The website then displays it to you.

Key Components of an API

  • Client: The application or user making the request (e.g., a web browser, a mobile app, another server).
  • Server: The system that holds the data or functionality and processes the request.
  • Request: The message sent by the client to the server, asking for something.
  • Response: The message sent back from the server to the client, containing the requested data or a status message.
  • Endpoint: A specific URL (Uniform Resource Locator) where the API can be accessed (e.g., https://api.example.com/users).

REST API Basics

Not all APIs are created equal. The most common type you will encounter in web development is a REST API. REST stands for Representational State Transfer. It is a set of architectural principles that make APIs simple, scalable, and stateless.

Core Principles of REST

  • Statelessness: Each request from a client contains all the information the server needs to process it. The server does not store any client context between requests. This makes the API highly scalable.
  • Client-Server Architecture: The client and server are separate entities that communicate over a network. They can be developed and updated independently.
  • Uniform Interface: Resources (like users, products, or posts) are identified by URLs. The actions on these resources are performed using standard HTTP methods.
  • Resource-Based: Everything is a resource. A resource can be a user, a blog post, a product, etc. Each resource has a unique identifier (URL).

HTTP Methods (Verbs)

REST APIs use standard HTTP methods to perform operations on resources. Think of them as the actions you can take.

  • GET: Retrieve data from the server (e.g., get a list of users).
  • POST: Create a new resource on the server (e.g., create a new user).
  • PUT: Update an existing resource completely (e.g., replace all user information).
  • PATCH: Partially update an existing resource (e.g., update only the user’s email).
  • DELETE: Remove a resource from the server (e.g., delete a user).

HTTP Status Codes

Every API response includes a status code that tells the client what happened.

  • 200 OK: The request was successful.
  • 201 Created: A new resource was successfully created (used with POST).
  • 204 No Content: The request was successful, but there is no content to return (used with DELETE).
  • 400 Bad Request: The request was invalid (e.g., missing required data).
  • 404 Not Found: The requested resource does not exist.
  • 500 Internal Server Error: Something went wrong on the server.

What is FastAPI?

FastAPI is a modern, high-performance web framework for building APIs with Python. It was created by Sebastián Ramírez and was first released in 2018. It is designed to be fast to code, fast to run, and easy to use. FastAPI is built on top of Starlette (for the web parts) and Pydantic (for the data parts).

Key Features

  • High Performance: FastAPI is one of the fastest Python frameworks available, on par with Node.js and Go.
  • Automatic Interactive Documentation: FastAPI automatically generates interactive API documentation using Swagger UI and ReDoc. This is a huge time-saver for both development and testing.
  • Data Validation: Using Pydantic models, FastAPI automatically validates request data. If the data is invalid, it returns a clear error message to the client.
  • Type Hints: FastAPI leverages Python’s type hints to define data models, request parameters, and response models. This makes your code more readable and less error-prone.
  • Async Support: FastAPI supports asynchronous programming out of the box, making it ideal for I/O-bound operations like database calls and external API requests.

Why FastAPI is Popular

FastAPI has gained massive popularity in a short time. Here is why developers and companies are choosing it:

  • Developer Productivity: The combination of automatic documentation, data validation, and type hints reduces boilerplate code significantly. You write less code and get more done.
  • Performance: In benchmarks, FastAPI consistently outperforms other Python frameworks like Flask and Django. This makes it suitable for high-traffic applications.
  • Modern Python Features: FastAPI is built for Python 3.6+ and fully embraces modern Python features like async/await and type hints.
  • Community and Ecosystem: FastAPI has a vibrant community, extensive documentation, and a growing ecosystem of tools and extensions.
  • Production-Ready: Many large companies, including Microsoft, Uber, and Netflix, use FastAPI in production.

FastAPI vs Flask vs Django

Choosing the right framework is crucial for your project. Here is a comparison to help you understand when to use FastAPI, Flask, or Django.

Flask

  • Type: Micro-framework. Minimalist and flexible.
  • Use Case: Small to medium projects, prototypes, and microservices.
  • Pros: Simple, easy to learn, highly customizable.
  • Cons: No built-in data validation, no async support (without extensions), manual documentation.
  • Performance: Moderate.

Django

  • Type: Full-stack framework. “Batteries-included.”
  • Use Case: Large, complex applications, content management systems, data-driven sites.
  • Pros: Built-in ORM, admin panel, authentication, security features.
  • Cons: Heavier, steeper learning curve, less flexible for small APIs.
  • Performance: Moderate to low (compared to FastAPI).

FastAPI

  • Type: Modern web framework for APIs.
  • Use Case: High-performance APIs, real-time applications, microservices, machine learning model serving.
  • Pros: Extremely fast, automatic documentation, data validation, async support, type hints.
  • Cons: Newer framework (smaller ecosystem than Django), not a full-stack framework (no built-in ORM or admin panel).
  • Performance: Very high.

When to Choose FastAPI?

Choose FastAPI when you need a high-performance API with automatic documentation and data validation. It is ideal for building RESTful APIs, microservices, and real-time applications. If you are building a large, monolithic web application with a frontend, Django might be a better fit. For small, simple projects or learning, Flask is still a great option.

FastAPI Architecture Overview

Understanding the architecture of FastAPI will help you write better code and debug issues more effectively. FastAPI’s architecture is built on two main pillars: Starlette and Pydantic.

Starlette

Starlette is a lightweight ASGI (Asynchronous Server Gateway Interface) framework. FastAPI uses Starlette for all the web-related functionality: routing, middleware, request handling, and response generation. This means FastAPI inherits all of Starlette’s performance and flexibility.

Pydantic

Pydantic is a data validation library that uses Python type hints. FastAPI uses Pydantic models to define the shape of request and response data. When a request comes in, FastAPI automatically validates the data against the Pydantic model. If validation fails, it returns a 422 Unprocessable Entity error with details about what went wrong.

Request-Response Flow

  1. Client sends a request: The client (e.g., a browser or mobile app) sends an HTTP request to a specific URL (endpoint).
  2. FastAPI receives the request: The ASGI server (like Uvicorn) receives the request and passes it to FastAPI.
  3. Routing: FastAPI matches the request URL and HTTP method to the appropriate path operation function (the function you defined with a decorator like @app.get).
  4. Data Validation: If the request has data (e.g., JSON body, query parameters), FastAPI validates it against the Pydantic model you defined. If validation fails, an error response is returned immediately.
  5. Path Operation Function Executes: The function runs. It can do anything: query a database, call another API, process data, etc.
  6. Response is Generated: The function returns data (usually a Python dict or a Pydantic model). FastAPI automatically converts this to JSON and sends it back to the client with the appropriate HTTP status code.
  7. Client receives the response: The client receives the JSON response and processes it (e.g., displays it on a web page).

Practical: Your First FastAPI Application

Let’s put theory into practice. We will build a simple API that manages a list of books. This will cover the basics of defining routes, handling requests, and sending responses.

Step 1: Installation

First, make sure you have Python 3.7 or higher installed. Then, install FastAPI and Uvicorn (the ASGI server).

pip install fastapi uvicorn

Step 2: Create the Application

Create a new file called main.py and add the following code:

from fastapi import FastAPI

app = FastAPI()

# Sample data
books = [
    {"id": 1, "title": "The Great Gatsby", "author": "F. Scott Fitzgerald"},
    {"id": 2, "title": "1984", "author": "George Orwell"},
    {"id": 3, "title": "To Kill a Mockingbird", "author": "Harper Lee"},
]

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

@app.get("/books")
def get_books():
    return books

@app.get("/books/{book_id}")
def get_book(book_id: int):
    for book in books:
        if book["id"] == book_id:
            return book
    return {"error": "Book not found"}

Step 3: Run the Application

Open your terminal, navigate to the directory containing main.py, and run:

uvicorn main:app --reload

You should see output like this:

INFO:     Uvicorn running on http://127.0.0.1:8000
INFO:     (Press CTRL+C to quit)

Step 4: Test the API

Open your browser and go to http://127.0.0.1:8000. You should see the JSON response: {"message": "Welcome to the Book API!"}.

Now go to http://127.0.0.1:8000/books. You will see the list of all books.

Finally, go to http://127.0.0.1:8000/books/1. You will see the details of the book with ID 1.

Code Explanation

  • from fastapi import FastAPI: We import the FastAPI class.
  • app = FastAPI(): We create an instance of the FastAPI application.
  • books = [...]: This is our in-memory “database” – a simple list of dictionaries.
  • @app.get("/"): This is a decorator. It tells FastAPI that the function below handles GET requests to the root URL (“/”).
  • def read_root():: This is the path operation function. It runs when the endpoint is accessed.
  • return {"message": "Welcome to the Book API!"}: FastAPI automatically converts this Python dict to JSON and sends it as the response.
  • @app.get("/books/{book_id}"): The {book_id} is a path parameter. It captures the value from the URL.
  • def get_book(book_id: int):: The function parameter book_id is automatically extracted from the URL. The type hint : int tells FastAPI to validate that it is an integer.

Automatic Documentation

One of FastAPI’s best features is automatic documentation. While your server is running, go to http://127.0.0.1:8000/docs. You will see an interactive Swagger UI where you can test all your endpoints. Also, check http://127.0.0.1:8000/redoc for an alternative documentation view.

Common Mistakes

As a beginner, you will likely make some of these mistakes. Here is how to avoid them:

  • Forgetting to install Uvicorn: You cannot run FastAPI without an ASGI server. Always install Uvicorn with pip install uvicorn.
  • Running the wrong Uvicorn command: The correct command is uvicorn main:app --reload. The main is the filename (without .py), and app is the variable name of your FastAPI instance. The --reload flag automatically restarts the server when you make changes.
  • Not using type hints for path parameters: If you write def get_book(book_id): without the type hint, FastAPI will treat it as a string. Always use : int, : str, etc., to get automatic validation.
  • Returning non-serializable data: FastAPI converts your return value to JSON. Make sure you return Python objects that can be serialized (dicts, lists, strings, numbers, or Pydantic models).
  • Confusing path parameters with query parameters: Path parameters are part of the URL path (e.g., /books/1). Query parameters come after a ? in the URL (e.g., /books?author=Orwell). We will cover query parameters in the next module.

Practice Task

Now it’s your turn. Build a simple API for managing a list of tasks (a To-Do list). Your API should have the following endpoints:

  • GET /tasks – Returns a list of all tasks.
  • GET /tasks/{task_id} – Returns a specific task by its ID.

Each task should have an id (integer), a title (string), and a completed (boolean) field. Use a Python list as your in-memory database, just like in the example. Run your application and test it using the browser or the interactive docs at /docs.

Summary

In this first module, you learned the absolute fundamentals of APIs and FastAPI. You now understand:

  • What an API is and how it enables communication between software systems.
  • The basics of REST APIs, including HTTP methods and status codes.
  • What FastAPI is and why it has become so popular among developers.
  • How FastAPI compares to Flask and Django.
  • The high-level architecture of FastAPI, including Starlette and Pydantic.
  • How to create your first FastAPI application with multiple endpoints.
  • The request-response flow in FastAPI.

You have taken the first step toward becoming a professional FastAPI developer. The foundation is solid. In the next module, we will dive deeper into request handling, including path parameters, query parameters, and request bodies.

FAQs

1. Do I need to know Flask or Django before learning FastAPI?

No, you do not. FastAPI is beginner-friendly and can be learned directly. However, having basic Python knowledge is essential.

2. Can I use FastAPI for a full-stack web application?

FastAPI is primarily an API framework. You can use it as the backend for a full-stack application, but you will need a separate frontend framework (like React, Vue, or Angular) for the user interface.

3. Is FastAPI production-ready?

Yes, absolutely. FastAPI is used in production by companies like Microsoft, Uber, and Netflix. It is stable, well-tested, and has a strong community.

4. What is the difference between ASGI and WSGI?

WSGI (Web Server Gateway Interface) is the older standard for Python web applications. ASGI (Asynchronous Server Gateway Interface) is the modern successor that supports asynchronous programming. FastAPI uses ASGI, which makes it faster and more scalable than WSGI-based frameworks like Flask.

5. How do I deploy a FastAPI application?

FastAPI can be deployed using various methods: using Uvicorn behind a reverse proxy (like Nginx), using Docker, or using cloud platforms like Heroku, AWS, or Google Cloud. We will cover deployment in a later module.

Congratulations on completing Module 1! You now have a solid understanding of APIs and FastAPI. In Module 2: Request Handling & Data Validation, we will explore path parameters, query parameters, request bodies, and how to validate data using Pydantic models. Get ready to build more complex and robust APIs.

More Practical Examples

Let’s expand your understanding with several real-world examples that demonstrate FastAPI’s capabilities beyond the basic “Hello World” endpoint. These examples will show you how to handle different HTTP methods, work with query parameters, and return structured data.

Example 1: A Simple To-Do List API

This example creates a basic to-do list manager with endpoints to create, read, update, and delete tasks. It uses an in-memory list for simplicity, but the same pattern applies to database-backed applications.

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional

app = FastAPI()

# In-memory storage (replace with database in production)
todos = []
todo_id_counter = 1

# Pydantic model for request validation
class TodoCreate(BaseModel):
    title: str
    description: Optional[str] = None
    completed: bool = False

class Todo(TodoCreate):
    id: int

@app.post("/todos/", response_model=Todo, status_code=201)
async def create_todo(todo: TodoCreate):
    """Create a new todo item"""
    global todo_id_counter
    new_todo = Todo(id=todo_id_counter, **todo.dict())
    todos.append(new_todo)
    todo_id_counter += 1
    return new_todo

@app.get("/todos/", response_model=List[Todo])
async def get_all_todos():
    """Get all todos"""
    return todos

@app.get("/todos/{todo_id}", response_model=Todo)
async def get_todo(todo_id: int):
    """Get a specific todo by ID"""
    for todo in todos:
        if todo.id == todo_id:
            return todo
    raise HTTPException(status_code=404, detail="Todo not found")

@app.put("/todos/{todo_id}", response_model=Todo)
async def update_todo(todo_id: int, todo_update: TodoCreate):
    """Update a todo item"""
    for idx, todo in enumerate(todos):
        if todo.id == todo_id:
            updated_todo = Todo(id=todo_id, **todo_update.dict())
            todos[idx] = updated_todo
            return updated_todo
    raise HTTPException(status_code=404, detail="Todo not found")

@app.delete("/todos/{todo_id}", status_code=204)
async def delete_todo(todo_id: int):
    """Delete a todo item"""
    for idx, todo in enumerate(todos):
        if todo.id == todo_id:
            todos.pop(idx)
            return
    raise HTTPException(status_code=404, detail="Todo not found")

Explanation: This code demonstrates the full CRUD (Create, Read, Update, Delete) pattern using FastAPI. Notice how:

  • Pydantic models (TodoCreate and Todo) define the data structure and provide automatic validation.
  • HTTP methods map to operations: POST creates, GET reads, PUT updates, DELETE deletes.
  • Status codes are explicitly set (201 for creation, 204 for deletion without content).
  • Error handling uses HTTPException to return proper 404 responses.

Example 2: Query Parameters and Filtering

This example shows how to accept optional query parameters for filtering results, a common requirement in real APIs.

from fastapi import FastAPI, Query
from typing import Optional

app = FastAPI()

# Sample data
products = [
    {"id": 1, "name": "Laptop", "category": "electronics", "price": 999.99},
    {"id": 2, "name": "Book", "category": "education", "price": 19.99},
    {"id": 3, "name": "Phone", "category": "electronics", "price": 699.99},
    {"id": 4, "name": "Notebook", "category": "office", "price": 5.99},
]

@app.get("/products/")
async def get_products(
    category: Optional[str] = Query(None, description="Filter by category"),
    min_price: Optional[float] = Query(None, ge=0, description="Minimum price"),
    max_price: Optional[float] = Query(None, ge=0, description="Maximum price"),
    sort_by: Optional[str] = Query(None, regex="^(price|name)$", description="Sort field")
):
    """Get products with optional filtering and sorting"""
    filtered = products.copy()
    
    if category:
        filtered = [p for p in filtered if p["category"] == category]
    
    if min_price is not None:
        filtered = [p for p in filtered if p["price"] >= min_price]
    
    if max_price is not None:
        filtered = [p for p in filtered if p["price"] <= max_price]
    
    if sort_by == "price":
        filtered.sort(key=lambda x: x["price"])
    elif sort_by == "name":
        filtered.sort(key=lambda x: x["name"])
    
    return filtered

Explanation: This example introduces query parameters – values passed in the URL after ? (e.g., /products/?category=electronics&min_price=100). Key points:

  • Query() allows you to add metadata like descriptions and validation rules (e.g., ge=0 for non-negative prices, regex for allowed sort fields).
  • Parameters are Optional so the API works without them.
  • The function filters and sorts the in-memory list based on provided parameters.

Example 3: Path Parameters with Validation

Path parameters are essential for identifying specific resources. FastAPI provides built-in validation.

from fastapi import FastAPI, Path

app = FastAPI()

@app.get("/users/{user_id}/posts/{post_id}")
async def get_user_post(
    user_id: int = Path(..., ge=1, description="The user ID"),
    post_id: int = Path(..., ge=1, description="The post ID")
):
    """Get a specific post for a specific user"""
    # In a real app, you'd query a database here
    return {
        "user_id": user_id,
        "post_id": post_id,
        "message": f"Fetching post {post_id} for user {user_id}"
    }

Explanation: Path parameters are extracted from the URL path itself (e.g., /users/42/posts/101). The Path() function adds validation:

  • ... means the parameter is required.
  • ge=1 ensures the value is at least 1.
  • FastAPI automatically converts string paths to the declared type (int in this case).

Class-Based Example

While FastAPI primarily uses function-based views, you can also organize endpoints using classes with the APIRouter and class-based views for better code organization in larger applications.

Using APIRouter for Modular Code

This example shows how to split your API into logical modules using APIRouter, which is the recommended approach for organizing endpoints.

# app/main.py
from fastapi import FastAPI
from app.routers import items, users

app = FastAPI()

app.include_router(users.router, prefix="/users", tags=["users"])
app.include_router(items.router, prefix="/items", tags=["items"])

@app.get("/")
async def root():
    return {"message": "Welcome to the modular API!"}
# app/routers/users.py
from fastapi import APIRouter

router = APIRouter()

@router.get("/")
async def get_users():
    return [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]

@router.get("/{user_id}")
async def get_user(user_id: int):
    return {"id": user_id, "name": f"User {user_id}"}
# app/routers/items.py
from fastapi import APIRouter
from pydantic import BaseModel

router = APIRouter()

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

@router.get("/")
async def get_items():
    return [{"name": "Laptop", "price": 999.99}]

@router.post("/")
async def create_item(item: Item):
    return {"message": f"Created item {item.name} with price {item.price}"}

Explanation: This modular approach offers several benefits:

  • Separation of concerns: Each router handles a specific domain (users, items).
  • Prefix support: The prefix="/users" automatically prepends to all routes in that router.
  • Tags: The tags parameter groups endpoints in the auto-generated documentation.
  • Scalability: As your application grows, you can add more routers without cluttering the main file.

Class-Based Endpoints with APIRouter

For even more structure, you can use classes with the @router.api_route decorator or create custom endpoint classes. Here’s a practical example using a class to group related operations:

from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from typing import List, Optional

router = APIRouter()

class Task(BaseModel):
    id: int
    title: str
    completed: bool = False

class TaskManager:
    def __init__(self):
        self.tasks = []
        self.counter = 1
    
    def create(self, title: str) -> Task:
        task = Task(id=self.counter, title=title)
        self.tasks.append(task)
        self.counter += 1
        return task
    
    def get_all(self) -> List[Task]:
        return self.tasks
    
    def get(self, task_id: int) -> Task:
        for task in self.tasks:
            if task.id == task_id:
                return task
        raise HTTPException(status_code=404, detail="Task not found")
    
    def update(self, task_id: int, title: str, completed: bool) -> Task:
        task = self.get(task_id)
        task.title = title
        task.completed = completed
        return task
    
    def delete(self, task_id: int) -> None:
        task = self.get(task_id)
        self.tasks.remove(task)

manager = TaskManager()

@router.post("/tasks/", response_model=Task, status_code=201)
async def create_task(title: str):
    return manager.create(title)

@router.get("/tasks/", response_model=List[Task])
async def list_tasks():
    return manager.get_all()

@router.get("/tasks/{task_id}", response_model=Task)
async def get_task(task_id: int):
    return manager.get(task_id)

@router.put("/tasks/{task_id}", response_model=Task)
async def update_task(task_id: int, title: str, completed: bool):
    return manager.update(task_id, title, completed)

@router.delete("/tasks/{task_id}", status_code=204)
async def delete_task(task_id: int):
    manager.delete(task_id)

Explanation: This class-based approach encapsulates all business logic within the TaskManager class, while the endpoint functions remain simple and focused on HTTP concerns. This pattern is especially useful when you need to maintain state or complex operations across multiple endpoints.

Step-by-Step Exercise

Now it’s your turn to build a complete FastAPI application from scratch. Follow these steps to create a simple “Book Library” API.

Exercise: Build a Book Library API

Objective: Create a RESTful API that manages a collection of books with the ability to add, list, search, and delete books.

Step 1: Set up the project

# Create a new directory and virtual environment
mkdir book-library-api
cd book-library-api
python -m venv venv
source venv/bin/activate  # On Windows: venvScriptsactivate

# Install FastAPI and Uvicorn
pip install fastapi uvicorn

Step 2: Create the main application file

Create a file called main.py with the following starter code:

from fastapi import FastAPI, HTTPException, Query
from pydantic import BaseModel
from typing import List, Optional

app = FastAPI(title="Book Library API")

# In-memory storage
books = []
book_id_counter = 1

# Pydantic models
class BookCreate(BaseModel):
    title: str
    author: str
    year: int
    isbn: Optional[str] = None

class Book(BookCreate):
    id: int

Step 3: Implement the endpoints

Add these endpoint functions to your main.py file:

@app.post("/books/", response_model=Book, status_code=201)
async def add_book(book: BookCreate):
    """Add a new book to the library"""
    global book_id_counter
    new_book = Book(id=book_id_counter, **book.dict())
    books.append(new_book)
    book_id_counter += 1
    return new_book

@app.get("/books/", response_model=List[Book])
async def list_books(
    author: Optional[str] = Query(None, description="Filter by author"),
    year: Optional[int] = Query(None, description="Filter by publication year")
):
    """List all books, with optional filtering"""
    if author and year:
        return [b for b in books if b.author == author and b.year == year]
    elif author:
        return [b for b in books if b.author == author]
    elif year:
        return [b for b in books if b.year == year]
    return books

@app.get("/books/{book_id}", response_model=Book)
async def get_book(book_id: int):
    """Get a specific book by ID"""
    for book in books:
        if book.id == book_id:
            return book
    raise HTTPException(status_code=404, detail="Book not found")

@app.delete("/books/{book_id}", status_code=204)
async def delete_book(book_id: int):
    """Delete a book by ID"""
    for idx, book in enumerate(books):
        if book.id == book_id:
            books.pop(idx)
            return
    raise HTTPException(status_code=404, detail="Book not found")

Step 4: Run and test the API

# Start the server
uvicorn main:app --reload

Your API will be available at http://127.0.0.1:8000. Test it using curl commands or the interactive documentation at http://127.0.0.1:8000/docs.

Step 5: Test with curl commands

# Add a book
curl -X POST "http://127.0.0.1:8000/books/" 
  -H "Content-Type: application/json" 
  -d '{"title": "1984", "author": "George Orwell", "year": 1949}'

# Add another book
curl -X POST "http://127.0.0.1:8000/books/" 
  -H "Content-Type: application/json" 
  -d '{"title": "To Kill a Mockingbird", "author": "Harper Lee", "year": 1960}'

# List all books
curl "http://127.0.0.1:8000/books/"

# Filter by author
curl "http://127.0.0.1:8000/books/?author=George%20Orwell"

# Get a specific book
curl "http://127.0.0.1:8000/books/1"

# Delete a book
curl -X DELETE "http://127.0.0.1:8000/books/1"

Step 6: Verify the interactive docs

Open http://127.0.0.1:8000/docs in your browser. You should see the automatically generated Swagger UI documentation where you can test all endpoints interactively.

Challenge: Extend the API by adding a PUT /books/{book_id} endpoint that updates a book’s details. Use the same BookCreate model for the request body.

Interview and Job Use Cases

Understanding FastAPI is increasingly valuable in the job market. Here are common interview questions and real-world scenarios where FastAPI skills are essential.

Common Interview Questions

  1. “What are the main advantages of FastAPI over Flask?”
    Answer: FastAPI offers automatic OpenAPI documentation, built-in data validation with Pydantic, async support out of the box, and significantly better performance due to Starlette’s asynchronous foundation. It also provides automatic request/response serialization and dependency injection.
  2. “How does FastAPI handle data validation?”
    Answer: FastAPI uses Pydantic models for data validation. When you define a model with type annotations, FastAPI automatically validates incoming JSON against that schema, returns meaningful error messages for invalid data, and converts data to the correct Python types.
  3. “Explain dependency injection in FastAPI.”
    Answer: FastAPI’s dependency injection system allows you to define reusable components (like database connections, authentication checks, or configuration) that can be injected into multiple endpoints. Dependencies are declared as function parameters and FastAPI automatically resolves and provides them.
  4. “How do you handle authentication in FastAPI?”
    Answer: FastAPI supports various authentication methods including OAuth2 with JWT tokens, API keys, and basic authentication. You can create dependency functions that validate tokens or credentials and apply them to specific routes or globally.
  5. “What is the difference between a path parameter and a query parameter?”
    Answer: Path parameters are part of the URL path (e.g., /users/42) and are used to identify specific resources. Query parameters appear after ? in the URL (e.g., /users/?page=2) and are used for filtering, sorting, or pagination.

Real-World Job Scenarios

Scenario 1: Building a Microservice for an E-commerce Platform
A company needs a high-performance inventory management service that can handle thousands of requests per second. FastAPI’s async capabilities and automatic documentation make it ideal for this use case. The API would need endpoints for checking stock levels, updating inventory, and processing orders with proper validation and error handling.

Scenario 2: Creating a Machine Learning Model Serving API
Data scientists often need to deploy ML models as APIs. FastAPI’s Pydantic integration allows for strict input validation (ensuring the model receives correctly formatted data), while its async support enables concurrent prediction requests. The automatic OpenAPI docs make it easy for frontend teams to understand the API contract.

Scenario 3: Developing a Backend for a Mobile Application
Mobile apps require fast, reliable APIs with clear documentation. FastAPI’s auto-generated Swagger UI becomes the single source of truth for the mobile development team. Features like WebSocket support (for real-time features) and background tasks (for sending push notifications) are particularly useful.

Key Skills Employers Look For

  • Understanding of RESTful design principles (HTTP methods, status codes, resource naming)
  • Proficiency with Pydantic models for data validation and serialization
  • Experience with async/await for handling concurrent requests
  • Knowledge of dependency injection for clean, modular code
  • Familiarity with authentication (OAuth2, JWT, API keys)
  • Ability to write automated tests using FastAPI’s TestClient
  • Database integration skills (SQLAlchemy, Tortoise-ORM, MongoDB)

Extra Beginner FAQs

Here are answers to common questions that beginners often ask when learning FastAPI.

Q1: Do I need to know async/await to use FastAPI?

No, you don’t. FastAPI works perfectly with regular synchronous functions. You can define endpoints as def (synchronous) instead of async def. However, if you’re making I/O-bound operations (database queries, HTTP requests), using async functions can improve performance by allowing other requests to be processed while waiting for I/O.

Q2: How is FastAPI different from Flask for small projects?

For very small projects (like a single endpoint), Flask might be simpler because it has less boilerplate. However, FastAPI’s advantages (automatic docs, validation, better performance) become apparent as soon as you add a second endpoint or need to handle complex data. The learning curve is similar, but FastAPI scales much better.

Q3: Can I use FastAPI with a database?

Absolutely. FastAPI integrates seamlessly with popular databases. For SQL databases, you’d typically use SQLAlchemy or Tortoise-ORM. For MongoDB, you’d use Beanie or Motor. The dependency injection system makes it easy to manage database sessions and connections.

Q4: How do I deploy a FastAPI application?

FastAPI applications can be deployed like any Python web application. Common options include:

  • Uvicorn/Gunicorn with a reverse proxy (Nginx, Apache)
  • Docker containers for containerized deployments
  • Cloud platforms like Heroku, AWS Elastic Beanstalk, Google Cloud Run, or DigitalOcean App Platform
  • Serverless with AWS Lambda or Google Cloud Functions (using Mangum adapter)

Q5: What is the difference between FastAPI and Django REST Framework (DRF)?

FastAPI is a micro-framework focused on APIs, while Django REST Framework is a full-stack framework built on top of Django. FastAPI is generally faster, has better async support, and generates OpenAPI docs automatically. DRF offers more built-in features (admin panel, ORM, authentication system) but is heavier and synchronous by default. Choose FastAPI for microservices and performance-critical APIs; choose DRF for large monolithic applications that need a built-in admin interface.

Q6: How do I handle CORS in FastAPI?

FastAPI has built-in CORS support through the CORSMiddleware. You add it to your application like this:

from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # In production, specify your frontend domain
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

Q7: Can I use FastAPI with GraphQL?

Yes. While FastAPI is primarily REST-focused, you can integrate GraphQL using libraries like Strawberry or Graphene. FastAPI’s async support works well with GraphQL resolvers, and you can even have both REST and GraphQL endpoints in the same application.

Q8: How do I handle file uploads in FastAPI?

FastAPI makes file uploads straightforward using the UploadFile class:

from fastapi import FastAPI, UploadFile, File

app = FastAPI()

@app.post("/upload/")
async def upload_file(file: UploadFile = File(...)):
    contents = await file.read()
    # Process the file contents
    return {"filename": file.filename, "size": len(contents)}

Explanation: The UploadFile class provides async methods for reading file contents, and FastAPI automatically handles multipart form data parsing. You can also accept multiple files by using List[UploadFile].

Q9: What is the best way to structure a FastAPI project?

For medium to large projects, a common structure is:

project/
├── app/
│   ├── __init__.py
│   ├── main.py          # FastAPI application instance
│   ├── config.py        # Configuration settings
│   ├── database.py      # Database connection
│   ├── models/          # Pydantic models
│   ├── routers/         # APIRouter modules
│   ├── services/        # Business logic
│   └── dependencies.py  # Shared dependencies
├── tests/
├── requirements.txt
└── Dockerfile

This structure keeps your code organized, testable, and scalable as your application grows.

Q10: How do I add logging to my FastAPI application?

FastAPI uses Python’s standard logging module. You can configure it in your main application file:

import logging
from fastapi import FastAPI

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

app = FastAPI()

@app.get("/")
async def root():
    logger.info("Root endpoint called")
    return {"message": "Hello World"}

For more advanced logging (request IDs, structured logging), you can use libraries like loguru or integrate with monitoring tools like Sentry or Datadog.

Leave a Reply

Your email address will not be published. Required fields are marked *