A comprehensive FastAPI boilerplate with SQLAlchemy, PostgreSQL, Redis caching, rate limiting, and RBAC.
- FastAPI - High-performance web framework
- SQLAlchemy - Async ORM with PostgreSQL support
- Alembic - Database migrations
- Redis - Caching and rate limiting
- RBAC - Role-based access control
- Docker - Containerization
- Environment Management - Separate development and production environments
- UV - Fast Python package installer
- Python 3.13+
- UV (Python package installer and resolver)
- Docker and Docker Compose (for containerized deployment)
- PostgreSQL (for local development)
- Redis (for local development)
- Note for Windows users: Redis doesn't officially support Windows. Use WSL (Windows Subsystem for Linux) or Docker. See Running Redis on Windows Using WSL section below.
fastapi-boilerplate/
├── src/
│ ├── app/
│ │ ├── api/ # API routes
│ │ │ ├── v1/ # API version 1
│ │ │ │ ├── endpoints/ # API endpoints
│ │ │ │ └── router.py # API router
│ │ │ ├── deps.py # API dependencies
│ │ │ └── router.py # Main API router
│ │ ├── models/ # SQLAlchemy models
│ │ ├── schemas/ # Pydantic schemas
│ │ ├── services/ # Business logic
│ │ └── main.py # FastAPI application
│ ├── core/
│ │ ├── cache/ # Redis cache
│ │ ├── db/ # Database
│ │ ├── utils/ # Utilities
│ │ ├── config.py # Configuration
│ │ ├── exceptions.py # Exception handlers
│ │ ├── logging.py # Logging configuration
│ │ ├── middleware.py # Middleware
│ │ └── security.py # Security utilities
│ └── settings/
│ └── run.py # Run script
├── envs/ # Environment variables
├── migrations/ # Alembic migrations
├── tests/ # Tests
├── .env # Environment variables (copied from envs)
├── .gitignore # Git ignore file
├── alembic.ini # Alembic configuration
├── docker-compose.yml # Docker Compose configuration
├── Dockerfile # Docker configuration
├── pyproject.toml # Project configuration
└── README.md # Project documentation
- Clone the repository:
git clone <your-repository-url>
cd fastapi-boilerplate- Install dependencies using UV (this will install the latest versions of all packages):
# On Windows
install_latest.bat
# On Unix/Linux/Mac
python -m venv .venv
source .venv/bin/activate
uv pip install -e .- Set up PostgreSQL and Redis locally or using Docker:
docker-compose up -d postgres redis- Initialize the database:
uv run init-db- Run the development server:
uv run dev- Build and run the Docker containers:
docker-compose up -d- Run the development server:
uv run dev- Run the production server:
uv run prod- Format code using Ruff:
uv run format- Lint code using Ruff:
uv run lint- Initialize the database:
uv run init-dbThis project uses UV along with Poetry for package management. UV is a fast Python package installer and resolver.
- Install all packages defined in pyproject.toml:
uv pip install -e .- Add a new package:
uv pip install package_nameThen add it to the dependencies list in pyproject.toml.
Redis doesn't officially support Windows. The recommended approach is to use Windows Subsystem for Linux (WSL).
- Open PowerShell as Administrator and run:
wsl --install-
Restart your computer when prompted.
-
After restart, a Ubuntu terminal will open. Set up your Linux username and password.
- Open your WSL terminal (Ubuntu) and update the package lists:
sudo apt update- Install Redis:
sudo apt install redis-server- Start the Redis service:
sudo service redis-server start- Verify Redis is running:
redis-cli pingIf Redis is running correctly, it should respond with "PONG".
- Edit the Redis configuration file:
sudo nano /etc/redis/redis.conf-
Find the line with
bind 127.0.0.1 ::1and change it tobind 0.0.0.0to allow connections from Windows. -
Find the line that contains
supervised noand change it tosupervised systemd. -
Save and exit (Ctrl+O, Enter, Ctrl+X).
-
Restart Redis:
sudo systemctl restart redis-serverUpdate your .env.development file to point to the WSL Redis instance:
REDIS_URL=redis://localhost:6379/0
Start Redis:
sudo service redis-server startStop Redis:
sudo service redis-server stop- Swagger UI: http://localhost:8000/docs
- ReDoc: http://localhost:8000/redoc
The application uses environment variables for configuration. These are stored in the envs/ directory and copied to .env when running the application.
# Application settings
APP_ENV=development
DEBUG=true
HOST=127.0.0.1
PORT=8000
RELOAD=true
LOG_LEVEL=DEBUG
SECRET_KEY=dev_secret_key_change_in_production
# Database settings
DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/fastapi_dev
# Redis settings
REDIS_URL=redis://localhost:6379/0
# Rate limiting
RATE_LIMIT_ENABLED=true
RATE_LIMIT_DEFAULT=100/minute
# CORS settings
CORS_ORIGINS=http://localhost:3000,http://localhost:8000# Application settings
APP_ENV=production
DEBUG=false
HOST=0.0.0.0
PORT=8000
RELOAD=false
LOG_LEVEL=INFO
SECRET_KEY=your_production_secret_key_here
# Database settings
DATABASE_URL=postgresql+asyncpg://postgres:postgres@postgres:5432/fastapi_prod
# Redis settings
REDIS_URL=redis://redis:6379/0
# Rate limiting
RATE_LIMIT_ENABLED=true
RATE_LIMIT_DEFAULT=60/minute
# CORS settings
CORS_ORIGINS=https://yourdomain.comThe application uses JWT tokens for authentication. The following endpoints are available:
POST /api/v1/auth/login- Login with username and passwordPOST /api/v1/auth/refresh- Refresh access token
This project implements a comprehensive Role-Based Access Control (RBAC) system that provides flexible and granular access control.
-
Users
- Can have multiple roles
- Can be superusers (bypass all permission checks)
- Authenticated using JWT tokens
-
Roles
- Named collections of permissions (e.g., "admin", "editor", "viewer")
- Can have multiple permissions
- Users can have multiple roles
-
Permissions
- Defined by resource and action pairs (e.g., "posts:create", "users:read")
- Can be assigned to roles
- Superusers automatically have all permissions
Permissions follow the format: resource:action
Common actions include:
create: Create new resourcesread: View resourcesupdate: Modify existing resourcesdelete: Remove resources*: All actions on a resource
Examples:
# Full access to posts
Permission(resource="posts", action="*")
# Read-only access to users
Permission(resource="users", action="read")
# Create and update products
Permission(resource="products", action="create")
Permission(resource="products", action="update")- Protecting API Endpoints
from src.app.api.deps import has_permission
@router.post("/posts/")
async def create_post(
post: PostCreate,
current_user: User = Depends(has_permission("posts", "create"))
):
# Only users with 'posts:create' permission can access this endpoint
pass
@router.get("/users/")
async def list_users(
current_user: User = Depends(has_permission("users", "read"))
):
# Only users with 'users:read' permission can access this endpoint
pass- Creating Roles and Permissions
# Create an editor role
editor_role = Role(
name="editor",
description="Can manage blog posts"
)
# Add permissions to the role
editor_permissions = [
Permission(resource="posts", action="create"),
Permission(resource="posts", action="update"),
Permission(resource="posts", action="read"),
]
editor_role.permissions.extend(editor_permissions)- Assigning Roles to Users
# Assign editor role to user
user.roles.append(editor_role)
await db.commit()- Superuser
- Has all permissions
- Created using
uv run createsuperuser - Can manage other users and roles
- Creating a Superuser
# Using CLI
uv run createsuperuser
# Follow the prompts to enter email, username, and password- API Endpoints for RBAC Management
POST /api/v1/roles/- Create new roleGET /api/v1/roles/- List all rolesPUT /api/v1/roles/{role_id}- Update roleDELETE /api/v1/roles/{role_id}- Delete rolePOST /api/v1/permissions/- Create new permissionGET /api/v1/permissions/- List all permissionsPUT /api/v1/permissions/{permission_id}- Update permissionDELETE /api/v1/permissions/{permission_id}- Delete permission
- Checking Permissions in Code
# Check if user has specific permission
for role in user.roles:
for permission in role.permissions:
if permission.resource == "posts" and permission.action == "create":
# User has permission to create posts
pass
# Using the dependency
from src.app.api.deps import has_permission
@router.post("/posts/")
async def create_post(
current_user: User = Depends(has_permission("posts", "create"))
):
# Only accessible to users with posts:create permission
pass-
Permission Naming
- Use lowercase for resources and actions
- Use descriptive names (e.g., "user_profiles" instead of "profiles")
- Be consistent with action names (create, read, update, delete)
-
Role Design
- Follow the principle of least privilege
- Create roles based on job functions
- Avoid giving more permissions than necessary
- Document role purposes and permissions
-
Security Considerations
- Regularly audit role assignments
- Remove unused roles and permissions
- Keep superuser access limited
- Log permission checks and role changes
Rate limiting can be configured in the .env file:
- Enable/Disable Rate Limiting: Set
RATE_LIMIT_ENABLEDtotrueorfalse. - Default Rate Limit: Set
RATE_LIMIT_DEFAULTto a value like100/minute(100 requests per minute).
Example configuration in .env.development:
# Rate limiting
RATE_LIMIT_ENABLED=true
RATE_LIMIT_DEFAULT=100/minuteThe rate limiter is implemented as a dependency that can be added to any FastAPI route. For example:
from fastapi import APIRouter, Depends
from src.core.utils.rate_limit import create_rate_limiter
router = APIRouter()
@router.get("/example")
async def example_endpoint(
_: None = Depends(create_rate_limiter(5, 60)), # 5 requests per minute
):
return {"message": "This endpoint is rate-limited."}When rate limiting is enabled, the following headers are added to responses:
X-RateLimit-Limit: The maximum number of requests allowed.X-RateLimit-Remaining: The number of requests remaining in the current time window.X-RateLimit-Reset: The time (in seconds) until the rate limit resets.
If a client exceeds the rate limit, they will receive a 429 Too Many Requests response:
{
"detail": "Too many requests"
}This project includes a caching mechanism to improve performance by reducing redundant computations or database queries. It uses Redis as the backend for caching.
- The
fastapi-cachelibrary is used to implement caching. - A decorator
cachedis provided to cache the results of API endpoints or functions. - Cache keys can be customized using a
key_builderfunction. - User-specific cache keys can be generated using the
user_specific_cache_keyutility.
Caching can be configured in the .env file:
- Redis URL: Set
REDIS_URLto the Redis connection string.
Example configuration in .env.development:
# Redis configuration
REDIS_URL=redis://localhost:6379/0-
Basic Caching: Use the
cacheddecorator to cache the results of an API endpoint or function. For example:from fastapi import APIRouter from src.core.cache.utils import cached router = APIRouter() @router.get("/example") @cached(expire=60) # Cache for 60 seconds async def example_endpoint(): return {"message": "This response is cached."}
-
Custom Cache Keys: Use the
user_specific_cache_keyutility to create user-specific cache keys. For example:from fastapi import APIRouter from src.core.cache.utils import cached, user_specific_cache_key router = APIRouter() @router.get("/user-data") @cached(expire=120, key_builder=user_specific_cache_key) # Cache for 120 seconds async def user_data_endpoint(): return {"message": "This response is cached per user."}
-
Custom Key Builder: You can define your own key builder function to customize cache keys. For example:
def custom_key_builder(func, namespace: str = "", **kwargs): return f"{namespace}:{func.__name__}:custom_key" @router.get("/custom-cache") @cached(expire=30, key_builder=custom_key_builder) # Cache for 30 seconds async def custom_cache_endpoint(): return {"message": "This response uses a custom cache key."}
To invalidate a cache entry, you can use the fastapi-cache API to delete the corresponding key from Redis.
When caching is enabled, subsequent requests to the same endpoint will return the cached response until the cache expires.
{
"message": "This response is cached."
}- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Format your code (
uv run format) - Lint your code (
uv run lint) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request