• Labs icon Lab
  • Core Tech
Labs

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.

Labs

Path Info

Level
Clock icon Beginner
Duration
Clock icon 41m
Published
Clock icon Apr 29, 2025

Contact sales

By filling out this form and clicking submit, you acknowledge our privacy policy.

Table of Contents

  1. 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 and async 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.

  2. Challenge

    Add an Asynchronous Endpoint

    Add a Non-blocking /movies Endpoint

    Your current /blocking-movies route uses time.sleep(), which blocks the server thread until the timer finishes. This simulates what would happen with the requests 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 execution
    • asyncio.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.
  3. Challenge

    Benchmark the Asynchronous Endpoint

    Benchmark the Async /movies Endpoint

    You've implemented a non-blocking version of the movie endpoint using async def and httpx.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 and async.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.

  4. 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.

  5. 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.

  6. 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() or Celery 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.

Kevin is a Developer Educator, speaker and writer based in beautiful Northern Ireland. He specializes in web development across a range of languages and frameworks.

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.