Pydantic Series: Settings in a FastAPI App

Building a robust and configurable API requires a clean and maintainable settings management strategy. FastAPI, combined with Pydantic, provides a powerful way to manage application configurations, ensuring that settings are well-structured, validated, and easily accessible. In this post, we’ll explore how to integrate Pydantic settings into a FastAPI application using dependency injection.


Why Use Pydantic Settings in FastAPI?

When developing an API, configurations such as database connections, external services, security keys, and feature toggles need to be accessible throughout the application. Pydantic’s BaseSettings offers:

  • Automatic environment variable parsing
  • Type validation to prevent incorrect configurations
  • Hierarchical settings models for complex applications
  • Seamless integration with FastAPI’s dependency injection system

Step 1: Defining Pydantic Settings for FastAPI

We’ll define our application settings using Pydantic’s BaseSettings class.

from pydantic import BaseSettings, Field, PostgresDsn, RedisDsn
from typing import Optional

class AppSettings(BaseSettings):
    app_name: str = Field("FastAPI App", env="APP_NAME")
    environment: str = Field("development", env="ENVIRONMENT")
    debug: bool = Field(True, env="DEBUG")
    secret_key: str = Field(..., env="SECRET_KEY")
    database_url: PostgresDsn = Field(..., env="DATABASE_URL")
    redis_url: Optional[RedisDsn] = Field(None, env="REDIS_URL")

    class Config:
        env_file = ".env"

This class ensures that:

  • Configuration values are fetched from environment variables.
  • Type safety is enforced (e.g., database_url must be a valid PostgreSQL DSN).
  • Default values can be set (debug=True).
  • The .env file is used for local development.

Step 2: Injecting Settings into FastAPI Using Dependencies

To make settings available throughout the application, we use FastAPI’s dependency injection system.

from fastapi import Depends, FastAPI

# Load settings once and reuse them
settings = AppSettings()

def get_settings() -> AppSettings:
    return settings

app = FastAPI()

@app.get("/settings")
def read_settings(config: AppSettings = Depends(get_settings)):
    return {
        "app_name": config.app_name,
        "environment": config.environment,
        "debug": config.debug
    }

Why Use Dependency Injection?

  • Ensures single-instance reuse instead of creating a new settings object per request.
  • Allows for easier testing by overriding dependencies in test cases.
  • Provides cleaner separation of concerns, ensuring settings are accessed consistently.

Step 3: Using Pydantic Settings in Database Connections

FastAPI applications often require database connections, and Pydantic settings help in configuring them properly.

from sqlalchemy import create_engine

# Create database engine using Pydantic settings
engine = create_engine(settings.database_url)

This ensures that:

  • The database URL is validated at startup.
  • Configuration is consistent and easy to manage.

Step 4: Environment-Specific Settings

Different environments (development, staging, production) require different configurations. We can manage them easily with .env files.

.env (Development)

APP_NAME=FastAPI Dev
ENVIRONMENT=development
DEBUG=True
DATABASE_URL=postgresql://dev_user:dev_pass@localhost/dev_db
REDIS_URL=redis://localhost:6379/0
SECRET_KEY=dev-secret-key

.env (Production)

APP_NAME=FastAPI App
ENVIRONMENT=production
DEBUG=False
DATABASE_URL=postgresql://prod_user:prod_pass@db.prod/prod_db
REDIS_URL=redis://cache.prod:6379/0
SECRET_KEY=prod-secret-key

By switching .env files, we can adapt the API configuration dynamically.


Step 5: Overriding Settings for Testing

Testing requires controlled configurations. FastAPI allows dependency overrides for testing purposes.

from fastapi.testclient import TestClient

def get_test_settings() -> AppSettings:
    return AppSettings(
        app_name="Test App",
        environment="testing",
        debug=True,
        secret_key="test-secret-key",
        database_url="postgresql://test_user:test_pass@localhost/test_db"
    )

app.dependency_overrides[get_settings] = get_test_settings
client = TestClient(app)

response = client.get("/settings")
print(response.json())

This ensures that:

  • Tests run with isolated settings.
  • The real application configuration remains unchanged.

Step 6: Security Best Practices

When handling sensitive settings, avoid hardcoding secrets in code:

  • Use environment variables instead of embedding credentials in source code.
  • Restrict access to .env files in production (chmod 600 .env).
  • Use cloud secrets managers like AWS Secrets Manager or HashiCorp Vault for storing secrets securely.

Conclusion

Integrating Pydantic settings into FastAPI ensures clean, structured, and secure application configurations. By leveraging dependency injection, environment-based settings, and test overrides, we achieve scalability and maintainability.

Key Takeaways:

Automatic environment variable parsing using BaseSettings
Dependency injection ensures reusable and manageable settings
Database and third-party service configurations stay organized
Environment-based overrides make transitioning between dev/staging/prod seamless
Test-specific settings help ensure reliability without affecting production

By structuring settings this way, FastAPI applications become highly configurable, testable, and secure.

Leave a comment