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:
- Read environment variables
- Validate configuration values
- Set default values
- Generate documentation for your settings
Simple Settings Example
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 fromBaseSettings
- 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:
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:
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
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:
# 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:
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:
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
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
-
Never hardcode sensitive information: Always use environment variables or secure vaults for secrets.
-
Use type validation: Leverage Pydantic's type validation to catch configuration errors early.
-
Set sensible defaults: Provide reasonable default values where appropriate.
-
Use hierarchy for complex configurations: Organize related settings together for better readability.
-
Cache configuration objects: Use
@lru_cache
to avoid reloading configuration files for each request. -
Use separate configurations for tests: Create a specific configuration for your test environment.
-
Document your configuration options: Comment your configuration classes to explain what each setting does.
-
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:
-
Never commit secrets to version control.
-
Use environment-specific .env files and make sure they're in
.gitignore
. -
Consider using a secrets management tool like HashiCorp Vault or AWS Secrets Manager for production secrets.
-
Validate and sanitize configuration values, especially those that might be used in database queries or templates.
Example of a secure secrets approach:
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
-
Create a FastAPI application that uses
BaseSettings
to configure database connection parameters. -
Extend the configuration to support different environments (development, testing, production).
-
Implement a configuration system that loads different settings based on an environment variable.
-
Create a route that returns non-sensitive configuration information for debugging.
-
Add validation to your configuration class to ensure that certain settings meet specific requirements.
Additional Resources
- Pydantic Settings Documentation
- FastAPI Documentation on Dependencies
- The Twelve-Factor App - Config
- Python dotenv documentation
- Managing Configuration in Python Applications
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! :)