FastAPI is a high-performance Python framework for building APIs, offering speed and efficiency. Its seamless integration with OpenAPI accelerates development, reduces errors, and provides interactive documentation. In this topic, we will set up FastAPI and configure its core components, including ASGI for async requests, Uvicorn as the server, Starlette for routing and middleware, and Pydantic for data validation.
Introduction to FastAPI
FastAPI is a modern, high-performance Python framework for building APIs, designed for ease of use with standard type declarations. It simplifies development with automatic OpenAPI documentation for path operations, parameters, request bodies, and security.
FastAPI is built on an architecture that integrates several tools to create fast and reliable web APIs. This framework streamlines development by automating request validation, data conversion, and API documentation generation, reducing manual coding while ensuring high performance.
The key components of the architecture include:
ASGI is the interface between the server and the application, enabling asynchronous communication and scalability. Uvicorn is an ASGI server used to run FastAPI applications.
Starlette serves as the lightweight framework that handles routing, middleware, and request/response processing.
Pydantic is the library responsible for validating and serializing data using Python type hints.
Dependency Injection is a built-in mechanism in FastAPI that allows seamless injection of dependencies such as services, configurations, or database connections.
Automatic API documentation.
This diagram illustrates the FastAPI architecture and the interaction between its core components:
To better understand how a FastAPI application is structured, we will now begin creating one, showing how each component works together to form a complete web framework.
FastAPI Core Components
To get started, we will first install the essential libraries and dependencies required for building a FastAPI application. This will include both FastAPI itself and Uvicorn, which is the ASGI server used to run the application:
pip install "fastapi[all]" uvicornWith the libraries installed, let's create a simple "Hello, World!" application. To create a web application using FastAPI, follow these steps:
from fastapi import FastAPI
# Create an instance app which defines routes and handles HTTP requests:
app = FastAPI()
# Define a route that listens for GET requests at the root URL (/):
@app.get("/")
# Create an async function that handles GET requests to the root URL:
async def hello():
# Return a dictionary, which FastAPI automatically converts into JSON format:
return {"message": "Hello, World!"}Before running our FastAPI script, it's important to understand that FastAPI is built on the ASGI (Asynchronous Server Gateway Interface) specification, a standard for asynchronous web servers and applications in Python.
ASGI
ASGI evolved from WSGI (Web Server Gateway Interface), which was the standard for synchronous Python web applications. While WSGI handles one request at a time, ASGI supports real-time features such as HTTP/1.1, HTTP/2, and WebSockets, enabling simultaneous connections and processing high loads without blocking:
When using FastAPI, we don’t need to manually write the following code snippet, which demonstrates a basic ASGI application that handles HTTP requests and sends a plain text response:
## The ASGI asynchronous function handles requests using scope, receive, and send:
async def asgi_app(scope, receive, send):
# Check that the request type is HTTP
assert scope['type'] == 'http'
# Receive the incoming request
request = await receive()
# Send the start of the HTTP response with a 200 status and content-type header
await send({
'type': 'http.response.start',
# Status code for successful request
'status': 200,
'headers': [
# Content type is plain text
[b'content-type', b'text/plain'],
],
})
# Send the body of the HTTP response with a simple text message
await send({
'type': 'http.response.body',
# Response message in byte format
'body': b'Hello, ASGI!',
})FastAPI automatically manages the HTTP lifecycle, including response headers, status codes, and content types, so we can focus on defining routes and logic. While FastAPI handles most of this behind the scenes, understanding scope and ASGI helps clarify how it works. The scope is a dictionary containing metadata for each connection, such as method, path, query string, and headers, with each request or connection having a unique scope. Here's an example of scope used in an ASGI application:
scope = {
# Connection type: HTTP
"type": "http",
# HTTP version used: 1.1
"http_version": "1.1",
# HTTP method: GET
"method": "GET",
# Requested URL path: /hello
"path": "/hello",
# Query string: none
"query_string": b"",
# HTTP headers: Host domain, Client's user agent, Accept
"headers": [
(b"host", b"localhost"),
(b"user-agent", b"Mozilla/5.0"),
(b"accept", b"text/html"),
],
# Client's IP address and port
"client": ("127.0.0.1", 8080),
# Server's IP address and port
"server": ("127.0.0.1", 8000),
}To run our FastAPI application, we use Uvicorn, the default ASGI server recommended for its speed and async support.
Uvicorn
The basic function of Uvicorn is to act as an ASGI web server for running Python web applications. So, as we discussed earlier, all ASGI servers perform three main tasks:
Listens on the specified host and port (by default, 127.0.0.1:8000) for incoming HTTP or WebSocket requests.
Аccepts an incoming TCP connection on the specified host and port, parses the HTTP request using
h11orhttptools, converts it into the ASGI format (scope,receive,send), and forwards it to the application for processing.Translates the request into ASGI
scopeandeventmessages and passes them to client.
Uvicorn achieves this by using an event loop, with uvloop for even better performance.
We can run our FastAPI application using Uvicorn directly from the terminal:
uvicorn main:appThis command runs the application instance app from the file main.py on the default host (127.0.0.1) and port (8000). The application will be accessible at http://127.0.0.1:8000 and will return responses in JSON format:
{"message": "Hello, World!"}For more control, we can set a custom port, manage the server lifecycle programmatically, or change the log level using Uvicorn’s configuration classes:
import uvicorn
async def app(scope, receive, send):
...
if __name__ == "__main__":
config = uvicorn.Config("main:app", port=8000, log_level="info")
server = uvicorn.Server(config)
server.run()Now, we can test our FastAPI route with curl or any HTTP client. For instance:
# Example request to test the server's root endpoint
curl http://127.0.0.1:8000/This command sends a GET request to the root (/) endpoint of your application:
# This is an HTTP GET request to the root ("/") endpoint
GET / HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: curl/7.64.1
Accept: */*The server with our app will respond with the following JSON:
{"message": "Hello, World!"}To get this response, the lightweight ASGI framework Starlette matches the incoming request’s method and path (/) with the defined route in your application. Once the route is matched, Starlette invokes the hello() function, which handles the request and generates the response.
Starlette
Starlette is a framework that manages low-level tasks such as routing, middleware, and ASGI compatibility, making it a foundation for developing asynchronous web applications. FastAPI uses Starlette as its core to handle these low-level tasks.
When using FastAPI, we don't need to manually define routing and other tasks, but the underlying logic of handling the request can be seen in the following example using Starlette:
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route
# Route handler for "/"
async def hello(request):
return JSONResponse({'message': 'Hello, World!'})
# Define the routes
routes = [
Route("/", hello),
]
# Create the Starlette app
app = Starlette(debug=True, routes=routes)As we can see, FastAPI uses Starlette for handling routing, allowing FastAPI to focus on higher-level functionalities such as pydantic-based data validation and dependency Injection system.
Pydantic validation
FastAPI uses Pydantic for data validation and serialization, allowing users to define data models and automatically validate incoming data, which minimizes the need for manual checks.
Let’s build a FastAPI app that accepts a "Page" object and validates its data using a Pydantic model. To do this, we need to follow these steps:
#1. Import the required libraries
from fastapi import FastAPI
from pydantic import BaseModel
#2. Create an instance of app with FastAPI, which initializes Starlette
app = FastAPI()
#3. Define the Pydantic model for the Page object to validate its data
class Page(BaseModel):
title: str
content: str
views: int
#4. Define a POST route that accepts a Page object as the request body
@app.post("/pages/")
async def create_page(page: Page):
return {"page": page}Now, FastAPI app is ready to accept an object, validate the input data using Pydantic, and return the validated page data as a response. We can run the FastAPI application using Uvicorn, with the --reload option enabling automatic server restarts upon code changes:
uvicorn main:app --reloadHere's an example of how to test a POST request:
# This is an HTTP POST request to the '/pages/' endpoint
curl -X POST "http://127.0.0.1:8001/pages/" \\
-H "Content-Type: application/json" \\
-d '{"title": "First Page", "content": "This is the content of the page.", "views": 1}'If the request is valid, the response will include the validated data in JSON format:
{
"page": {
"title": "First Page",
"content": "This is the content of the page.",
"views": 1
}
}Additionally, to retrieve the created page data from a database, we can use dependency injection to integrate the database connection into our route functions.
Dependency Injection
Dependency injection is a design pattern that allows a function or class to receive its dependencies from an external source rather than creating them internally. In FastAPI, dependency injection automatically handles hierarchies, validation, and integrates seamlessly with resources such as authentication systems and databases.
Let's add a dependency to our FastAPI app with Pydantic to demonstrate how dependency injection works. We need to follow these steps:
#1. Import required modules
from fastapi import Depends
from typing import List
#2. Create a function that simulates a database connection
def get_db():
db = DatabaseConnection()
try:
yield db
finally:
db.close()
class DatabaseConnection:
def __init__(self):
self.pages = [
{"title": "Page 1", "content": "Content of Page 1", "views": 10},
{"title": "Page 2", "content": "Content of Page 2", "views": 20}
]
def fetch_all_pages(self):
return self.pages
def close(self):
pass
#3. Inject the database dependency into a route function using Depends:
@app.get("/pages/", response_model=List[Page])
async def read_pages(db: DatabaseConnection = Depends(get_db)):
return db.fetch_all_pages()For testing our API, we will run the FastAPI app with Uvicorn on a specific port by adding the --host and --port options to the command:
uvicorn main:app --reload --host 127.0.0.1 --port 8001This command will start the FastAPI app on localhost (127.0.0.1) with port 8001, accessible at http://127.0.0.1:8001.
Next, we need to send an HTTP request to the app:
# This is an HTTP GET request to the '/pages/' endpoint
curl -X GET "http://127.0.0.1:8001/pages/"As a result, we should receive a JSON response containing the list of pages:
[
{
"title": "Page 1",
"content": "Content of Page 1",
"views": 10
},
{
"title": "Page 2",
"content": "Content of Page 2",
"views": 20
}
]To write correct requests and understand the API's behavior, we can refer to the documentation.
Documentation
FastAPI provides auto-generated, interactive API documentation via /docs (Swagger UI) and /redoc (ReDoc). These tools are powered by OpenAPI and JSON Schema, allowing developers to explore and test endpoints easily:
/docs(Swagger UI) offers interactive testing of endpoints, displaying available routes, HTTP methods, parameters, request/response schemas, and validation. Here, user can view and test real requests directly from the browser. For our API app, we can access this interface by navigating to http://127.0.0.1:8001/docs:/redoc (ReDoc UI) displays the same API details in a clean, easy-to-read format, making it ideal for reading or sharing. However, it is not interactive, so you can't send requests directly from it. For our API app, we can access this interface by navigating to http://127.0.0.1:8001/redoc:
Conclusion
FastAPI is a Python framework designed to simplify API development by providing built-in tools for request validation and automatic OpenAPI documentation. It integrates with ASGI for asynchronous request handling, Uvicorn as the server, Starlette for routing, and Pydantic for data validation. With its integration of modern tools, FastAPI enables developers to efficiently build high-performance APIs with minimal code.