A universal application template that follows the principles of a clean architecture and contains everything you need to get started quickly.
We're all familiar with the problem of the "Blank Slate" where we have an idea but don't even know where to start, a typical application includes a lot of fundamental things that are implemented in almost every project and you still have to write them over and over again every time, from project to project. In order for you to focus only on business logic and not think about secondary things, this template was created. Template implements a simple backend application, but you can actually use it for a CLI, telegram bot, or GUI, thanks to the extensible architecture of the project and the focus on fundamental concepts.
- Clean Architecture. The template follows the principles of Clean Architecture, that is designed to simplify the process of developing, refactoring, and testing your application.
- Testing. The template provides a ready-made framework for integration testing of your application, including things like cleaning up the database to isolate tests, as well as an example of writing tests for a backend application.
- Working with the database. The template uses SQLAlchemy in an imperative style to work with the database, and there is also a database migration system using alembic.
- User management and authentication. The template provides two useful endpoints for creating a user of your application and reading it, as well as authenticating users.
- Authentication. The template shows an example of using OAuth2-Proxy along with a self-hosted Keycloak for user authentication, as well as an example of integrating this into your application, and shows the nginx configuration so that each outgoing request is authenticated first.
- Logging. The template uses structlog for logs in JSON format, including tracing feature, showing how to configure the Vector, Loki and Grafana stack to process, store and visualize your application's logs.
- Infrastructure. The template shows how to write a Docker image for your application, how to configure Docker-Compose to run the service in development mode or in test mode with all the necessary infrastructure including Redis, PostgreSQL, OAuth2-Proxy, Keycloak, and Nginx.
- Code quality. The quality of the code in your project will be ensured by the tools built into this template, such as mypy (with strict mode, of course) for static type analysis and ruff as a linter with many rules and import-linter that enforces dependency-rule.
- CI/CD. The template will provide you with Github-Actions already configured for linting the code, testing it, and deploying it. Docker image building inside the pipeline, their semantic versioning, and sending to Github Container Registry have been implemented.
- Backend. The backend part is implemented using FastAPI, showing application error handling, loading a TOML config, and using Dishka for dependency injection.
- Docker and Docker Compose installed
- Just command runner
-
Start all services:
just up
To run in detached mode:
just up-silent
-
Access the application:
- API:
http://localhost/ - API Documentation:
http://localhost/docs - Grafana (logs visualization):
http://localhost:3000
- API:
-
Stop all services:
just down
The project uses Just for command management. Here are the most useful commands:
just up- Start all services in development modejust up-silent- Start all services in detached modejust up-db- Start only the databasejust down- Stop all servicesjust clear- Stop all services and remove volumes (cleans database)just test- Run integration testsjust lint- Run code linters (ruff, mypy, import-linter)just dev-environment- Install development dependencies locallyjust generate-migration <name>- Generate a new database migrationjust cookie-secret- Generate OAuth2-Proxy cookie secret
Once the application is running, you can access:
GET /internal/alive- Liveness probeGET /internal/ready- Readiness probePOST /users/- Create a new user (requires authentication)GET /users/{user_id}- Get user by ID (requires authentication)GET /docs- Interactive API documentation (Swagger UI)
Run integration tests:
just testThis will:
- Start test environment (API, database)
- Run all integration tests
- Clean up and stop services
Generate a new migration:
just generate-migration "Add new table"Migrations are applied automatically in docker-compose on startup.
crudik/
├── src/crudik/ # Main package (following src-layout)
│ ├── entities/ # Domain entities
│ │ ├── base.py # Base entity class
│ │ ├── user.py # User entity
│ │ ├── common/ # Common domain utilities
│ │ │ └── identifiers.py # Type-aliases to identifiers (UserId, etc.)
│ │ └── errors/ # Domain-specific errors
│ │ └── base.py # Base application error
│ │
│ ├── application/ # Application layer (use cases)
│ │ ├── common/ # Common application interfaces (ports)
│ │ │ ├── interactor.py # Base interactor decorator
│ │ │ ├── uow.py # Unit of Work pattern interface
│ │ │ ├── auth_provider.py # Authentication provider interface
│ │ │ ├── idp.py # Identity provider interface
│ │ │ ├── logger.py # Logger interface
│ │ │ └── gateway/ # Database gateway interfaces
│ │ │ └── user.py # User gateway interface
│ │ ├── create_user.py # Create user interactor
│ │ ├── read_user.py # Read user interactor
│ │ └── errors/ # Application-specific errors
│ │ └── user.py # User-related errors
│ │
│ ├── adapters/ # Adapters (infrastructure) layer
│ │ ├── config.py # Adapter configuration
│ │ ├── tracing.py # Tracing utilities
│ │ ├── db/ # Database adapters
│ │ │ ├── config.py # Database configuration
│ │ │ ├── models/ # SQLAlchemy models
│ │ │ ├── gateway/ # Database gateway implementations
│ │ │ └── alembic/ # Database migrations
│ │ │ └── migrations/ # Migration files
│ │ ├── auth/ # Authentication adapters
│ │ │ ├── auth_provider.py # Auth provider implementation
│ │ │ ├── model.py # Auth models
│ │ │ ├── idp/ # Identity provider implementations
│ │ │ ├── common/ # Common auth interfaces
│ │ │ │ └── gateway/ # Auth gateway interfaces
│ │ │ └── errors/ # Auth-specific errors
│ │ └── errors/ # Adapter-specific errors
│ │ └── http/ # HTTP-specific errors
│ │
│ ├── presentation/ # Presentation sub-layer
│ │ └── fast_api/ # FastAPI-specific
│ │ ├── config.py # FastAPI configuration
│ │ ├── tracing.py # FastAPI tracing middleware
│ │ ├── error_handlers.py # Global error handlers
│ │ └── routers/ # API route handlers
│ │ ├── user.py # User endpoints
│ │ └── root.py # Root endpoints
│ │
│ └── main/ # Application initialization
│ ├── fast_api.py # FastAPI app factory
│ ├── cli.py # CLI entry point
│ ├── logs.py # Logging configuration
│ ├── config/ # Configuration loading
│ │ └── loader.py # Configuration loader
│ └── di/ # Dependency injection setup
│ ├── container.py # DI container
│ └── providers/ # Provider modules
│ ├── config.py # Config providers
│ ├── adapter.py # Adapter providers
│ ├── interactor.py # Interactor providers
│ └── tracing.py # Tracing providers
│
├── tests/ # Test suite
│ ├── api_client.py # API client for testing
│ └── integration/ # Integration tests
│ ├── conftest.py # Pytest fixtures
│ ├── test_probe.py # Health check tests
│ └── user/ # User-related tests
│ ├── utils.py # Test utilities
│ ├── test_create.py # Create user tests
│ └── test_read.py # Read user tests
│
├── .config/ # Configuration files
│ ├── app/ # Application configuration
│ │ ├── config.toml # Main configuration
│ │ └── config.local.toml # Local overrides
│ ├── nginx.conf # Nginx reverse proxy configuration
│ ├── oauth2-proxy.toml # OAuth2-Proxy configuration
│ ├── loki.yaml # Loki log aggregation configuration
│ ├── vector.yaml # Vector log processing configuration
│ └── grafana/ # Grafana provisioning
│ └── provisioning/
│ ├── dashboards/ # Dashboard definitions
│ │ ├── dashboards.yml
│ │ └── logs.json
│ └── datasources/ # Data source configurations
│ └── loki.yml
│
├── docker/ # Docker configuration
│ ├── Dockerfile # Main application image
│ ├── tests.Dockerfile # Tests image
│ ├── grafana.Dockerfile # Grafana image
│ ├── loki.Dockerfile # Loki image
│ ├── vector.Dockerfile # Vector image
│ ├── docker-compose.yml # Development environment
│ ├── docker-compose.tests.yml # Tests environment
│ └── docker-compose.base.yml # Base docker-compose template
│
├── keycloak/ # Keycloak configuration
│ ├── clients/ # Client configs
│ └── README.md
│
├── justfile # Just command runner recipes
├── pyproject.toml # Project dependencies and metadata
└── uv.lock # Locked dependencies (uv)
-
entities - Contains domain entities representing core business objects.
-
application - Implements interactors that orchestrate business logic. This layer defines interfaces (ports) that adapters must implement, following the Dependency Inversion Principle.
-
adapters - Provides concrete implementations mostly of interfaces defined in the application layer. Includes database access (SQLAlchemy) and authentication logic.
-
presentation - Sub-layer of adapters layer. Handles HTTP requests and responses using FastAPI.
-
main - Responsible for application initialization, dependency injection container setup, configuration loading, logging setup and CLI interface.
The application uses a multi-layer authentication architecture with Keycloak as the identity provider, OAuth2-Proxy as the authentication proxy, and Nginx as the reverse proxy that enforces authentication on all requests.
Client → Nginx → OAuth2-Proxy → Keycloak (OIDC)
↓
(if authenticated)
↓
Application API
-
Request Interception: When a client makes a request to the application, Nginx intercepts it using the
auth_requestdirective. -
OAuth2-Proxy Validation: Nginx forwards the authentication check to OAuth2-Proxy at
/oauth2/auth. OAuth2-Proxy validates the session cookie:- If valid session exists → returns 200 with user information headers
- If no valid session → returns 401, triggering redirect to Keycloak
-
Keycloak Authentication: If unauthenticated, OAuth2-Proxy redirects the user to Keycloak's OIDC login page. After successful login, Keycloak redirects back to OAuth2-Proxy with an authorization code.
-
Session Creation: OAuth2-Proxy exchanges the code for tokens, creates a session, stores it in Redis, and sets a secure HTTP-only cookie.
-
Header Propagation: Once authenticated, OAuth2-Proxy sets response header containing keycloak user id.
-
Nginx Header Forwarding: Nginx extracts these header and forwards them to the application
-
Application User Resolution: The application extracts the auth user ID from the configured header. The header name is configurable to match what Nginx forwards. It then looks up the corresponding
AuthUserrecord in the database and retrieves the associated applicationUserentity.
- Role: Identity Provider (IdP) using OpenID Connect (OIDC)
- Configuration: Client configuration stored in
keycloak/clients/ - Environment: Configured via
.config/.env.kc
- Role: Authentication proxy that handles OAuth2/OIDC flow
- Session Storage: Redis
- Configuration:
.config/.env.oauthproxy
- Role: Reverse proxy that enforces authentication
- Configuration:
.config/nginx.conf - Key Features:
auth_request /oauth2/auth: Validates every request through OAuth2-Proxyerror_page 401 =403 /oauth2/sign_in: Redirects unauthenticated users to login- Header forwarding: Passes user information to the backend application
When a new user is created via the application API:
-
User Entity Creation: A new
Userentity is created with a generated UUID. -
Auth User Linking: The
AuthProvider.setup_auth()method:- Extracts the current auth user ID from the request header
- Creates an
AuthUserrecord linking the Keycloak user ID to the application user ID - Stores this mapping in the database
-
Database Schema: The
AuthUsertable maintains the relationship:auth_user_id: Keycloak user identifier (string)user_id: Application user identifier (UUID)- Foreign key relationship to
Userentity
The project uses GitHub Actions for continuous integration and deployment. The workflow is defined in .github/workflows/lint_test_deploy.yml and runs on every push and pull request.
The pipeline consists of four main jobs that run in sequence:
- lint - Code quality checks
- test - Tests running in docker-compose.
- bump_version - Semantic versioning (only on
masterbranch) - build_and_push - Docker image building and publishing (only on
masteranddevbranches)
- Triggers: Runs on every push and pull request
- Purpose: Ensures code quality and consistency
- Steps:
- Runs Ruff for code linting
- Runs Mypy for static type checking
- Runs Import-linter for dependency rule validation
- Triggers: Runs on every push and pull request
- Purpose: Validates application functionality
- Steps:
- Sets up Docker Compose
- Runs integration tests using
docker-compose.tests.yml - Tests execute in isolated containers with database
- Triggers: Only runs on
masterbranch after successful lint and test - Purpose: Automatically increments version and creates Git tag
- Steps:
- Uses
anothrNick/github-tag-actionfor semantic versioning - Default bump:
patch - Creates version tag with
vprefix (e.g.,v0.0.1) - Saves version to artifact for use in build job
- Uses
- Triggers: Runs on
masteranddevbranches after successful lint and test - Purpose: Builds and publishes Docker images to GitHub Container Registry
- Versioning Strategy:
- master branch: Uses bumped version from previous job (e.g.,
v0.0.1) - dev branch: Uses bumped version with
-devsuffix (e.g.,v0.0.1-dev) - Each image is tagged with both version tag and
latest(orlatest-devfor dev branch)
- master branch: Uses bumped version from previous job (e.g.,
- Images Built:
- Main application image (
ghcr.io/<repo>/<version>) - Grafana image (
ghcr.io/<repo>-grafana/<version>) - Vector image (
ghcr.io/<repo>-vector/<version>) - Loki image (
ghcr.io/<repo>-loki/<version>)
- Main application image (
- Lint and Test: Always run on push and pull requests
- Version Bump: Only on
masterbranch merges - Build and Push: Only on
masteranddevbranches when lint and test succeed
All images are published to GitHub Container Registry (ghcr.io)
The AI in this project was only used to cover the code with docstrings, as well as to write some parts of README (under my strict control)
lubaskinc0de
MIT
Contributions are welcome
