- Lab
- Core Tech

Guided: Asynchronous API Development with FastAPI
Unlock the full potential of FastAPI to build blazing-fast, scalable, and efficient APIs. This hands-on Code Lab walks you through the best practices of asynchronous API development, equipping you with the skills to handle high-concurrency workloads, integrate custom middleware, and optimize request handling. By the end of this Code Lab, you’ll have a production-ready API that’s built for performance, resilience, and scalability. Whether you're new to FastAPI or looking to refine your skills, this guided experience will elevate your API development expertise.

Path Info
Table of Contents
-
Challenge
Introduction and Benchmarking
Asynchronous API Development with FastAPI
You have been brought in to continue developing a movie recommendation API that works, but users experience delays under load.
As your user base grows, even small delays in external API calls can compound into sluggish performance, timeouts, and unhappy customers. To scale effectively, you need to handle concurrent requests efficiently, track performance in real time, and offload slow tasks that don’t need to block the user.
In this Code Lab, you'll take a working FastAPI project and do the following:
- Simulate performance problems with a slow, blocking route.
- Implement an async version using
asyncio
andasync def
. - Benchmark both and compare the results.
- Add middleware to monitor request performance.
- Use background tasks to offload non-critical work.
You'll discover that FastAPI already protects you from some of the downsides of blocking code, but that using proper async I/O still makes a measurable difference in both speed and scalability.
By the end of this lab, you'll have a high-performance API that:
- Responds faster under load
- Logs performance data for analysis
- Handles slow tasks without slowing users down
🧠 Real-world framing: This is how production APIs scale by handling I/O efficiently, logging intelligently, and prioritizing what work happens after the user gets their response. Your
/blocking-movies
route simulates a 3-second delay using a blocking HTTP call (requests.get
).
Before optimizing, run a baseline benchmark to understand how it performs under load.
Goal
- Simulate 100 concurrent users.
- Send a total of 200 requests.
- Capture performance metrics using Apache Bench. ### Confirm It Works
After it completes, you can check the output in the Terminal with the following commands:
head blocking.log tail blocking.log
Note: You can also open the
blocking.log
file in your editor.
🔍 What to Look For
| Metric | What It Means | |--------------------------|--------------------------------------| | Time taken for tests | Total duration of all requests | | Requests per second | Overall throughput | | Percentage served within | Slowest requests and queuing delays |
Outcome
You've now captured a benchmark of your blocking API under real-world load.
This gives you a clear baseline to compare against once you implement async I/O in the next step. -
Challenge
Add an Asynchronous Endpoint
Add a Non-blocking
/movies
EndpointYour current
/blocking-movies
route usestime.sleep()
, which blocks the server thread until the timer finishes. This simulates what would happen with therequests
library. While it works, it causes long response times and poor concurrency under load.In the next task, you'll create a non-blocking version of the same route using:
async def
for concurrency-aware executionasyncio.sleep()
to perform the same 3-second delay without blocking
Why This Matters
Using Python's
async
and an async HTTP client lets your server handle other requests while waiting for I/O operations to complete.This improves:
- Throughput
- Resource efficiency
- Scalability under real-world load You now have a non-blocking version of the movie endpoint. Although it still takes 3 seconds for one request, it will scale better under heavy load. In the next step, you'll test this by benchmarking it.
-
Challenge
Benchmark the Asynchronous Endpoint
Benchmark the Async
/movies
EndpointYou've implemented a non-blocking version of the movie endpoint using
async def
andhttpx.AsyncClient
. Now it's time to test how it performs under pressure.In the next task, you'll run the same benchmark tool you used in the first task, but this time against the
/movies
route. This will give you real data to compare the two approaches side-by-side.
Why This Matters
Benchmarking helps you:
- Validate that your async code actually improves performance.
- Understand how concurrency affects response time and throughput.
- Compare blocking vs non-blocking behavior using real data.
You're not just guessing, you're measuring. ### Confirm It Works After it completes, you can check the output in the Terminal with the following commands:
head async.log tail async.log
Note: You can also open the
async.log
file in your editor.
Reflection: Compare Blocking vs Async Performance
Now that you've benchmarked both routes, it's time to review the results and understand what they tell you.
Compare the following values from both the
blocking.log
andasync.log
files:- Time taken for tests
- Requests per second
- Max response time (100%)
Look for patterns in the results:
- Did the async route finish faster overall?
- Was the longest response time lower?
- Did it serve more requests per second?
What to Look For
In most environments, the async version will:
- Finish faster overall (lower "Time taken for tests")
- Serve more requests per second
- Handle spikes in concurrency with lower latency
However, the difference may be moderate rather than dramatic and that’s part of the lesson.
Why It Matters
FastAPI automatically moves blocking routes to a thread pool, which hides some of the cost. That's why both versions perform reasonably well.
Keep in mind, async I/O has long-term advantages:
- It's more efficient under extreme load.
- It uses fewer threads and less memory.
- It lets you build scalable APIs without thread exhaustion.
Final Thought
Async code isn't just faster, it's smarter. It frees your app to do other work while waiting on external services.
In the next step, you'll improve observability by adding middleware to log the performance of each request in real time.
-
Challenge
Adding Observability Middleware
Add Request Logging Middleware
Now that your API can handle concurrent requests efficiently, you will make it observable.
In the next task, you'll add custom middleware that logs the method, path, and duration of each request. This gives you insight into how long your endpoints are talking and which routes are being accessed without modifying your route code directly.
Why This Matters
Middleware is the right place for cross-cutting concerns like logging, monitoring, and request validation.
By measuring performance at the middleware level, you can track bottlenecks and make informed decisions about where to optimize next.Once the middleware is in place, every request will generate a log entry in a file you can inspect later, even under load.
Define the Middleware Class
Your first task is to define a custom middleware that tracks request duration and logs basic request info. You’ve created a basic middleware that wraps every request and measures its duration.
Log Request Data to a File
Now that your middleware measures how long a request takes, you'll add simple logging to record each request’s method, path, and duration to a file. Your middleware now logs each request to
request.log
, including how long it took to process.Register and Test the
LoggingMiddleware
You've built a middleware that logs request method, path, and duration. Now you'll plug it into your FastAPI app so it's active on every request. ### Observability through Middleware
You've now added custom middleware that logs every request made to your API, including the method, path, and how long it took to process.
By registering this middleware globally, you've made your app more transparent and easier to debug under real-world load. You've also created a foundation for adding more production-level observability later, such as metrics, trace IDs, or structured logs.
What You Achieved
- Measured request performance without changing your route logic
- Logged traffic to a file for offline analysis
- Set up a reusable structure for cross-cutting concerns like auth or validation
In the next step, you'll look at background tasks, a better place to run logic that doesn’t need to block the user, such as sending notifications, logging analytics, or syncing data.
-
Challenge
Background Tasks
Background Tasks: Post-response Logic without the Wait
So far, you've improved performance by using async I/O and added middleware to log every request, but not every task needs to block the response.
Some operations (like writing logs, sending emails, or posting metrics) can happen after the response is sent. That's where background tasks come in.
FastAPI provides a simple way to queue up functions to run in the background once the response is returned. It's a great fit for work that's important, but not urgent.
--- ### When to Use a Background Task
You've now seen how to delegate work to a background task in FastAPI — allowing your API to respond quickly without skipping important follow-up work.
This pattern is especially useful when:
- The task doesn't affect the response (e.g., logging, analytics, and email).
- You want to reduce perceived latency.
- You're doing something that could fail silently without hurting the user experience.
Middleware vs Background Tasks: When to Use Each
| Use Case | Middleware ✅ | Background Task ✅ | |---------------------------------------|---------------|---------------------| | Measure request duration | ✅ Yes | 🚫 Not after response | | Validate authentication or headers | ✅ Yes | 🚫 No access to request/response | | Send an email after a user signs up | 🚫 No | ✅ Yes | | Log to an external service | 🚫 No | ✅ Yes | | Modify the request/response object | ✅ Yes | 🚫 No |
By understanding where each tool fits, you'll be able to keep your code clean, your APIs responsive, and your side tasks safely out of the critical path.
-
Challenge
Conclusion
Final Wrap-up: Building High-performance APIs with FastAPI
In this lab, you've built a small, but powerful API and transformed it from a blocking, opaque service into a responsive and observable one.
What You Practiced
- Async I/O with
asyncio.sleep
to handle external requests without blocking your app - Benchmarking with
ab
to see how concurrency affects response times - Middleware for logging to gain visibility into every request
- Background tasks to defer non-essential work like logging until after the response is sent
Real-world Takeaways
- FastAPI makes it easy to write async endpoints, but to see the benefit, you need to simulate real concurrency.
- Middleware is ideal for cross-cutting concerns like request logging, authentication, or header validation.
- Background tasks are a clean way to handle non-blocking, non-critical side work (like analytics or emails).
- Performance isn't just about code, it's about understanding when and how to make your app do less in the hot path.
Where to Go Next
- Add caching with
fastapi-cache
or an in-memory store to improve repeated responses. - Integrate structured logging tools like Loguru or a remote log aggregator.
- Use
asyncio.create_task()
orCelery
if your background tasks get more complex.
You're now equipped with a strong foundation for building scalable, efficient, and production-ready APIs with FastAPI.
- Async I/O with
What's a lab?
Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.
Provided environment for hands-on practice
We will provide the credentials and environment necessary for you to practice right within your browser.
Guided walkthrough
Follow along with the author’s guided walkthrough and build something new in your provided environment!
Did you know?
On average, you retain 75% more of your learning if you get time for practice.