Home / Blog / Engineering
Engineering

Building Scalable APIs with FastAPI

Best practices for designing and implementing high-performance APIs

Yudi Nugraha
March 1, 2024
3 min read

Building Scalable APIs with FastAPI

FastAPI has become my go-to framework for building modern APIs in Python. Here's why and how I use it.

Why FastAPI?

FastAPI combines the best aspects of modern Python development:

  • Performance: Comparable to Node.js and Go
  • Type Safety: Built on Pydantic for automatic validation
  • Async Support: Native async/await for high concurrency
  • Auto Documentation: OpenAPI and JSON Schema out of the box
  • Getting Started

    Installation is straightforward:

    pip install fastapi uvicorn
    

    A simple API:

    from fastapi import FastAPI
    
    app = FastAPI()
    
    @app.get("/")
    async def root():
        return {"message": "Hello World"}
    
    @app.get("/items/{item_id}")
    async def read_item(item_id: int, q: str = None):
        return {"item_id": item_id, "q": q}
    

    Type Safety with Pydantic

    Define your data models:

    from pydantic import BaseModel
    
    class User(BaseModel):
        username: str
        email: str
        full_name: str = None
    
    @app.post("/users/")
    async def create_user(user: User):
        return user
    

    FastAPI automatically validates the request body and generates documentation.

    Database Integration

    Using SQLAlchemy with async support:

    from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
    from sqlalchemy.orm import sessionmaker
    
    engine = create_async_engine(DATABASE_URL)
    async_session = sessionmaker(
        engine, class_=AsyncSession, expire_on_commit=False
    )
    
    async def get_db():
        async with async_session() as session:
            yield session
    
    @app.get("/users/{user_id}")
    async def get_user(user_id: int, db: AsyncSession = Depends(get_db)):
        result = await db.execute(
            select(User).filter(User.id == user_id)
        )
        return result.scalar_one_or_none()
    

    Authentication

    Implementing JWT authentication:

    from fastapi.security import OAuth2PasswordBearer
    from jose import JWTError, jwt
    
    oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
    
    def create_access_token(data: dict):
        to_encode = data.copy()
        encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
        return encoded_jwt
    
    @app.post("/token")
    async def login(form_data: OAuth2PasswordRequestForm = Depends()):
        user = authenticate_user(form_data.username, form_data.password)
        access_token = create_access_token(data={"sub": user.username})
        return {"access_token": access_token, "token_type": "bearer"}
    

    Performance Tips

    1. Use Background Tasks

    For non-blocking operations:

    from fastapi import BackgroundTasks
    
    def send_email(email: str, message: str):
        # Send email logic
        pass
    
    @app.post("/send-notification/")
    async def send_notification(
        email: str,
        background_tasks: BackgroundTasks
    ):
        background_tasks.add_task(send_email, email, "Welcome!")
        return {"message": "Notification sent"}
    

    2. Caching

    Implement Redis caching:

    import redis
    from functools import wraps
    
    redis_client = redis.Redis(host='localhost', port=6379, db=0)
    
    def cache(expire=300):
        def decorator(func):
            @wraps(func)
            async def wrapper(*args, **kwargs):
                key = f"{func.__name__}:{args}:{kwargs}"
                cached = redis_client.get(key)
                if cached:
                    return json.loads(cached)
                
                result = await func(*args, **kwargs)
                redis_client.setex(key, expire, json.dumps(result))
                return result
            return wrapper
        return decorator
    

    3. Connection Pooling

    Use connection pools for database and external services.

    Testing

    FastAPI makes testing easy:

    from fastapi.testclient import TestClient
    
    client = TestClient(app)
    
    def test_read_main():
        response = client.get("/")
        assert response.status_code == 200
        assert response.json() == {"message": "Hello World"}
    

    Deployment

    Deploy with Docker:

    FROM python:3.11-slim
    
    WORKDIR /app
    COPY requirements.txt .
    RUN pip install --no-cache-dir -r requirements.txt
    
    COPY . .
    
    CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
    

    Conclusion

    FastAPI is an excellent choice for building modern APIs. Its combination of performance, developer experience, and automatic documentation makes it my preferred framework for Python backends.

    Start building with FastAPI today and experience the difference!

    Tags

    PythonFastAPIAPI DesignBackend
    Y

    Yudi Nugraha

    Software Engineer | Builder

    More Articles

    Explore more articles on similar topics

    View All Articles