FastAPI applications can dynamically configure themselves based on environment variables, allowing for flexible deployments without code changes.

Let’s see this in action. Imagine a FastAPI app that needs a database URL and an API key.

# main.py
from fastapi import FastAPI
from pydantic import BaseSettings

class Settings(BaseSettings):
    database_url: str
    api_key: str

    class Config:
        env_file = ".env" # Optional: load from .env file if present

settings = Settings()

app = FastAPI()

@app.get("/status")
async def status():
    return {
        "database_connected": settings.database_url is not None,
        "api_key_loaded": settings.api_key is not None
    }

To run this, we need to set the environment variables. We can do this directly in our shell:

export DATABASE_URL="postgresql://user:password@host:port/dbname"
export API_KEY="supersecretkey123"
uvicorn main:app --reload

Now, if we hit the /status endpoint:

curl http://127.0.0.1:8000/status

We’ll see:

{
  "database_connected": true,
  "api_key_loaded": true
}

This demonstrates how Pydantic’s BaseSettings automatically reads environment variables that match the field names (case-insensitive by default) and converts them to the appropriate Python types.

The core problem Pydantic’s BaseSettings solves is managing configuration drift across different environments (development, staging, production). Instead of hardcoding values or managing separate config files for each, you define a single Settings model and let the environment dictate the specifics. This promotes the "12-Factor App" principle of storing configuration in the environment.

Internally, BaseSettings works by inspecting the Pydantic model you define. For each field, it looks for an environment variable with a matching name. If found, it attempts to cast the environment variable’s string value to the field’s declared type. If a .env file is specified in the Config class and exists, it will also load variables from there, prioritizing environment variables over .env file entries. The settings_delimiter and case_sensitive attributes within the Config class offer further customization on how environment variable names are matched.

Here’s the mental model: Think of your Settings class as a blueprint for your application’s configuration. Pydantic, with BaseSettings, acts as the builder. It takes your blueprint and fetches the raw materials (environment variables) to construct the final configuration object. This object (settings in our example) is then injected into your application, providing a strongly typed and validated configuration.

The env_prefix within the Config class is a powerful, yet often overlooked, feature. If you have multiple Pydantic settings models or want to namespace your application’s environment variables to avoid collisions, you can set env_prefix = "MYAPP_". Then, BaseSettings will only look for variables starting with MYAPP_, like MYAPP_DATABASE_URL and MYAPP_API_KEY. This prevents your application’s settings from accidentally overriding or being overridden by other applications or system-level environment variables.

The next concept you’ll likely encounter is handling secrets securely, as directly embedding sensitive information like API keys or database passwords in environment variables, even if they are loaded into the application, is not always the best practice for production deployments.

Want structured learning?

Take the full Fastapi course →