A backend API for a multi-role finance dashboard. Supports financial record management, role-based access control, JWT authentication, and dashboard analytics.
| Layer | Choice |
|---|---|
| Runtime | Node.js + TypeScript |
| Framework | Express |
| ORM | Prisma |
| Database | SQLite (dev) / PostgreSQL (prod) |
| Auth | JWT + bcrypt |
| Validation | Zod |
| Tests | Vitest |
| Rate limiting | express-rate-limit |
# 1. Install dependencies, generate Prisma client, create DB, seed data
npm run setup
# 2. Start dev server with hot reload
npm run devServer starts at http://localhost:3000
Copy .env.example to .env and fill in:
DATABASE_URL="file:./dev.db" # SQLite path (or postgres:// for Postgres)
JWT_SECRET="your-secret-here" # Change in production
JWT_EXPIRES_IN="7d"
PORT=3000
NODE_ENV=development| Password | Role | |
|---|---|---|
| admin@finance.dev | password123 | ADMIN |
| analyst@finance.dev | password123 | ANALYST |
| viewer@finance.dev | password123 | VIEWER |
| Action | VIEWER | ANALYST | ADMIN |
|---|---|---|---|
| View records | ✅ | ✅ | ✅ |
| View dashboard | ✅ | ✅ | ✅ |
| View trends | ❌ | ✅ | ✅ |
| Create records | ❌ | ✅ | ✅ |
| Update own records | ❌ | ✅ | ✅ |
| Update any record | ❌ | ❌ | ✅ |
| Delete records (soft) | ❌ | ❌ | ✅ |
| Manage users | ❌ | ❌ | ✅ |
POST /auth/register Create account (role defaults to VIEWER)
POST /auth/login Get JWT token
GET /auth/me Get current user (requires token)
GET /users List users ?role=ADMIN&isActive=true&page=1&limit=20
GET /users/:id Get user by ID
PATCH /users/:id/role Change role { "role": "ANALYST" }
PATCH /users/:id/status Toggle active { "isActive": false }
DELETE /users/:id Permanently delete user
GET /records List records (VIEWER+)
?type=income&category=Salary&from=2024-01-01T00:00:00Z
&to=2024-12-31T23:59:59Z&search=rent&page=1&limit=20
&sortBy=date&order=desc
GET /records/:id Get single record (VIEWER+)
POST /records Create record (ANALYST+)
PATCH /records/:id Update record (ANALYST+ and must own it, or ADMIN)
DELETE /records/:id Soft delete (ADMIN only)
GET /dashboard/summary Totals + net balance (VIEWER+)
GET /dashboard/categories Category breakdown (VIEWER+)
GET /dashboard/recent?limit=10 Recent activity (VIEWER+)
GET /dashboard/top-categories Top 5 income/expense categories (VIEWER+)
GET /dashboard/trends/monthly Month-by-month trends (ANALYST+) ?months=12
GET /dashboard/trends/weekly Week-by-week trends (ANALYST+) ?weeks=8
GET /health Server status check
curl -X POST http://localhost:3000/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"admin@finance.dev","password":"password123"}'curl -X POST http://localhost:3000/records \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"amount": 4500,
"type": "income",
"category": "Salary",
"date": "2024-01-15T00:00:00Z",
"description": "January salary"
}'curl http://localhost:3000/dashboard/summary \
-H "Authorization: Bearer <token>"curl "http://localhost:3000/records?type=expense&category=Rent&from=2024-01-01T00:00:00Z" \
-H "Authorization: Bearer <token>"npm testTests cover:
- Dashboard aggregation logic (summary, category totals, monthly trends)
- RBAC middleware (role hierarchy, blocking, allowing)
-
SQLite by default — Zero-config for evaluation. Change
DATABASE_URLto a Postgres connection string and re-runprisma migrate devto use Postgres; no code changes needed. -
Roles are hierarchical —
ADMIN > ANALYST > VIEWER. TherequireRole("ANALYST")guard allows ANALYST and ADMIN, not just ANALYST. -
Soft deletes — Records are never physically removed. They get a
deletedAttimestamp and are excluded from all queries. This preserves audit history. -
Owner-or-admin for PATCH — An ANALYST can only update records they created. ADMINs can update any record.
-
JWT expiry — 7 days, configurable via
JWT_EXPIRES_INin.env. No refresh token mechanism (out of scope), but trivial to add. -
Pagination defaults —
page=1,limit=20, maxlimit=100. -
New users default to VIEWER — Only an ADMIN can promote a user to ANALYST or ADMIN after registration.
-
Rate limiting — 200 req/15min globally; 20 req/15min on auth routes specifically to mitigate brute-force attacks.
DATABASE_URL="postgresql://user:password@localhost:5432/finance_db"npx prisma migrate dev --name init
npm run db:seed