A backend assignment project for finance data processing, access control, and dashboard analytics. The API is built with Node.js, Express, and SQLite, and demonstrates:
- user and role management
- active or inactive user status
- financial record CRUD with filtering
- dashboard summary aggregation APIs
- backend-enforced role-based access control
- validation, error handling, rate limiting, and tests
- Node.js
- Express
- SQLite via
node-sqlite3-wasm - JWT authentication with
jsonwebtoken - Password hashing with
bcryptjs - Validation with
express-validator - Rate limiting with
express-rate-limit - Integration tests with Jest and Supertest
viewer: can access dashboard summaries onlyanalyst: can read financial records and dashboard summariesadmin: can manage users and perform full financial record CRUD
src/
app.js
config/
database.js
controllers/
authController.js
dashboardController.js
recordController.js
userController.js
middleware/
auth.js
errorHandler.js
rateLimiter.js
validators.js
models/
recordModel.js
userModel.js
routes/
authRoutes.js
dashboardRoutes.js
recordRoutes.js
userRoutes.js
services/
authService.js
dashboardService.js
recordService.js
userService.js
utils/
finance.js
ranges.js
response.js
tests/
auth.test.js
dashboard.test.js
records.test.js
users.test.js
The code follows a controller -> service -> model separation:
- controllers handle HTTP concerns
- services enforce business rules and access logic
- models contain SQL and persistence details
Users are stored with:
nameemailpasswordrolestatus- timestamps
- soft delete support via
deleted_at
Financial records are stored with:
amount_centstypecategoryrecord_datenotescreated_byupdated_by- timestamps
- soft delete support via
deleted_at
Amounts are persisted as integer cents instead of floating point values. This avoids rounding drift in totals and lets the dashboard aggregations stay precise.
- Node.js 18+
- npm
npm install
copy .env.example .env
npm run seed
npm run devThe API runs on http://localhost:3000 by default.
PORT=3000
NODE_ENV=development
JWT_SECRET=replace_with_a_strong_secret
JWT_EXPIRES_IN=7d
DB_PATH=./data/finance-dashboard.db
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX=100All responses use this envelope:
{
"success": true,
"message": "Human readable message",
"data": {}
}Validation failures return 422:
{
"success": false,
"message": "Validation failed.",
"errors": [
{
"field": "email",
"message": "A valid email is required."
}
]
}Registers a user and returns a JWT.
Example body:
{
"name": "Asha",
"email": "asha@example.com",
"password": "password123",
"role": "analyst"
}Note: allowing role at registration is an assessment convenience so the reviewer can quickly exercise all access paths. In a real system, role assignment would usually be admin-only.
Authenticates a user and returns a JWT.
Returns the authenticated user's profile.
All user endpoints require authentication.
Admin only. Supports:
pagelimitsearchrolestatus
Admin only. Creates a managed user account.
Accessible by the same user or an admin.
Accessible by the same user or an admin.
- non-admin users can update their own
name,email, andpassword - admins can also update
roleandstatus
Admin only. Soft-deletes a user. Self-archival is blocked.
- viewers: no record access
- analysts: read-only access
- admins: full CRUD access
Available to analysts and admins.
Supported filters:
pagelimitsearchtypecategoryfromtominAmountmaxAmount
Available to analysts and admins.
Admin only.
Example body:
{
"amount": 2500.75,
"type": "income",
"category": "Consulting",
"record_date": "2026-03-15",
"notes": "Monthly advisory retainer"
}Admin only. Partial updates are supported.
Admin only. Soft-deletes a financial record.
Available to viewers, analysts, and admins.
Supported query params:
fromtotrendBywith valuesmonthorweekrecentLimit
Response sections:
totalscategoryTotalstrendsrecentActivity
Example response shape:
{
"filters": {
"from": "2026-01-01",
"to": "2026-03-31",
"trendBy": "month",
"recentLimit": 5
},
"totals": {
"totalIncome": 12000,
"totalExpenses": 3400,
"netBalance": 8600,
"recordCount": 8
},
"categoryTotals": [
{
"category": "Sales",
"totalIncome": 9500,
"totalExpenses": 0,
"netBalance": 9500,
"recordCount": 1
}
],
"trends": [
{
"period": "2026-01",
"totalIncome": 9500,
"totalExpenses": 1800,
"netBalance": 7700,
"recordCount": 2
}
],
"recentActivity": []
}Access control is enforced at the backend using middleware and service checks:
authenticateverifies JWTs and blocks deleted or inactive accountsauthorize(...roles)restricts endpoints by role- user service methods also protect self-service versus admin-only updates
This keeps authorization rules close to the API boundary while still preserving business checks in the service layer.
The project demonstrates:
- request validation with
express-validator 401for missing or invalid authentication403for permission failures and inactive accounts404for missing resources409for duplicate email conflicts422for invalid request payloads- global error formatting middleware
- JWT authentication
- pagination on list APIs
- filtering on record and user listing
- soft delete for users and records
- rate limiting
- seed script
- integration tests
npm testThe tests run against an in-memory SQLite database and cover:
- authentication flows
- user management
- financial record permissions and CRUD
- dashboard summary aggregation
roleis accepted on self-registration for demo convenience.- Financial records are shared organization-level records, not user-owned records.
- Viewers can access dashboard summaries but not raw financial records.
- Soft-deleted users remain unavailable for login and future queries.
- SQLite is used because it keeps setup simple for an assessment project while still exercising real persistence and SQL aggregation.