Skip to main content

FastAPI Configuration

When building web applications with FastAPI, proper configuration management is essential for creating maintainable, secure, and deployable applications. In this guide, we'll explore different approaches to handle configuration in FastAPI applications, from simple settings to more complex configuration systems.

Introduction to FastAPI Configuration

Configuration in FastAPI applications refers to how we manage various application settings such as:

  • Database connection strings
  • API keys and secrets
  • Environment-specific settings (development, testing, production)
  • Feature flags
  • Application behavior parameters

Good configuration management follows these principles:

  • Separation of code and configuration
  • Environment-specific settings
  • Security of sensitive data
  • Easy maintenance and updates

Let's dive into different approaches to handle configuration in FastAPI.

Basic Configuration with Pydantic Settings

FastAPI integrates well with Pydantic for configuration management through the BaseSettings class. This approach allows you to:

  1. Read environment variables
  2. Validate configuration values
  3. Set default values
  4. Generate documentation for your settings

Simple Settings Example

python
from pydantic import BaseSettings

class Settings(BaseSettings):
app_name: str = "FastAPI App"
admin_email: str
items_per_page: int = 10
database_url: str

class Config:
env_file = ".env"

# Create settings instance
settings = Settings()

In this example:

  • We define a Settings class that inherits from BaseSettings
  • We specify configuration variables with their types and default values
  • The env_file setting allows loading values from a .env file

To use these settings in your FastAPI app:

python
from fastapi import FastAPI
from .config import settings

app = FastAPI(title=settings.app_name)

@app.get("/info")
async def info():
return {
"app_name": settings.app_name,
"items_per_page": settings.items_per_page,
"admin_contact": settings.admin_email
}

Environment Variables

Environment variables are a common way to provide configuration values to your application. With the Pydantic BaseSettings approach, you can easily map environment variables to your settings.

For example, if you set these environment variables:

APP_NAME="My Cool API"
ADMIN_EMAIL="[email protected]"
DATABASE_URL="postgresql://user:password@localhost/db"

The Settings class will automatically use these values.

Using .env Files

For development, it's common to use .env files to store environment variables. Create a .env file in your project root:

APP_NAME=My FastAPI App
[email protected]
ITEMS_PER_PAGE=20
DATABASE_URL=postgresql://user:password@localhost/db

Make sure to install the python-dotenv package:

bash
pip install python-dotenv

Pydantic's BaseSettings will automatically load values from this file if you specify env_file = ".env" in the Config class.

Advanced Configuration with Multiple Environments

In real applications, you often need different configurations for different environments (development, testing, production).

Multiple Environment Setup

python
from pydantic import BaseSettings, PostgresDsn
from typing import Optional
import os

class Settings(BaseSettings):
# Application settings
app_name: str = "FastAPI App"
api_prefix: str = "/api/v1"
debug: bool = False

# Database settings
database_url: PostgresDsn

# Security settings
secret_key: str
allowed_hosts: list[str] = ["*"]

class Config:
env_file = f".env.{os.getenv('ENV', 'development')}"

# Initialize settings object
settings = Settings()

Now you can create different env files:

  • .env.development for development settings
  • .env.testing for testing settings
  • .env.production for production settings

To switch environments, set the ENV environment variable:

bash
# For development
ENV=development uvicorn main:app --reload

# For production
ENV=production uvicorn main:app

Dependency Injection with Settings

FastAPI's dependency injection system works well with settings. You can create a dependency to make settings available to your route handlers:

python
from fastapi import Depends, FastAPI
from functools import lru_cache
from .config import Settings

app = FastAPI()

# Cache the settings to avoid loading .env file for each request
@lru_cache()
def get_settings():
return Settings()

@app.get("/settings")
async def read_settings(settings: Settings = Depends(get_settings)):
return {
"app_name": settings.app_name,
"database_url": settings.database_url,
"debug_mode": settings.debug
}

Notice how we use @lru_cache() to cache the settings instance. This is important for performance as it prevents reloading the environment variables on each request.

Real-world Example: Complete Configuration System

Let's build a more complete configuration system for a FastAPI application:

python
from pydantic import BaseSettings, PostgresDsn, validator
from typing import Any, Dict, List, Optional
import os
import secrets

class Settings(BaseSettings):
# API SETTINGS
API_V1_STR: str = "/api/v1"
PROJECT_NAME: str = "FastAPI Application"

# SECURITY
SECRET_KEY: str = secrets.token_urlsafe(32)
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 8 # 8 days

# CORS SETTINGS
BACKEND_CORS_ORIGINS: List[str] = []

@validator("BACKEND_CORS_ORIGINS", pre=True)
def assemble_cors_origins(cls, v: Any) -> List[str]:
if isinstance(v, str) and not v.startswith("["):
return [i.strip() for i in v.split(",")]
return v

# DATABASE
POSTGRES_SERVER: str
POSTGRES_USER: str
POSTGRES_PASSWORD: str
POSTGRES_DB: str
SQLALCHEMY_DATABASE_URI: Optional[PostgresDsn] = None

@validator("SQLALCHEMY_DATABASE_URI", pre=True)
def assemble_db_connection(cls, v: Optional[str], values: Dict[str, Any]) -> Any:
if isinstance(v, str):
return v
return PostgresDsn.build(
scheme="postgresql",
user=values.get("POSTGRES_USER"),
password=values.get("POSTGRES_PASSWORD"),
host=values.get("POSTGRES_SERVER"),
path=f"/{values.get('POSTGRES_DB') or ''}",
)

# ENVIRONMENT
ENVIRONMENT: str = "development"

class Config:
case_sensitive = True
env_file = f".env.{os.getenv('ENVIRONMENT', 'development')}"

settings = Settings()

Using this Configuration in a FastAPI App

python
from fastapi import FastAPI, Depends
from fastapi.middleware.cors import CORSMiddleware
from .config import settings, get_settings

app = FastAPI(
title=settings.PROJECT_NAME,
openapi_url=f"{settings.API_V1_STR}/openapi.json"
)

# Set up CORS middleware
if settings.BACKEND_CORS_ORIGINS:
app.add_middleware(
CORSMiddleware,
allow_origins=[str(origin) for origin in settings.BACKEND_CORS_ORIGINS],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

@app.get("/")
async def root():
return {"message": f"Welcome to {settings.PROJECT_NAME}!"}

@app.get("/info")
async def info(config: Settings = Depends(get_settings)):
return {
"project_name": config.PROJECT_NAME,
"environment": config.ENVIRONMENT,
"api_version": config.API_V1_STR,
}

Best Practices for FastAPI Configuration

  1. Never hardcode sensitive information: Always use environment variables or secure vaults for secrets.

  2. Use type validation: Leverage Pydantic's type validation to catch configuration errors early.

  3. Set sensible defaults: Provide reasonable default values where appropriate.

  4. Use hierarchy for complex configurations: Organize related settings together for better readability.

  5. Cache configuration objects: Use @lru_cache to avoid reloading configuration files for each request.

  6. Use separate configurations for tests: Create a specific configuration for your test environment.

  7. Document your configuration options: Comment your configuration classes to explain what each setting does.

  8. Always keep .env files out of version control: Add them to .gitignore to avoid exposing sensitive data.

Configuration Security

When handling configuration, security is paramount. Here are some important security considerations:

  1. Never commit secrets to version control.

  2. Use environment-specific .env files and make sure they're in .gitignore.

  3. Consider using a secrets management tool like HashiCorp Vault or AWS Secrets Manager for production secrets.

  4. Validate and sanitize configuration values, especially those that might be used in database queries or templates.

Example of a secure secrets approach:

python
from pydantic import BaseSettings, SecretStr

class SecuritySettings(BaseSettings):
api_key: SecretStr
database_password: SecretStr

class Config:
env_file = ".env"

security = SecuritySettings()

# When you need to use the actual secret value:
password = security.database_password.get_secret_value()

The SecretStr type helps ensure that secrets don't get accidentally logged or exposed in error messages.

Summary

In this guide, we've covered:

  • Basic configuration using Pydantic's BaseSettings
  • Using environment variables and .env files
  • Creating environment-specific configurations
  • Dependency injection with settings
  • Building a complete configuration system
  • Best practices for secure configuration management

Proper configuration management is fundamental to building robust FastAPI applications. It allows your application to adapt to different environments, keeps sensitive information secure, and makes your application more maintainable.

Exercises

  1. Create a FastAPI application that uses BaseSettings to configure database connection parameters.

  2. Extend the configuration to support different environments (development, testing, production).

  3. Implement a configuration system that loads different settings based on an environment variable.

  4. Create a route that returns non-sensitive configuration information for debugging.

  5. Add validation to your configuration class to ensure that certain settings meet specific requirements.

Additional Resources

Happy coding with FastAPI!



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)