A monorepo inventory management system with a Kotlin Multiplatform app (Android + iOS + Desktop) and a Fastify + Drizzle backend.
- User Authentication: JWT-based authentication with role-based access control
- Component Management: CRUD operations for inventory components (Admin/TA only)
- Request System: Users can create requests for components, admins/TAs can view and manage requests
- Role-Based Access: User roles (ADMIN, FACULTY, STUDENT, TA, PENDING) with different permissions; new registrations start as PENDING until promoted
- Database Seeding: Automated admin user creation for initial setup
- Comprehensive Testing: Full test suite for authentication, components, and requests
- HTTP Caching & Local Cache:
- Backend supports conditional
GETviaIf-Modified-Since/Last-Modifiedand returns304 Not Modifiedwhen the client cache is fresh. - The KMP app uses SQLDelight to cache the components list locally and drives the UI from the cache, so the list remains visible across navigation and 304 responses.
- Backend supports conditional
- Bun
- PostgreSQL (local or hosted, e.g. Supabase/Neon)
- Docker or Podman with Compose (optional: for local PostgreSQL and/or running the backend in a container)
cd backend
bun installThe UI lives in the app/ multiplatform module:
-
Android:
- Open the repo in Android Studio.
- Use a recent JDK (21+) and Android Gradle Plugin (already configured).
- Run the
androidrun configuration or./gradlew :android:installDebugfromapp/.
-
Desktop:
- Requires JDK 21+.
- From
app/:./gradlew :desktop:run
- This launches the Compose Desktop app (AppImage/MSI/EXE packaging is configured for releases).
Compose uses profiles so the main and test databases don’t run at the same time (same port). From backend/:
| Command | Effect |
|---|---|
podman compose -f compose.db.yaml --profile main up -d |
Start main Postgres (port 5432, DB iiitnr_inventory, persisted) |
podman compose -f compose.db.yaml --profile test up -d |
Start test Postgres (port 5432, DB iiitnr_inventory_test, ephemeral) |
Main DB (development):
cd backend
podman compose -f compose.db.yaml --profile main up -d- Database:
iiitnr_inventory - User:
postgres - Password:
postgres
Test DB (used by just test; ephemeral, no volume):
cd backend
podman compose -f compose.db.yaml --profile test up -dUse a hosted DB (e.g. Supabase, Neon) or install Postgres yourself. Set DATABASE_URL in backend/.env; no POSTGRES_* vars are required.
Copy the example environment file:
cp backend/config/env.example backend/.envEdit backend/.env and set your Environment Variables
Important: Change JWT_SECRET to a secure random string. The application will not start if it's set to "change-me".
Generate migration files (after schema changes):
cd backend
bun run db:generateApply existing migrations (development / production / container):
cd backend
bun run migrateCreate an admin user:
cd backend
bun run seedThis creates an admin account with the credentials specified in your .env file (defaults shown above).
cd backend
bun run devThe server will start on http://localhost:4000 (or the port specified in PORT).
Use the pre-built image or build from backend/Dockerfile. From backend/:
- Migrations: Run once (e.g. for Supabase/Neon or a remote DB):
bun run migrate
- Compose (uses
backend/compose.yaml: image,env_file: .env, port 4000):Ensurepodman compose up -d
backend/.envexists and hasDATABASE_URL(andJWT_SECRET, etc.). The backend listens on port 4000.
Build the image locally (from backend/):
podman build -t iiitnr-inventory-backend -f Dockerfile .
# optional: podman build -t iiitnr-inventory-backend .Then run with podman run --rm -p 4000:4000 --env-file .env iiitnr-inventory-backend, or point compose.yaml at your image.
GET /health- Health check endpointGET /- Hello World endpoint
All authentication endpoints are public (no auth required).
-
POST /auth/register- Register a new user- Body:
{ email, password, name? } - New users receive role
PENDINGuntil an admin promotes them - Returns:
{ user, token }
- Body:
-
POST /auth/login- Login with email and password- Body:
{ email, password } - Returns:
{ user, token }
- Body:
-
GET /auth/me- Get current user info (requires authentication)- Headers:
Authorization: Bearer <token> - Returns:
{ user }
- Headers:
All component endpoints require authentication. Create/Update/Delete require Admin or TA role.
GET /components- List all components (authenticated users)GET /components/:id- Get component by ID (authenticated users)POST /components- Create a new component (Admin/TA only)- Body:
{ name, description?, quantity?, category?, location? }
- Body:
PUT /components/:id- Update component (Admin/TA only)- Body:
{ name?, description?, quantity?, category?, location? }
- Body:
DELETE /components/:id- Delete component (Admin/TA only)
All request endpoints require authentication. Users can only see their own requests unless they're Admin/TA.
-
POST /requests- Create a new request (authenticated users)- Body:
{ items: [{ componentId, quantity }] } - Returns:
{ request }with items and component details
- Body:
-
GET /requests- List requests (authenticated users)- Query params:
?status=PENDING|APPROVED|REJECTED|FULFILLED(optional) - Query params:
?userId=<uuid>(Admin/TA only, optional) - Students/Faculty: Only see their own requests
- Admin/TA: See all requests, optionally filtered by userId
- Returns:
{ requests }with items, components, and user details
- Query params:
- ADMIN: Full access to all endpoints, can manage components and view all requests
- TA (Teaching Assistant): Can manage components and view all requests
- FACULTY: Can view components and create/view their own requests
- STUDENT: Can view components and create/view their own requests
- PENDING: New registrations start here; protected routes return 403 until an admin assigns a role (e.g. via
bun run create:user)
Recommended – from repo root, use the Justfile to start the ephemeral test DB, run migrations, then tests:
just testThis starts the test DB (compose.db.yaml --profile test), runs bun test (using TEST_DATABASE_URL or DATABASE_URL from backend/.env), then stops the test DB.
To run tests only (test DB must already be running, either locally or on remote):
cd backend
bun testThe test suite includes:
- Authentication tests (
tests/auth.test.ts) - Component management tests (
tests/components.test.ts) - Request system tests (
tests/requests.test.ts)
Important: TEST_DATABASE_URL (or DATABASE_URL for derivation) must be set; the test setup exits with an error if not. Use a dedicated test database (e.g. name containing _test) and keep it different from production.
From backend/:
bun run dev- Start development server with hot reloadbun run build- Build for productionbun run start- Start production serverbun run lint- Run ESLintbun run lint:fix- Fix ESLint errorsbun run typecheck- Type check TypeScriptbun run format- Format backend code with Prettierbun run db:generate- Generate Drizzle migration files from schema changesbun run migrate- Apply Drizzle migrations (src/drizzle)bun run db:studio- Open Drizzle Studiobun run seed- Seed database with admin userbun run migrate:backup- Backup app data, reset public schema, run migrations, then restore data (works on Supabase/Neon; requirespg_dumpandpsql). Optional:bun run migrate:backup -- --restore-from=backups/data_YYYY-MM-DD.sqlto restore from a specific backup file.bun run create:user- Create a user (e.g. promote PENDING to a role). Usage:bun run create:user -- --email ... --password ... --role PENDING|STUDENT|FACULTY|TA|ADMIN [--name "Name"]
At the repo root, Justfile streamlines common tasks:
just/just dev— Start backend dev serverjust install— Install backend dependenciesjust image— Build backend image (Dockerfile) and run with--env-file .env -p 4000:4000just up— Start backend container (podman compose up -d)just down— Stop backend containerjust restart—compose downthenup -djust logs— Follow compose logsjust test— Start test DB (compose.db.yaml --profile test), runbun test, then stop DBjust desk— Run KMP desktop appjust lint— Lint backend +ktlintCheck(app)just lint-fix— Lint fix +ktlintFormatjust typecheck— Backend typecheckjust fmt— Format backend +ktlintFormatjust detekt— Detekt on the app
The mobile app is an Android application built with Kotlin. See the app/ directory for Android-specific setup and build instructions.
To deploy the backend to Render:
- Create a new Web Service pointing at this repository
- Root directory:
backend - Build command:
bun install --frozen-lockfile && bun run build - Start command:
bun run start - Add environment variables:
DATABASE_URL(required)JWT_SECRET(required, must be changed from "change-me")PORT(optional, defaults to 4000)
The health check endpoint is available at GET /health for Render's health checks.
.
├── backend/ # Fastify backend API
│ ├── config/ # Config (e.g. env.example)
│ ├── scripts/ # Operational scripts (seed, create-user, migrate-with-backup)
│ ├── src/ # Source code
│ │ ├── app.ts # Fastify app and routes
│ │ ├── server.ts # Entry point (DB checks at startup)
│ │ └── drizzle/ # Drizzle schema, migrations, and DB client
│ ├── tests/ # Test suite
│ ├── compose.yaml # Backend service (image, env_file, port 4000)
│ ├── compose.db.yaml # Local Postgres only (profiles: main, test)
│ ├── Dockerfile # Multi-stage image build (Bun → runner)
│ └── drizzle.config.ts # Drizzle Kit config (DB URL, schema path, migrations out)
├── app/ # KMP app (Android, Desktop)
└── Justfile # Tasks: dev, test, image, up, down, lint, etc.
The application uses the following main models:
- User: Authentication and user information with roles
- Component: Inventory components with quantity, category, and location
- Request: User requests for components with status tracking
- RequestItem: Individual items within a request linking components to requests
See backend/src/drizzle/schema.ts for the complete schema definition.
Private project for IIITNR.