DEV Community

Cover image for Asynchronous Programming with FastAPI: Building Efficient APIs
Dhrumit Kansara
Dhrumit Kansara

Posted on

Asynchronous Programming with FastAPI: Building Efficient APIs

Introduction

In modern web development, building scalable and efficient APIs is essential. While traditional synchronous APIs have served us well, they can become a bottleneck when dealing with high concurrency or long-running operations. Enter asynchronous programming, a powerful tool that allows you to perform non-blocking operations and handle many requests simultaneously.

FastAPI, a modern Python web framework, provides native support for asynchronous programming, making it a fantastic choice for building high-performance APIs. In this blog post, we will explore how asynchronous programming works in FastAPI and why it's crucial for building efficient APIs.

What Is Asynchronous Programming?

Asynchronous programming enables your code to perform tasks like I/O operations (e.g., database queries, file reads, or network calls) without blocking the main thread of execution. In a traditional synchronous application, every I/O operation would block the thread until it completes. However, in asynchronous programming, these operations are performed "in the background," allowing the program to continue processing other tasks without waiting.

Why Use Asynchronous Programming in FastAPI?

  1. Concurrency: Asynchronous programming allows handling multiple I/O-bound tasks concurrently without waiting for each one to finish before starting the next. This is particularly beneficial for applications that handle many API requests at once.

  2. Non-blocking Operations: With async code, your API can interact with databases, external services, or files without slowing down the entire process.

  3. Performance Boost: FastAPI supports asynchronous views and routes, meaning you can scale your application better without adding significant hardware or servers.

Setting Up FastAPI with Asynchronous Support

FastAPI makes it simple to build asynchronous APIs. The framework supports asynchronous route handlers using Python’s async/await syntax. Let’s look at how to set up a basic FastAPI app with asynchronous functionality.

Installation

To get started with FastAPI and asynchronous programming, you'll first need to install FastAPI and uvicorn (the ASGI server).

pip install fastapi uvicorn
Enter fullscreen mode Exit fullscreen mode

Creating an Asynchronous API Endpoint

Let’s create a simple FastAPI application with an asynchronous endpoint:

from fastapi import FastAPI
import asyncio

app = FastAPI()

# Simulate a long-running I/O-bound task
async def fake_db_query():
    await asyncio.sleep(2)  # Simulate a delay (e.g., database query)
    return {"message": "Data fetched from the database"}

@app.get("/data")
async def get_data():
    data = await fake_db_query()  # Wait for the fake DB query to finish
    return data
Enter fullscreen mode Exit fullscreen mode

In this example, the get_data route is asynchronous, and it uses await to call fake_db_query, which simulates a delay as if it were querying a database. By using async and await, we ensure that the server can handle other requests while waiting for the fake DB query to complete.

Running the FastAPI Application

Run the FastAPI application using uvicorn:

uvicorn main:app --reload
Enter fullscreen mode Exit fullscreen mode

When you navigate to http://127.0.0.1:8000/data in your browser, you'll see the simulated response after a short delay. FastAPI will efficiently handle other incoming requests during the wait time for the fake DB query.

Async and Await in FastAPI

FastAPI leverages Python’s async/await syntax to define asynchronous endpoints. The async def keyword indicates that the route handler is asynchronous, while await is used to wait for an async operation to finish before continuing.

Here’s a quick breakdown:

  • async def: Declares an asynchronous function.
  • await: Tells the event loop to pause and wait for the result of an async operation (e.g., a database query, an HTTP request).

Using Asynchronous Databases

One of the main use cases for asynchronous programming is interacting with databases. By using an asynchronous database connector, your application can handle many database queries simultaneously without blocking the server.

Let’s see an example using an asynchronous database library, databases:

pip install databases
Enter fullscreen mode Exit fullscreen mode
import databases
import sqlalchemy
from fastapi import FastAPI

DATABASE_URL = "sqlite+aiosqlite:///./test.db"

database = databases.Database(DATABASE_URL)
metadata = sqlalchemy.MetaData()

# Define a table
user_table = sqlalchemy.Table(
    "users",
    metadata,
    sqlalchemy.Column("id", sqlalchemy.Integer, primary_key=True),
    sqlalchemy.Column("name", sqlalchemy.String),
)

app = FastAPI()

@app.on_event("startup")
async def startup():
    await database.connect()

@app.on_event("shutdown")
async def shutdown():
    await database.disconnect()

@app.get("/users")
async def get_users():
    query = user_table.select()
    result = await database.fetch_all(query)
    return result
Enter fullscreen mode Exit fullscreen mode

Here, we use the databases library, which supports asynchronous database queries. The get_users route fetches data asynchronously from the database, allowing FastAPI to continue processing other requests while waiting for the result.

Handling Concurrency in FastAPI

FastAPI is built on top of Starlette, an ASGI framework that supports concurrency through Python’s asyncio event loop. This means that FastAPI can handle multiple requests concurrently, making it highly efficient for I/O-bound tasks.

For example, when multiple clients hit the /data endpoint in the earlier example, FastAPI can process them concurrently because of its non-blocking behavior. However, it's essential to remember that asynchronous programming benefits I/O-bound tasks. CPU-bound tasks (e.g., heavy computations) might not benefit as much from async/await.

Best Practices for Asynchronous Programming with FastAPI

  1. Avoid Blocking I/O: When writing asynchronous code, ensure that operations that may block (like long computations) are handled asynchronously or in separate threads.

  2. Use Async Database Drivers: Use async database connectors (like databases, asyncpg, or aiomysql) to take full advantage of asynchronous programming when interacting with your database.

  3. Limit Long-running Tasks: If you need to perform long-running tasks (e.g., heavy computations), consider offloading them to background tasks or using separate worker processes.

  4. Testing Asynchronous Routes: FastAPI provides great tools for testing asynchronous routes with pytest and httpx. Be sure to write tests to verify that your async routes behave as expected.

Final thoughts

Asynchronous programming in FastAPI is a powerful tool for building efficient and scalable APIs. By using async and await, FastAPI allows you to handle many requests concurrently and perform non-blocking I/O operations. This makes it ideal for building high-performance web applications that interact with databases, external services, or perform time-consuming tasks.

By following best practices and leveraging the async capabilities of FastAPI, you can significantly improve the performance and scalability of your API, ensuring a smooth and responsive user experience.

Top comments (0)