This directory contains the Rust backend for Agora Events. The server exposes a versioned HTTP API with Axum, persists data in PostgreSQL through SQLx, and uses Redis for cache-backed features.
- Axum: HTTP framework for routing, middleware layers, shared state, and typed responses.
- SQLx: Async PostgreSQL access, compile-time friendly query support, connection pooling, and database migrations.
- PostgreSQL: Primary relational database for users, organizers, events, tickets, transactions, ratings, audit logs, and related application data.
- Redis: Cache layer used by event and rates features. The current server startup requires a reachable Redis instance.
- Rust stable toolchain and Cargo
- PostgreSQL 14+ or Docker
- Redis 6+ or Docker
sqlx-cliwith PostgreSQL support
Install sqlx-cli:
cargo install sqlx-cli --no-default-features --features postgresThe server loads configuration from a .env file in this directory. Start from the example file:
cp .env.example .envPowerShell:
Copy-Item .env.example .envRequired variables:
| Variable | Example | Description |
|---|---|---|
DATABASE_URL |
postgres://user:password@localhost:5432/agora |
PostgreSQL connection string used by the server and SQLx migrations. |
Optional variables:
| Variable | Default | Description |
|---|---|---|
PORT |
3001 |
HTTP port for the Axum server. |
RUST_ENV |
development |
Runtime environment. production enables stricter security behavior such as HSTS. |
RUST_LOG |
info |
Log filter used by tracing-subscriber. |
CORS_ALLOWED_ORIGINS |
http://localhost:3000,http://localhost:5173 |
Comma-separated list of browser origins allowed by CORS. |
SOROBAN_RPC_URL |
https://soroban-testnet.stellar.org |
RPC endpoint used by blockchain health checks. |
REDIS_URL |
redis://127.0.0.1:6379 |
Redis connection URL for caching. Startup currently fails if Redis is unavailable. |
S3_BUCKET |
empty | Bucket name for image uploads. Required only for upload flows. |
S3_REGION |
auto |
S3/R2 region. auto is suitable for Cloudflare R2. |
S3_ACCESS_KEY_ID |
empty | S3/R2 access key. Required only for upload flows. |
S3_SECRET_ACCESS_KEY |
empty | S3/R2 secret key. Required only for upload flows. |
S3_ENDPOINT_URL |
unset | Custom S3/R2 endpoint URL. Required for Cloudflare R2. |
S3_PUBLIC_URL |
empty | Public base URL for uploaded files. Required only for upload flows. |
Run all commands from the server/ directory.
cp .env.example .envConfirm that DATABASE_URL points at your local PostgreSQL database:
DATABASE_URL=postgres://user:password@localhost:5432/agora
The included Compose file starts PostgreSQL with credentials that match .env.example:
docker compose up -dThis creates:
- Host:
localhost - Port:
5432 - Database:
agora - Username:
user - Password:
password
If your Docker Compose command is the older standalone binary, use docker-compose up -d.
If Redis is not already running locally, start it with Docker:
docker run --name agora_redis -p 6379:6379 -d redis:7The default REDIS_URL is:
REDIS_URL=redis://127.0.0.1:6379
Apply the SQLx migrations in migrations/:
sqlx migrate runThe same migrations are also executed during server startup, but running them explicitly makes setup failures easier to diagnose.
cargo runWhen startup succeeds, the API listens on:
http://localhost:3001
Use a different port by setting PORT in .env.
curl http://localhost:3001/api/v1/health
curl http://localhost:3001/api/v1/health/db
curl http://localhost:3001/api/v1/health/readyThe backend follows a layered Axum architecture:
Request -> Layer -> Route -> Handler -> Model -> Database -> Response
src/
|-- main.rs # Loads env, initializes logging, connects services, runs migrations, starts Axum.
|-- lib.rs # Exposes application modules for the binary and tests.
|-- config/ # Environment config plus CORS, request ID, and security header layers.
|-- routes/ # Builds the Axum Router and registers versioned API paths.
|-- handlers/ # Endpoint functions that validate input, call models/services, and return responses.
|-- models/ # SQLx-backed Rust structs that represent database records and payload shapes.
|-- middleware/ # Request middleware such as audit logging, rate limiting, and request tracing.
|-- cache/ # Redis cache integration.
|-- notifications/ # Email and SMS notification adapters.
`-- utils/ # Shared errors, responses, pagination, logging, and test helpers.
main.rsloads.env, initializes tracing, buildsConfig, opens aPgPool, runs SQLx migrations, connects to Redis, and callsroutes::create_routes.src/routes/mod.rsregisters API routes under/api/v1and applies shared Axum layers.- Request layers handle request IDs, tracing, CORS, security headers, rate limits, and route-specific middleware.
- The matched route calls a handler from
src/handlers. - The handler extracts path/query/body/state values, performs endpoint orchestration, and uses models or shared services for data work.
- Model types in
src/modelsrepresent database-backed entities and keep SQLx row mapping close to the domain shape. - Handlers return consistent API responses through shared utilities in
src/utils.
Use this pattern when adding a new API feature:
- Add a migration in
migrations/if the feature needs schema changes. - Add or update model types in
src/models/for database-backed data. - Add handler functions in
src/handlers/for request validation and response construction. - Export new handler/model modules from their
mod.rsfiles. - Register the path in
src/routes/mod.rs, usually under/api/v1. - Add route or handler tests for the new behavior.
For example, a new orders API would typically add src/models/order.rs, src/handlers/orders.rs, export both modules, and nest an /orders router from src/routes/mod.rs.
Run Rust tests:
cargo testRun formatting and lint checks before opening a PR:
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warningsRun the health endpoint smoke test after starting the server:
bash ./test_health_endpoints.shThe script checks:
GET /api/v1/healthGET /api/v1/health/blockchainGET /api/v1/health/dbGET /api/v1/health/ready
On Windows, run the script from Git Bash or WSL.
When opening the PR for this issue, include the closing keyword in the PR description:
Closes #issue_number