
REST APIs with FastAPI
Welcome back, Python enthusiast! Today, we're diving into one of the most exciting modern frameworks for building APIs: FastAPI. If you've been looking for a way to create high-performance, easy-to-write, and automatically documented APIs, you're in the right place. FastAPI is built on standard Python type hints, and it leverages Pydantic for data validation and Starlette for web handling. The result? An incredibly fast and developer-friendly experience.
Why FastAPI?
You might be wondering why you should choose FastAPI over other frameworks like Flask or Django REST Framework. The answer lies in its speed, simplicity, and out-of-the-box features. FastAPI is one of the fastest Python frameworks available, thanks to its asynchronous capabilities. It also provides automatic interactive API documentation, which is a huge time-saver during development and testing.
Here's a quick comparison of request handling speeds among popular Python web frameworks (requests per second):
Framework | Requests per Second |
---|---|
FastAPI | 5,900 |
Flask | 1,900 |
Django | 1,400 |
Tornado | 3,200 |
As you can see, FastAPI outperforms many alternatives, making it an excellent choice for high-throughput applications.
Getting Started with FastAPI
Let's jump right in and create your first FastAPI application. First, you'll need to install FastAPI and an ASGI server, such as Uvicorn. You can do this using pip:
pip install fastapi uvicorn
Now, create a new Python file (e.g., main.py
) and add the following code:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
return {"item_id": item_id, "q": q}
This simple example defines two endpoints: a root endpoint that returns a JSON response and an endpoint that takes a path parameter (item_id
) and an optional query parameter (q
).
To run your application, use the following command:
uvicorn main:app --reload
The --reload
flag enables auto-reload so your server restarts whenever you make changes to the code. Now, open your browser and go to http://localhost:8000
. You should see your {"Hello": "World"}
response. Even better, navigate to http://localhost:8000/docs
to see the automatically generated interactive API documentation—Swagger UI!
Path Parameters and Query Parameters
In FastAPI, you can define path parameters and query parameters directly in your function definitions using type hints. Path parameters are part of the URL path, while query parameters are appended to the URL after a ?
.
from fastapi import FastAPI
app = FastAPI()
@app.get("/users/{user_id}")
def get_user(user_id: int):
return {"user_id": user_id}
@app.get("/items/")
def read_items(skip: int = 0, limit: int = 10):
return {"skip": skip, "limit": limit}
In the first endpoint, user_id
is a path parameter. In the second, skip
and limit
are query parameters with default values.
Request Bodies with Pydantic Models
For more complex data, especially when you need to receive data from the client (e.g., in a POST request), you can use Pydantic models to define the structure of the request body. This is where FastAPI truly shines, as it automatically validates incoming data against your model.
First, define a Pydantic model:
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str = None
price: float
tax: float = None
Now, use it in your endpoint:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str = None
price: float
tax: float = None
@app.post("/items/")
def create_item(item: Item):
return item
When you send a POST request to /items/
with a JSON body, FastAPI will automatically validate the input based on the Item
model. If the data doesn't match, it returns a descriptive error.
Handling HTTP Methods
FastAPI supports all standard HTTP methods: GET, POST, PUT, DELETE, etc. You can define endpoints for each method using decorators like @app.post
, @app.put
, and @app.delete
.
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
items = []
@app.get("/items")
def get_items():
return items
@app.post("/items")
def add_item(item: Item):
items.append(item)
return item
@app.put("/items/{item_id}")
def update_item(item_id: int, item: Item):
if item_id < len(items):
items[item_id] = item
return item
return {"error": "Item not found"}
@app.delete("/items/{item_id}")
def delete_item(item_id: int):
if item_id < len(items):
deleted_item = items.pop(item_id)
return deleted_item
return {"error": "Item not found"}
This example demonstrates a simple CRUD (Create, Read, Update, Delete) API for managing a list of items.
Dependency Injection
FastAPI has a powerful dependency injection system that helps you manage shared logic, such as authentication, database sessions, or configuration. Dependencies are reusable components that you can inject into your path operations.
Here's a simple example of a dependency that checks for a query parameter:
from fastapi import FastAPI, Depends
app = FastAPI()
def common_parameters(q: str = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
def read_items(commons: dict = Depends(common_parameters)):
return commons
The common_parameters
function is a dependency that extracts query parameters. It's then injected into the read_items
endpoint using the Depends
function.
Error Handling
FastAPI makes it easy to handle errors and return appropriate HTTP status codes. You can raise HTTPException
to return custom error responses.
from fastapi import FastAPI, HTTPException
app = FastAPI()
items = {"foo": "The Foo Wrestlers"}
@app.get("/items/{item_id}")
def read_item(item_id: str):
if item_id not in items:
raise HTTPException(status_code=404, detail="Item not found")
return {"item": items[item_id]}
In this example, if the item_id
is not found in the items
dictionary, an HTTP 404 error is raised with a custom message.
Middleware
You can add middleware to your FastAPI application to process requests and responses globally. Middleware functions run for every request before reaching the path operation and after generating the response.
Here's an example of adding a custom middleware that logs request processing time:
import time
from fastapi import FastAPI, Request
app = FastAPI()
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
start_time = time.time()
response = await call_next(request)
process_time = time.time() - start_time
response.headers["X-Process-Time"] = str(process_time)
return response
This middleware calculates the time taken to process each request and adds it as a header in the response.
Testing Your API
Testing is a crucial part of API development. FastAPI provides a TestClient
that allows you to test your application without running a server.
First, install pytest
and requests
:
pip install pytest requests
Now, create a test file (e.g., test_main.py
):
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
def test_read_root():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"Hello": "World"}
def test_read_item():
response = client.get("/items/42?q=test")
assert response.status_code == 200
assert response.json() == {"item_id": 42, "q": "test"}
Run your tests with:
pytest
Deploying Your FastAPI Application
Once your API is ready, you'll want to deploy it. FastAPI applications can be deployed using any ASGI server, such as Uvicorn or Hypercorn. For production, it's recommended to use a process manager like Gunicorn with Uvicorn workers.
Install Gunicorn and Uvicorn:
pip install gunicorn uvicorn
Then, run your application with:
gunicorn -w 4 -k uvicorn.workers.UvicornWorker main:app
This command starts Gunicorn with 4 worker processes, each running an instance of your FastAPI app.
Best Practices for FastAPI Development
To make the most of FastAPI, follow these best practices:
- Use Pydantic models for all request and response bodies to leverage automatic validation and documentation.
- Take advantage of FastAPI's dependency injection to keep your code clean and reusable.
- Write tests for your endpoints to ensure reliability.
- Use async and await for I/O-bound operations to improve performance.
- Secure your API by implementing authentication and authorization, such as OAuth2 with JWT tokens.
Here's a quick example of implementing OAuth2 password flow:
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from pydantic import BaseModel
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
class User(BaseModel):
username: str
email: str = None
full_name: str = None
def fake_decode_token(token):
return User(username=token + "faked", email="john@example.com", full_name="John Doe")
async def get_current_user(token: str = Depends(oauth2_scheme)):
user = fake_decode_token(token)
return user
@app.get("/users/me")
async def read_users_me(current_user: User = Depends(get_current_user)):
return current_user
This is a simplified example. In a real application, you would verify the token against a database or auth service.
Advanced Features
FastAPI offers many advanced features that we haven't covered, such as:
- Background tasks: Execute tasks after returning a response.
- WebSocket support: Build real-time applications.
- GraphQL integration: Use Strawberry or Ariel to add GraphQL endpoints.
- Custom middleware: Implement advanced request/response processing.
- API versioning: Manage different versions of your API gracefully.
Here's an example of a background task:
from fastapi import FastAPI, BackgroundTasks
app = FastAPI()
def write_log(message: str):
with open("log.txt", mode="a") as log:
log.write(message)
@app.post("/send-notification/{email}")
def send_notification(email: str, background_tasks: BackgroundTasks):
background_tasks.add_task(write_log, f"notification sent to {email}")
return {"message": "Notification sent in the background"}
This endpoint immediately returns a response while the log writing happens in the background.
Performance Tips
To ensure your FastAPI application runs as efficiently as possible, consider these tips:
- Use async database drivers (e.g., asyncpg for PostgreSQL) to avoid blocking I/O.
- Limit the number of middleware functions, as each adds overhead.
- Enable compression for responses to reduce bandwidth usage.
- Use a CDN for static files to offload serving from your application.
- Monitor performance with tools like Prometheus and Grafana.
FastAPI is a powerful, modern framework that can help you build robust and high-performance APIs with minimal effort. Its combination of speed, ease of use, and automatic documentation makes it an excellent choice for projects of any size. Happy coding!