DEV Community

Cover image for How I Built and Consumed an External API Using FastAPI: A Practical Walkthrough
Jhaemis-hack
Jhaemis-hack

Posted on

How I Built and Consumed an External API Using FastAPI: A Practical Walkthrough

The Inspiration

I’ve been experimenting with FastAPI, one of the most modern and performant Python frameworks for building web APIs.

This week, I decided to take it a bit further — not just build an API, but also consume another external API inside my app, add rate limiting, error handling, and a touch of developer love.

So in this post, I’ll walk you through exactly how I built a simple but structured FastAPI app that:

  • Exposes routes like /, /health, and /me
  • Consumes an external cat-facts API 🐈
  • Implements rate limiting using SlowAPI
  • Handles errors gracefully
  • Is ready for deployment with Procfile, requirements.txt, and pytest tests

Project Setup

Let’s start from scratch.

1. Create and activate a virtual environment

python -m venv .venv
source .venv/bin/activate  # or on Windows: .venv\Scripts\activate
Enter fullscreen mode Exit fullscreen mode

2. Install dependencies

Your requirements.txt should look like this:

fastapi
slowapi
pydantic_settings
httpx[h2]
uvicorn[standard]
pytest
Enter fullscreen mode Exit fullscreen mode

Then install:

pip install -r requirements.txt
Enter fullscreen mode Exit fullscreen mode

Building the App

Let’s start with our main.py — the heart of the app.

# main.py
from fastapi import FastAPI, Request, Response, Depends
from datetime import datetime, timezone
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from core.error_handlers import register_error_handlers
from services.http_client import safe_http_request
from core.exceptions import NotFoundException
from slowapi import Limiter
from slowapi.errors import RateLimitExceeded
from slowapi.util import get_remote_address
from functools import lru_cache
from typing_extensions import Annotated
from core import config
from contextlib import asynccontextmanager


@asynccontextmanager
async def lifespan(app: FastAPI):
    print("App starting up...")
    await startup_event()
    yield
    print("App shutting down...")


app = FastAPI(lifespan=lifespan, title="My Profile App")
register_error_handlers(app)


@lru_cache
def get_settings():
    return config.Settings()


async def rate_limit_exceeded_handler(request: Request, exc: RateLimitExceeded):
    return JSONResponse(
        status_code=429,
        content={"success": False, "error": "Too many requests, please slow down."},
    )


limiter = Limiter(key_func=get_remote_address)


async def startup_event():
    app.state.limiter = limiter
    app.add_exception_handler(RateLimitExceeded, rate_limit_exceeded_handler)


app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=False,
    allow_methods=["*"],
    allow_headers=["*"],
)


Profile_details = {
    "email": "gbemilekekenny@gmail.com",
    "name": "James Kehinde",
    "stack": "NodeJs",
}


@app.get("/")
async def home():
    return JSONResponse({
        "success": True,
        "message": "Welcome to my profile API"
    })


@app.get("/health")
async def health_check():
    return JSONResponse({
        "success": True,
        "message": "Ok"
    })


@limiter.limit("8/minute")
@app.get("/me")
async def get_profile(request: Request, settings: Annotated[config.Settings, Depends(get_settings)]):
    url = settings.cat_api_url
    fact = await safe_http_request("GET", url, params={"max_length": 40}, timeout=3000)

    if not fact:
        raise NotFoundException("Timeout, try again later.")

    data = {
        "status": "success",
        "user": Profile_details,
        "timestamp": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
        "fact": fact["fact"]
    }
    return JSONResponse(content=data)


@app.get("/favicon.ico", include_in_schema=False)
async def favicon():
    return Response(status_code=204)
Enter fullscreen mode Exit fullscreen mode

Key Takeaways from the Implementation

1. Rate Limiting

Using slowapi, I restricted requests to /me to 8 per minute per client.

This prevents abuse and adds robustness.

2. External API Consumption

I used an async HTTP client wrapper (httpx) to fetch cat facts from an external API.

The idea was to simulate consuming a third-party service within my API.

3. Error Handling

Custom error handlers are registered in core/error_handlers.py, ensuring consistent JSON responses instead of raw stack traces.

4. Lifespan Events

Instead of deprecated @app.on_event("startup"), I used FastAPI’s lifespan context manager — the modern, recommended approach.


Testing the App with Pytest

To make sure everything works smoothly, I added test_main.py:

from fastapi.testclient import TestClient
from main import app

client = TestClient(app)

def test_home_route():
    response = client.get("/")
    assert response.status_code == 200
    data = response.json()
    assert data["success"] is True
Enter fullscreen mode Exit fullscreen mode

Run tests with:

pytest -v
Enter fullscreen mode Exit fullscreen mode

Running the App Locally

To start your server locally:

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

Your app will be live at:
👉 http://127.0.0.1:8000

You can visit:

  • / → Welcome route
  • /health → Health check
  • /me → Profile + external cat fact

🚢 Deployment Ready

If deploying on Render, Railway, or Heroku, your Procfile should look like:

web: uvicorn main:app --host=0.0.0.0 --port=${PORT:-8000}
Enter fullscreen mode Exit fullscreen mode

Wrapping Up

In this small but practical project, I:

  • Built a clean and modern FastAPI service
  • Integrated an external API with async HTTP calls
  • Implemented rate limiting
  • Structured the app for production and deployment

This project gave me a clearer view of API consumption patterns inside FastAPI, and how easy it is to scale small ideas into something production-ready.


Author

James Kehinde — Full-Stack Developer | Node.js + FastAPI Enthusiast | Building meaningful software from Lagos 🇳🇬

💬 Let’s connect on LinkedIn or Twitter if you’re building something cool with FastAPI!

Top comments (0)