A scalable, production-ready REST API built with Rust and Axum, featuring a custom authentication system supporting Local (Email/Username & Password) and OAuth2 (Google & GitHub) providers. This project implements Hexagonal Architecture to ensure core business logic remains decoupled from infrastructure concerns like PostgreSQL, Redis, and external APIs. The project includes a comprehensive test suite of unit tests (mocked dependencies) and integration tests (real PostgreSQL & Redis) that run automatically in CI/CD.
The project is structured to ensure that business rules are independent of external frameworks, databases, and tools. Rather than using monolithic layers (e.g., domain/, application/, infrastructure/), the codebase is organized by crate and feature, with each layer's responsibility clearly delineated:
-
platform/src/features/: The domain and application layers. Pure business logic organized by domain feature (auth, user, session, security, oauth, email, verification). Each feature subdirectory contains:- Port Traits (
input.rs/output.rs): Define what the application needs from the outside world (e.g.,UserRepository,SessionPort) and what capabilities the system exposes (e.g.,AuthUseCase,UserUseCase). - Services (
service.rs): Implement business use cases by orchestrating the defined ports. - Concrete Adapters (e.g.,
postgres.rs,redis.rs,paseto.rs): Provide real implementations of the output port traits.
- Port Traits (
-
platform/src/api/: The presentation layer. HTTP entry point — Axum routes, handlers, request/response DTOs, error handling, OpenAPI documentation, and custom extractors. -
platform/src/core/: Infrastructure configuration. Application config management,PlatformStatewiring, PostgreSQL connection pooling, Redis connections, and telemetry (tracing/logging). -
business/: A secondary domain crate that follows the same hexagonal pattern — its ownapi/,core/, andfeatures/directories — providing business-specific logic decoupled from the platform authentication concerns. -
api/: The binary entry point. Wiresplatformandbusinesstogether, starts the HTTP server, and runs database migrations. -
tests/: Integration test suite (integration-testscrate). End-to-end tests exercising the full stack against real PostgreSQL and Redis instances, with wiremock for external API mocking (e.g., Resend email service).
The system uses a robust access+refresh token strategy with PASETO (Platform-Agnostic Security Tokens).
Access Token:
- Lifetime: 15 Minutes
- Storage: HttpOnly, Secure, SameSite=Strict Cookie.
Refresh Token:
- Lifetime: 7 Days
- Storage: HttpOnly, Secure, SameSite=Strict Cookie and indexed in Redis for session revocation.
graph TD
A[Client] -->|Login: Local or OAuth| B(Auth Service)
B -->|Validate Credentials| C{Valid?}
C -->|No| D[401 Unauthorized]
C -->|Yes| E[Generate PASETO Pair]
E --> F[Store Refresh Token in Redis]
F --> G[Set HttpOnly Cookies]
G --> A
A -->|Request with Expired Access| H[Refresh Endpoint]
H -->|Check Redis| I{Valid Session?}
I -->|Yes| J[Rotate Tokens]
J --> A
I -->|No| K[Force Re-login]
Follow these steps to set up and run the application suite locally on your machine for development.
Before running the application locally, you must initialize both the local environment variables and the development configuration settings in your project root:
-
Create the local environment file: Create a file named
.envin the root directory of your project (whereCargo.tomlis located) and populate it with the local service credentials and deployment metadata:DB_USER=${VALUE} DB_PASSWORD=${VALUE} DB_NAME=${VALUE} REDIS_PASSWORD=${VALUE} BUSINESS_NAME=hexum IMAGE_TAG=hexum
(Remember to replace
${VALUE}with your actual local database and cache configuration values.) -
Create your development settings file: Create a file at
config/development/base.toml. Initialize this file by copying and modifying the provided template found here. This file holds the configuration parameters (such as application logging levels or local server ports) that the Rust binary reads upon boot.
Spin up the local databases and compile the application binary within the local environment container:
docker compose up -d --buildNote: Local execution automatically merges docker-compose.yml and docker-compose.override.yml.
The project contains two tiers of tests, both of which run automatically in CI/CD on every push to main or business-* branches (see deploy.yml):
- Unit tests — Located in
platform/src/tests/. Test individual services and use cases with mocked adapters. Fast, no external dependencies required. - Integration tests — Located in
tests/tests/. Spin up the full application stack against real PostgreSQL and Redis containers, using wiremock for external API mocking.
Ensure PostgreSQL and Redis are running (via Docker):
docker compose up -d postgres rediscargo test -p platform --libcargo test -p integration-testsNote: Integration tests load configuration from
config/testing/base.tomland expectAPP_ENV=testingto be set. The test harness handles this automatically.
Deployments to the production VPS are fully automated using GitHub Actions. On every push to main or business-* branches, the pipeline first runs unit and integration tests, then builds and deploys only if all tests pass.
Because the production credentials and server settings are strictly excluded from source control for security, you must manually log into your VPS once prior to deployment to initialize your configuration files:
- Open a terminal and SSH into the remote server:
ssh your_user@your_vps_ip
- Navigate to the deployment path and ensure the required production config directories exist:
cd ~/hexum mkdir -p config/production
- Create the production settings file:
Populate it with the configuration parameters for the Rust binary. Use the template found here as the template for file.
touch config/production/base.toml
Note: The
.envfile on the VPS is managed automatically by the CI/CD pipeline. On each deployment,BUSINESS_NAMEandIMAGE_TAGare set dynamically from the branch, and database secrets (DB_USER,DB_PASSWORD,DB_NAME,REDIS_PASSWORD) are populated with their pre-configured default values from GitHub Secrets. You only need to override these secrets manually if you require non-default values.
Whenever code is pushed to the main branch, the GitHub Actions runner automatically executes the following pipeline:
- Runs the full test suite (unit + integration) against ephemeral PostgreSQL and Redis service containers. The pipeline halts if any test fails.
- Compiles the production-ready, minimal Rust image.
- Pushes the compiled artifact to the GitHub Container Registry (GHCR).
- Securely connects to the VPS via an SSH key.
- Checks if a Traefik container is running. If not, the workflow automatically provisions the external
web-network, navigates into~/traefik, and spins up the Traefik stack using its standalone compose configuration found here before proceeding. If Traefik is already active, this initialization step is skipped. - Pulls down the fresh application image from GHCR and deploys it using the main docker-compose.yml file via
docker compose up -d, successfully completing the deployment.