Building_APIs_with_FastAPI
IV. Building APIs with FastAPI
A. Creating routes and endpoints
1. Defining route paths
a. Specifying the URL structure for each route
In FastAPI, we can define route paths using decorators and the app.route()
method. The route path specifies the URL structure for each route, allowing us to map our API endpoints to specific URLs.
For example, let’s define a route path for a simple GET request:
from fastapi import FastAPI
app = FastAPI()
@app.get("/items")
async def read_items():
return {"message": "GET request to fetch all items"}
In the above code snippet, the @app.get("/items")
decorator is used to define the route path /items
for the read_items()
function. This means that whenever a GET request is made to /items
, the read_items()
function will be executed.
b. Utilizing path parameters and query parameters in route paths
Path parameters and query parameters can be used to make our route paths more dynamic and flexible. Path parameters are parts of the URL that are marked with curly braces {}
and can be accessed as function parameters. Query parameters, on the other hand, are passed as key-value pairs in the URL query string.
Let’s see an example that demonstrates the usage of path and query parameters:
@app.get("/items/{item_id}")
async def get_item(item_id: int, q: str = None):
return {"item_id": item_id, "q": q}
In the above code, the route path /items/{item_id}
defines a path parameter item_id
. When a GET request is made to /items/42
, the get_item()
function will be executed with item_id=42
. Additionally, we can also pass a query parameter q
in the URL query string, like /items/42?q=search
, which will be accessible as q="search"
within the function.
2. Handling different HTTP methods
a. Implementing GET, POST, PUT, DELETE, and other HTTP methods
FastAPI allows us to implement various HTTP methods for our API endpoints, such as GET, POST, PUT, DELETE, and more. We can use decorators like @app.get()
, @app.post()
, @app.put()
, and @app.delete()
to specify the HTTP method for each route.
Let’s see an example that demonstrates how to implement different HTTP methods:
@app.get("/items")
async def read_items():
return {"message": "GET request to fetch all items"}
@app.post("/items")
async def create_item(item: Item):
return {"message": "POST request to create a new item"}
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
return {"message": f"PUT request to update item with ID: {item_id}"}
@app.delete("/items/{item_id}")
async def delete_item(item_id: int):
return {"message": f"DELETE request to delete item with ID: {item_id}"}
In the above code, we have defined route paths for different HTTP methods. For example, the @app.post("/items")
decorator specifies that the create_item()
function will handle POST requests made to /items
. Similarly, the @app.put("/items/{item_id}")
decorator maps the update_item()
function to the route path /items/{item_id}
for PUT requests.
b. Configuring route handlers for each HTTP method
We can have separate functions to handle different HTTP methods for the same route path. This allows us to have more control and modularity in our code.
Let’s see an example that demonstrates this:
@app.route("/items/{item_id}", methods=["GET", "POST"])
async def handle_item(item_id: int):
if request.method == "GET":
return {"message": f"GET request for item with ID: {item_id}"}
elif request.method == "POST":
return {"message": f"POST request for item with ID: {item_id}"}
In the above code, the @app.route("/items/{item_id}", methods=["GET", "POST"])
decorator is used to define a route path that handles both GET and POST requests. Inside the handle_item()
function, we can check the request.method
to determine which HTTP method was used and perform the appropriate actions accordingly.
By creating routes and endpoints in FastAPI, we can define the URL structure for each route, utilize path and query parameters, handle different HTTP methods, and configure route handlers accordingly. This allows us to build powerful and flexible APIs.
IV. Building APIs with FastAPI
B. Query parameters and pagination
1. Handling query parameters
a. Extracting query parameters from the request URL
Query parameters are commonly used in APIs to filter and sort data based on specific criteria. In FastAPI, query parameters can be easily extracted from the request URL using the Query
function from the fastapi.params
module.
Here’s an example of how to extract a query parameter named filter
from the URL:
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def get_items(filter: str = Query(None)):
return {"filter": filter}
In this example, the filter
query parameter is of type str
and has a default value of None
. If the query parameter is not provided in the URL, the default value is used.
b. Applying filters and sorting based on query parameters
Once the query parameters are extracted, they can be used to filter and sort data in the API endpoint. For instance, if your API returns a list of items, you can apply filters and sorting based on the provided query parameters.
Here’s an example of how to filter and sort items based on query parameters:
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def get_items(filter: str = Query(None), sort: str = Query(None)):
# Apply filters and sorting based on query parameters
# Retrieve items from the database
# ...
return {"items": items}
In this example, the get_items
endpoint accepts two query parameters: filter
and sort
. These parameters can be used to filter and sort the items retrieved from the database before returning the response.
2. Implementing pagination in APIs
a. Dividing large result sets into smaller pages
Pagination is a common technique used in APIs to handle large result sets. It involves dividing the results into smaller pages, allowing users to navigate through the data more efficiently.
FastAPI provides built-in support for pagination using query parameters. By specifying the skip
and limit
query parameters, you can control the number of items to skip and the maximum number of items to retrieve per page.
Here’s an example of implementing pagination in an API:
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def get_items(skip: int = Query(0), limit: int = Query(10)):
# Retrieve items from the database, considering skip and limit
# ...
return {"items": items}
In this example, the skip
parameter determines the number of items to skip, and the limit
parameter specifies the maximum number of items to retrieve per page. By default, the skip
parameter is set to 0, and the limit
parameter is set to 10. These values can be adjusted based on your API requirements.
b. Adding pagination parameters to enable navigation through the pages
To enable navigation through the pages, additional pagination parameters can be added to the API endpoint. These parameters can include the total number of items, the current page number, and links to previous and next pages.
Here’s an example of adding pagination parameters to an API:
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def get_items(skip: int = Query(0), limit: int = Query(10)):
# Retrieve items from the database, considering skip and limit
# ...
total_items = 100
current_page = skip // limit
total_pages = (total_items // limit) + 1
previous_link = f"/items/?skip={max(skip - limit, 0)}&limit={limit}" if skip > 0 else None
next_link = f"/items/?skip={skip + limit}&limit={limit}" if skip + limit < total_items else None
return {
"items": items,
"total_items": total_items,
"current_page": current_page,
"total_pages": total_pages,
"previous_link": previous_link,
"next_link": next_link
}
In this example, the pagination parameters are calculated based on the skip
and limit
values. The total_items
variable represents the total number of items available. The current_page
variable is calculated by dividing the skip
value by the limit
value. The total_pages
variable is calculated by dividing the total_items
value by the limit
value and adding 1.
Additionally, the previous_link
and next_link
variables are calculated to provide links to the previous and next pages, respectively. These links are included in the API response, allowing users to navigate through the paginated results.
By using query parameters and pagination techniques in FastAPI, you can enhance the functionality of your APIs and provide a more flexible and efficient user experience.
C. Path parameters and path operations
1. Using path parameters in routes
a. Defining dynamic segments in route paths
FastAPI allows us to define dynamic segments in route paths using path parameters. Path parameters are used to capture values from the URL path and use them in our route handlers. We can define path parameters by enclosing them in curly braces {}
within the route path.
For example, consider a route that retrieves information about a specific user. We can define a path parameter for the user ID as follows:
from fastapi import FastAPI
app = FastAPI()
@app.get("/users/{user_id}")
async def get_user(user_id: int):
return {"user_id": user_id}
In the above example, the user_id
path parameter is defined within the route path /users/{user_id}
. This allows us to access the value of user_id
in the get_user
route handler.
b. Extracting values from path parameters for further processing
Once we have defined path parameters in our route paths, we can extract their values for further processing within our route handlers. FastAPI automatically converts the path parameter values to the specified type, if any type is provided.
Continuing from the previous example, let’s extract the value of the user_id
path parameter and use it to retrieve the corresponding user information:
from fastapi import FastAPI
app = FastAPI()
@app.get("/users/{user_id}")
async def get_user(user_id: int):
# Retrieve user information based on user_id
user = get_user_by_id(user_id)
return {"user": user}
In the above example, the user_id
path parameter is passed as an argument to the get_user
route handler. We can then use this value to retrieve the corresponding user information from a hypothetical get_user_by_id
function.
2. Implementing path operations (GET, POST, etc.)
a. Assigning specific HTTP methods to route handlers
FastAPI allows us to assign specific HTTP methods to our route handlers using decorators. The most common HTTP methods include GET, POST, PUT, DELETE, etc.
For example, let’s define a route that handles GET requests for retrieving a list of users:
from fastapi import FastAPI
app = FastAPI()
@app.get("/users")
async def get_users():
# Retrieve list of users
users = get_all_users()
return {"users": users}
In the above example, the @app.get
decorator is used to assign the GET method to the get_users
route handler.
b. Executing corresponding actions based on the path operation and HTTP method
Once we have assigned specific HTTP methods to our route handlers, FastAPI executes the corresponding action based on the path operation and HTTP method.
Continuing from the previous example, let’s define a route that handles POST requests for creating a new user:
from fastapi import FastAPI
app = FastAPI()
@app.post("/users")
async def create_user(user: User):
# Create a new user
created_user = create_new_user(user)
return {"user": created_user}
In the above example, the @app.post
decorator is used to assign the POST method to the create_user
route handler. When a POST request is made to the /users
endpoint, FastAPI executes the create_user
route handler and creates a new user based on the provided user data.
By combining path parameters and path operations, we can build powerful APIs with FastAPI that handle various types of requests and perform different actions based on the provided data.
D. Request body and payload
1. Handling request payload
a. Receiving and processing data sent in the request body
FastAPI allows you to handle data sent in the request body easily. You can define a request body parameter in your route function with the Request
type annotation. This will automatically parse and deserialize the request body for you.
Here’s an example of receiving and processing JSON data sent in the request body:
from fastapi import FastAPI, Request
app = FastAPI()
@app.post("/items/")
async def create_item(request: Request):
item = await request.json()
# Process the received item
return {"item": item}
b. Differentiating between JSON, form data, and other payload formats
By default, FastAPI assumes that the request body contains JSON data. However, you can also handle other payload formats such as form data or plain text. To handle different payload formats, you can use the content_type
parameter of the request.body()
method.
Here’s an example of handling form data sent in the request body:
from fastapi import FastAPI, Request
app = FastAPI()
@app.post("/items/")
async def create_item(request: Request):
form_data = await request.form()
# Process the received form data
return {"form_data": form_data}
2. Validating and parsing request body
a. Implementing data validation on the received request body
FastAPI provides powerful tools for validating the request body data. You can use Pydantic models to define the expected structure and types of the request body. FastAPI will automatically validate the received data against the defined model.
Here’s an example of implementing data validation on the request body:
from fastapi import FastAPI, Request
from pydantic import BaseModel
class Item(BaseModel):
name: str
price: float
app = FastAPI()
@app.post("/items/")
async def create_item(request: Request, item: Item):
# The received item is already validated and of type Item
# Process the received item
return {"item": item}
b. Parsing and converting the request body into Python objects for further processing
Once the request body is validated, you can directly use the parsed and converted request body as Python objects in your route function. FastAPI automatically converts the validated data into the defined model.
Here’s an example of parsing and converting the request body into Python objects:
from fastapi import FastAPI, Request
from pydantic import BaseModel
class Item(BaseModel):
name: str
price: float
app = FastAPI()
@app.post("/items/")
async def create_item(item: Item):
# The received item is already validated and of type Item
# Process the received item
return {"item": item}