Python FastAPI
Learn how to build modern, fast, type-safe APIs with FastAPI, including request validation, Pydantic models, automatic documentation, dependency injection, and asynchronous endpoints.
Learning Objectives
FastAPI builds on several concepts you have already learned: functions, type hints, dictionaries, JSON, HTTP, REST APIs, and asynchronous programming.
By the end of this lesson, you will be able to:
- Understand what FastAPI is.
- Explain why FastAPI is popular for API development.
- Install and run a FastAPI application.
- Create API endpoints.
- Work with GET, POST, PUT, PATCH, and DELETE.
- Use path and query parameters.
- Accept JSON request bodies.
- Use Pydantic models for validation.
- Define response models.
- Return appropriate HTTP status codes.
- Handle API errors.
- Understand dependency injection.
- Use FastAPI's automatic documentation.
- Understand basic asynchronous endpoints.
What Is FastAPI?
FastAPI is a modern Python web framework designed primarily for building APIs.
It combines Python type hints with automatic request validation and API documentation.
The key idea
You describe your API using normal Python functions, type hints, and models. FastAPI uses that information to validate data and generate documentation automatically.
FastAPI is especially useful for backend services that communicate with websites, mobile applications, data applications, and other services.
Why FastAPI?
FastAPI has become popular because it provides a productive development experience while supporting modern Python features.
Important advantages
- Excellent support for Python type hints.
- Automatic validation of request data.
- Automatic interactive API documentation.
- Support for asynchronous endpoints.
- Clear API design.
- Excellent developer experience.
- Suitable for microservices and backend APIs.
FastAPI also works closely with the Pydantic library for data validation and serialization.
Installing FastAPI
Install FastAPI together with an ASGI server such as Uvicorn:
python -m pip install fastapi uvicorn
You can verify the installation with:
python -m pip show fastapi
python -m pip show uvicorn
Use a virtual environment
As with Flask and other Python projects, install FastAPI inside a project-specific virtual environment whenever possible.
Your First FastAPI Application
Create a file named main.py.
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def home():
return {
"message": "Hello, PyLearn!"
}
The application object is created with:
app = FastAPI()
The route:
@app.get("/")
tells FastAPI that the function should handle GET requests to the root URL.
Notice the difference
Flask commonly uses @app.route(), while
FastAPI provides method-specific decorators such as
@app.get(), @app.post(),
and @app.delete().
Running the Server
Start the FastAPI application with Uvicorn:
uvicorn main:app --reload
The command means:
-
main— the Python filemain.py. -
app— the FastAPI application object. -
--reload— automatically reload during development when source files change.
You can normally access the application at:
http://127.0.0.1:8000
Alternative command
python -m uvicorn main:app --reload
Routes & Path Operations
FastAPI calls its endpoint declarations path operations.
@app.get("/products")
def get_products():
return {
"message": "Product list"
}
@app.post("/products")
def create_product():
return {
"message": "Product created"
}
Each decorator connects a URL and HTTP method to a function.
@app.get()@app.post()@app.put()@app.patch()@app.delete()
HTTP Methods
REST APIs commonly use different HTTP methods to represent different operations.
- GET — retrieve data.
- POST — create data.
- PUT — replace a resource.
- PATCH — partially update a resource.
- DELETE — remove a resource.
Example
@app.get("/products")
def get_products():
return {"action": "get"}
@app.post("/products")
def create_product():
return {"action": "create"}
@app.put("/products/1")
def replace_product():
return {"action": "replace"}
@app.patch("/products/1")
def update_product():
return {"action": "update"}
@app.delete("/products/1")
def delete_product():
return {"action": "delete"}
Path Parameters
Path parameters allow a value from the URL to be passed directly into a Python function.
@app.get("/products/{product_id}")
def get_product(product_id: int):
return {
"product_id": product_id
}
A request such as:
/products/25
passes 25 to the function.
Type hints do useful work
Because product_id is declared as an
integer, FastAPI can validate and convert compatible
input automatically.
Query Parameters
Query parameters appear after ? in a URL.
/products?category=phones&limit=10
FastAPI can recognize query parameters directly from function parameters.
@app.get("/products")
def get_products(
category: str | None = None,
limit: int = 10
):
return {
"category": category,
"limit": limit
}
The URL:
/products?category=phones&limit=5
produces values similar to:
{
"category": "phones",
"limit": 5
}
Request Body
POST and PUT requests commonly send structured JSON data in the request body.
For example:
{
"name": "Laptop",
"price": 1200,
"quantity": 5
}
FastAPI can automatically parse and validate this data using a model.
This is one of the areas where FastAPI's integration with Pydantic becomes extremely useful.
Pydantic Models
A Pydantic model describes the structure of data that your API expects.
from pydantic import BaseModel
class Product(BaseModel):
name: str
price: float
quantity: int
You can then use the model as a function parameter.
@app.post("/products")
def create_product(product: Product):
return product
FastAPI understands that the endpoint expects a JSON object
matching the Product model.
Connection to previous lessons
This is where your earlier knowledge of type hints becomes directly useful in professional API development.
Automatic Validation
FastAPI and Pydantic can validate incoming data according to the types and rules defined by your models.
from pydantic import BaseModel, Field
class Product(BaseModel):
name: str
price: float = Field(
gt=0
)
quantity: int = Field(
ge=0
)
Here:
-
pricemust be greater than zero. -
quantitymust be zero or greater.
Why validation matters
Validation prevents invalid data from entering your application's business logic and makes API behavior more predictable.
Response Models
You can also define the structure of the data your API should return.
class ProductResponse(BaseModel):
id: int
name: str
price: float
@app.get(
"/products/{product_id}",
response_model=ProductResponse
)
def get_product(product_id: int):
return {
"id": product_id,
"name": "Laptop",
"price": 1200
}
Response models help keep your API output consistent and documented.
Useful concept
A response model can also help prevent unintended fields from being exposed in an API response.
HTTP Status Codes
APIs should communicate the result of an operation using appropriate HTTP status codes.
from fastapi import FastAPI, status
@app.post(
"/products",
status_code=status.HTTP_201_CREATED
)
def create_product():
return {
"message": "Product created"
}
- 200 — OK
- 201 — Created
- 204 — No Content
- 400 — Bad Request
- 401 — Unauthorized
- 403 — Forbidden
- 404 — Not Found
- 422 — Validation-related request error
- 500 — Internal Server Error
JSON Responses
Returning a Python dictionary from a FastAPI endpoint normally produces a JSON response.
@app.get("/api/products")
def products():
return {
"products": [
{
"id": 1,
"name": "Laptop",
"price": 1200
},
{
"id": 2,
"name": "Phone",
"price": 800
}
]
}
The client receives structured JSON that can easily be consumed by JavaScript, mobile applications, or other backend services.
Headers
HTTP headers carry additional information about requests and responses.
from fastapi import FastAPI, Header
app = FastAPI()
@app.get("/profile")
def profile(
user_agent: str | None = Header(default=None)
):
return {
"user_agent": user_agent
}
FastAPI can map HTTP headers into Python function parameters.
Common header uses
- Authentication information
- Content type
- Client information
- Request tracing
- Caching instructions
Error Handling
FastAPI provides HTTPException for returning
HTTP errors from your application.
from fastapi import FastAPI, HTTPException
app = FastAPI()
@app.get("/products/{product_id}")
def get_product(product_id: int):
if product_id != 1:
raise HTTPException(
status_code=404,
detail="Product not found"
)
return {
"id": 1,
"name": "Laptop"
}
The client receives an appropriate HTTP error response instead of an unexplained application failure.
Good API behavior
Error responses should be meaningful without exposing sensitive internal implementation details.
Dependency Injection
FastAPI includes a dependency injection system that allows reusable logic to be shared across endpoints.
from fastapi import Depends, FastAPI
app = FastAPI()
def get_database():
return "Database Connection"
@app.get("/products")
def products(
database=Depends(get_database)
):
return {
"database": database
}
Dependencies can be used for things such as authentication, database connections, reusable parameters, and shared application logic.
Why dependencies are useful
Instead of copying the same logic into every endpoint, you can define it once and inject it wherever required.
Automatic API Documentation
One of FastAPI's most useful features is automatic interactive API documentation.
After starting your application, FastAPI normally provides interactive documentation at:
/docs
For example:
http://127.0.0.1:8000/docs
FastAPI also provides an alternative documentation interface at:
/redoc
Why this matters
Developers can inspect available endpoints, parameters, request models, response models, and test API requests directly through the generated documentation.
Async Endpoints
FastAPI supports Python's asynchronous programming model.
from fastapi import FastAPI
app = FastAPI()
@app.get("/products")
async def products():
return {
"message": "Async endpoint"
}
An asynchronous function uses async def.
When an operation supports asynchronous waiting, you can
use await.
import asyncio
@app.get("/wait")
async def wait_example():
await asyncio.sleep(1)
return {
"message": "Finished waiting"
}
Important
Adding async does not automatically make
every operation faster. It is most useful when your
application spends time waiting for asynchronous I/O,
such as network or database operations.
Professional FastAPI Project Structure
A larger API should be organized into modules instead of putting every endpoint inside one file.
my_api/
│
├── app/
│ ├── main.py
│ │
│ ├── routers/
│ │ ├── products.py
│ │ └── users.py
│ │
│ ├── models/
│ │ └── product.py
│ │
│ ├── services/
│ │ └── product_service.py
│ │
│ └── database.py
│
├── tests/
│
├── requirements.txt
│
└── .env
This structure separates routing, models, business logic, database access, and testing.
Grow your architecture gradually
A small learning project does not need this entire structure. Introduce additional layers when the application becomes large enough to benefit from them.
Practical Example — Ecommerce Product API
Let's combine routes, path parameters, Pydantic models, validation, and status codes into a small ecommerce API.
main.py
from fastapi import (
FastAPI,
HTTPException,
status
)
from pydantic import BaseModel, Field
app = FastAPI()
class Product(BaseModel):
name: str
price: float = Field(
gt=0
)
quantity: int = Field(
ge=0
)
products = []
@app.get("/products")
def get_products():
return products
@app.post(
"/products",
status_code=status.HTTP_201_CREATED
)
def create_product(product: Product):
products.append(product)
return product
@app.get("/products/{product_id}")
def get_product(product_id: int):
if product_id >= len(products):
raise HTTPException(
status_code=404,
detail="Product not found"
)
return products[product_id]
Example JSON Request
{
"name": "Laptop",
"price": 1200,
"quantity": 10
}
FastAPI validates the incoming structure before passing the data into your endpoint.
Real-world connection
A production ecommerce API would normally add a real database, authentication, authorization, pagination, logging, testing, configuration management, and additional business rules.
Common FastAPI Mistakes
Mistake 1 — Forgetting to start Uvicorn
Defining an API does not automatically start a web server. You need to run the ASGI server during development.
Mistake 2 — Incorrect module path
If your file is main.py and your application
object is named app, the command is
uvicorn main:app --reload.
Mistake 3 — Ignoring validation
Do not assume that client input is correct. Use appropriate models and validation rules.
Mistake 4 — Returning inappropriate status codes
An API should clearly communicate whether an operation succeeded, failed, created a resource, or could not find a resource.
Mistake 5 — Putting everything in one file
Small examples are fine in one file, but larger applications should separate routes, models, services, and database logic.
Mistake 6 — Misunderstanding async
Do not use asynchronous syntax simply because it looks modern. Understand whether the operation benefits from asynchronous I/O.
Practice Exercises
Exercise 1 — First API
Create a FastAPI application with a root endpoint that returns a JSON welcome message.
Exercise 2 — Product Route
Create a GET endpoint at
/products.
Exercise 3 — Path Parameter
Create
/products/{product_id}
using an integer path parameter.
Exercise 4 — Query Parameters
Add category and limit
query parameters to your product endpoint.
Exercise 5 — Pydantic Model
Create a Product model containing name, price, and quantity.
Exercise 6 — Validation
Make sure price must be greater than zero and quantity cannot be negative.
Exercise 7 — API Error
Return a 404 error when a requested product does not exist.
Exercise 8 — Documentation
Run your application and explore the automatically
generated /docs interface.
Mini Challenge — Ecommerce Product API
Build a small but professional REST API for an ecommerce product catalog.
Your Tasks
- Create a FastAPI application.
-
Create a
ProductPydantic model. - Include id, name, category, price, and quantity.
- Validate that price is greater than zero.
- Validate that quantity is not negative.
-
Create GET
/products. -
Create GET
/products/{id}. -
Create POST
/products. - Create PUT or PATCH for updating products.
- Create DELETE for removing products.
- Return a 404 error when a product does not exist.
- Use appropriate HTTP status codes.
- Add query parameters for category filtering.
- Add a response model.
-
Test every endpoint through
/docs.
Suggested Product Model
from pydantic import BaseModel, Field
class Product(BaseModel):
id: int
name: str
category: str
price: float = Field(
gt=0
)
quantity: int = Field(
ge=0
)
Challenge goal
Do not think of this as just a coding exercise. Think of yourself as an API developer designing a backend that another application could actually consume.
Knowledge Check
Test your understanding of FastAPI.
1. What is FastAPI mainly used for?
2. Which decorator creates a GET endpoint?
3. What is Pydantic commonly used for in FastAPI?
4. What is a path parameter?
5. Which status code normally indicates a newly created resource?
6. Which command starts a development FastAPI server?
7. Where can you normally find FastAPI's interactive documentation?
8. What does Depends() help implement?
9. What keyword is used to define an asynchronous function?
10. Why are type hints particularly useful in FastAPI?
Lesson Summary
FastAPI provides a modern approach to building Python APIs. It combines Python type hints, validation, automatic documentation, and asynchronous capabilities.
Key concepts to remember
- FastAPI is a modern Python framework for APIs.
-
FastAPI()creates the application. -
@app.get(),@app.post(), and other decorators define endpoints. - Path parameters are embedded in URLs.
- Query parameters provide optional URL input.
- Pydantic models define and validate structured data.
- Response models define expected API output.
- HTTP status codes communicate operation results.
-
HTTPExceptionhandles API errors. -
Depends()supports dependency injection. - FastAPI automatically generates API documentation.
-
async defcan be used for asynchronous endpoints. - Uvicorn is commonly used to run FastAPI applications.
What's next?
You have now learned Flask and FastAPI. Next, we move into Async Python and examine asynchronous programming more deeply so you understand what actually happens when Python handles concurrent I/O operations.
Ready for the next lesson?
Mark this lesson complete when you can create a basic FastAPI application, define API endpoints, validate request data, and test your API through the interactive documentation.