A Rust reimplementation of the Expenses backend that powers registration, authentication, and transaction tracking. It keeps the original PostgreSQL schema so it can coexist with the TypeScript version while showcasing the same domain but with Axum + SeaORM + Redis.
- Axum for the HTTP layer
- SeaORM for database access (PostgreSQL)
- redis-rs for session storage
- Argon2 password hashing, Tokio runtime and tower-http tracing/cors helpers
The service reads the following environment variables (you can use .env during development):
| Variable | Description | Default |
|---|---|---|
DATABASE_URL |
PostgreSQL connection string | required |
REDIS_URL |
Redis connection string | required |
HOST |
Interface to bind (e.g. 0.0.0.0) |
0.0.0.0 |
PORT |
HTTP port | 8080 |
SESSION_TTL_SECS |
Session TTL in seconds | 21600 (6 hours) |
Make sure the PostgreSQL database already contains the tables created by the TypeScript
project's migrations (users, categories, transactions, ...). The Rust service reuses the
same schema so both stacks can talk to the same data.
cd expenses-rs
cargo runWith the environment configured you should see a Starting HTTP server log and the API will be
available at http://localhost:8080 (or the host/port you set).
All responses follow { result: boolean, data?: T, message?: string, errorCode?: number }. Endpoints that require
a session expect the Authorization: Bearer <token> header using the token issued by
POST /api/auth/login.
| Method & Path | Description |
|---|---|
GET /health |
Simple liveness probe |
POST /api/users |
Register a new user (email, firstName, lastName, password) |
POST /api/auth/login |
Authenticate, receive { token, user } and set the session cookie (sessionID by default) |
POST /api/auth/logout |
Invalidate the current session |
GET /api/users/me |
Return the authenticated user |
GET /api/categories |
List categories of the authenticated user |
POST /api/categories |
Create a category { transactionType, name, note? } |
GET /api/categories/:categoryId |
Fetch a single category |
DELETE /api/categories/:categoryId |
Delete a category. Add ?deleteTransactions=true to also remove its transactions |
GET /api/transactions |
List transactions for the user with optional month, year, categoryId, transactionType, limit filters |
POST /api/transactions |
Create one or multiple transactions. Accepts either categoryId, the legacy categoryName + categoryNote, or the nested { category: { name, type?, note? } } payload together with the financial fields (transactionType, amount, currency, month, year, etc.) |
GET /api/transactions/:transactionId |
Fetch a transaction by id |
DELETE /api/transactions/:transactionId |
Soft delete a transaction |
GET /api/transactions/balance |
Aggregate balances grouped by transaction type and currency |
GET /api/transactions/months |
Returns the months available per year for the user |
GET /api/transactions/total-saving |
Total amount saved (type SAVING) |
POST /api/users
{
"email": "user@example.com",
"firstName": "Jane",
"lastName": "Doe",
"password": "supersecret"
}POST /api/auth/login
{
"email": "user@example.com",
"password": "supersecret"
}POST /api/transactions
Authorization: Bearer <token>
{
"transactionType": "EXPENSE",
"amount": 50.5,
"currency": "USD",
"month": "JANUARY",
"year": 2025,
"note": "Groceries",
"category": {
"name": "Food",
"type": "EXPENSE"
}
}POST /api/transactions
Authorization: Bearer <token>
{
"transactions": [
{
"transactionType": "INCOME",
"amount": 3000,
"currency": "USD",
"month": "FEBRUARY",
"year": "2025",
"categoryId": "<existing-category-id>"
},
{
"transactionType": "EXPENSE",
"amount": 120.55,
"currency": "UYU",
"month": "FEBRUARY",
"year": 2025,
"categoryName": "Utilities"
}
]
}GET /api/transactions?month=JANUARY&year=2025
Authorization: Bearer <token>- Add persistence for the remaining resources (financial goals, shopping lists, etc.)
- Ship richer validation/middleware, OpenAPI docs and integration tests
- Package with Docker to mirror the TypeScript deployment story