Personal finance management application to track expenses and incomes. Built as a monorepo with three components:
front/— SvelteKit SPA (SSR) written in TypeScript.server/— Rust backend API with a layered architecture.database/— SurrealDB schema and data model.
| Layer | Technology |
|---|---|
| Frontend | Svelte 5 (runes), SvelteKit 2, TypeScript, Vite 7 |
| Icons | @iconify/svelte |
| Package manager | Bun |
| Backend | Rust (edition 2024), Actix-web 4 |
| Auth | jsonwebtoken 10 (JWT) |
| Database | SurrealDB 3 (surrealdb + surrealdb-types crates) |
| Env config | dotenvy |
| Testing | Vitest + Playwright (frontend) |
| Linting / Format | ESLint, Prettier (frontend) |
The frontend runs SvelteKit server-side load functions (+page.server.ts) that call the Rust API using a JWT stored in a token cookie. The API talks to SurrealDB over WebSocket.
+---------------------------------------------+
| BROWSER (SPA) |
| /signup /signin /dashboard |
+---------------------+-----------------------+
|
HTTP (form-urlencoded) | JWT in cookie / Authorization header
v
+---------------------------------------------+
| SVELTEKIT |
| SSR load functions (+page.server.ts) |
| actions / stores / interfaces |
+---------------------+-----------------------+
|
REST (fetch) | Authorization: <token>
v
+---------------------------------------------+
| RUST API (Actix-web) |
| routes -> services -> repositories |
| UserJwt middleware (JWT validation) |
+---------------------+-----------------------+
|
SurrealQL | WebSocket (Ws)
v
+---------------------------------------------+
| SurrealDB 3 |
| user expense income (had relation) |
+---------------------------------------------+
The server follows the layered pattern from Black Hat Rust by Sylvain Kerkour: controllers, services and repositories are separated, keeping HTTP concerns, business logic and data access independent.
HTTP request
|
v
+-------------------------+ +-------------------------+
| ROUTES / CONTROLLERS | ---> | SERVICES |
| Actix-web handlers | | Business logic, |
| (mod.rs) | | validation, |
+-------------------------+ | error handling (code) |
^ +-------------------------+
| |
| UserJwt middleware v
| (validates JWT, injects +-------------------------+
| user id into request) | REPOSITORIES |
| | SurrealQL queries, |
+-----------------------------| DB <-> domain mapping |
+-------------------------+
|
v
+---------------+
| SurrealDB |
+---------------+
Modules: auth, user, expenses, incomes, plus a shared layer (config, shared) for DB connection, JWT middleware and common entities.
POST /auth/signin (username + password)
|
v
SigninRepository: SELECT ... WHERE username = $u AND crypto::bcrypt::compare(password, $p)
|
v
Generate JWT (jsonwebtoken, secret from TOKEN_SEED)
|
v
Frontend stores token in `token` cookie
|
v
Subsequent requests -> UserJwt middleware decodes JWT -> req.extensions user id
Passwords are never stored in plain text: SurrealDB hashes them with crypto::bcrypt::generate on user.password (see database/model.surql).
gestion/
├── database/
│ └── model.surql # SurrealDB schema (tables, fields, permissions)
├── front/ # SvelteKit application
│ ├── src/
│ │ ├── lib/
│ │ │ ├── components/ # Reusable Svelte components
│ │ │ ├── interfaces/ # TS types (user, expenses, incomes)
│ │ │ ├── store/ # Svelte 5 runes store (user state)
│ │ │ └── config.ts # API URL config
│ │ └── routes/ # /signin /signup /dashboard/{expenses,incomes}
│ └── package.json
└── server/ # Rust API
├── src/
│ ├── main.rs # App bootstrap + routes
│ ├── auth/ # signup / signin (controller, service, repository)
│ ├── user/ # user profile endpoints
│ ├── expenses/ # expenses CRUD
│ ├── incomes/ # incomes CRUD
│ ├── config/ # env vars, DB + JWT config
│ └── shared/ # DB connection, JWT middleware, entities
├── .env.example
└── Cargo.toml
+--------+ +-------+ +-----------+
| user | 1 --- * | had | * --- 1 | expense |
+--------+ +-------+ +-----------+
| | +-----------+
| *--------| income |
| +-----------+
|
| username UNIQUE (index_username)
A user has (had) zero or more expense and income records. The had relation is ENFORCED SCHEMALESS and points IN to user and OUT to expense | income.
| Table | Type | Fields |
|---|---|---|
user |
SCHEMAFULL | name, lastname, username, password (bcrypt) |
expense |
SCHEMAFULL | amount, description, processed, date |
income |
SCHEMAFULL | amount, description, processed, date |
had |
RELATION | in: user, out: expense | income |
Table-level PERMISSIONS NONE and field-level permissions restrict access; the user table only allows select/update/delete for WHERE id = $auth.id.
| Method | Path | Description |
|---|---|---|
| GET | / |
Health check |
| POST | /auth/signup |
Create a user account |
| POST | /auth/signin |
Log in, returns user + JWT |
| GET | /user/refresh |
Refresh the JWT and return current user |
| GET | /user/username/{username} |
Check if a username exists |
| PATCH | /user/names |
Update name / lastname |
| PATCH | /user/username |
Update username |
| GET | /expense |
List expenses of the authenticated user |
| POST | /expense |
Create an expense |
| PATCH | /expense/{id} |
Update all expense fields |
| PATCH | /expense/{id}/amount |
Update expense amount |
| PATCH | /expense/{id}/description |
Update expense description |
| PATCH | /expense/{id}/date |
Update expense date |
| PATCH | /expense/{id}/processed |
Update expense processed flag |
| DELETE | /expense/{id} |
Delete an expense |
| GET | /income |
List incomes of the authenticated user |
| POST | /income |
Create an income |
| PATCH | /income/{id} |
Update all income fields |
| PATCH | /income/{id}/amount |
Update income amount |
| PATCH | /income/{id}/description |
Update income description |
| PATCH | /income/{id}/date |
Update income date |
| PATCH | /income/{id}/processed |
Update income processed flag |
| DELETE | /income/{id} |
Delete an income |
Expense and income endpoints are protected by the
UserJwtmiddleware and require anAuthorization: <token>header. Bodies are sent asapplication/x-www-form-urlencoded.
- Rust (edition 2024)
- Bun
- A running SurrealDB instance
- The schema from
database/model.surqlapplied to the target database
cd server
cp .env.example .env # then set the correct values
cargo runConfiguration (.env):
HOST=0.0.0.0
PORT=8080
DB_WSS=ws://localhost:8000
DB_USER=root
DB_PASS=your_root_password
DB_NS=management
DB_NAME=mndb
TOKEN_SEED=your_jwt_secretcd front
bun install
bun run devThe frontend points to the API via src/lib/config.ts (apiUrl). Available scripts:
| Command | Description |
|---|---|
bun run dev |
Start the development server |
bun run build |
Production build |
bun run check |
Type-check with svelte-check |
bun run lint |
Prettier + ESLint |
bun run test |
Run Vitest unit tests |