Backend API for the Bisakerja platform. This service owns authentication, user workflows, job discovery API contracts, application tracking, AI workflow orchestration, persistence access, runtime validation, and the public REST boundary consumed by the frontend.
Bisakerja is an AI-assisted career decision platform for Indonesian job seekers. The backend exists to provide a stable product API for finding relevant jobs, understanding job fit, improving CV quality, and tracking application outcomes.
- Overview
- Platform Context
- Service Boundary
- MVP Modules
- API Surface
- Documentation Map
- Tech Stack
- Getting Started
- Environment Configuration
- Database Workflow
- Available Scripts
- Testing And Verification
- API Documentation
- Project Structure
- Deployment Notes
- Contribution Guide
The Bisakerja Backend API is the main application backend for Bisakerja. It provides the frontend-facing REST API, validates user input, enforces authentication and ownership rules, formats consistent API responses, and coordinates data access through PostgreSQL and Prisma.
This repository is responsible for:
- Auth, session, email verification, password reset, and Google OAuth workflows
- User profile, preference, bookmark, CV file, and application tracker workflows
- Job search and job detail access over normalized job records
- AI CV Analyzer orchestration through the Model API boundary, including CV feedback, fit alignment, and embedded job recommendations
- Internal scraper sync and notification handoff endpoints protected by service credentials
- Prisma schema, migrations, seed data, and repository-level persistence logic
- Service-owned technical documentation and generated API artifacts
- Docker-based runtime deployment support for a VPS-style environment
The backend should not behave as a generic pass-through proxy. It should shape responses around product workflows, protect private user data, and translate downstream failures into frontend-safe API responses.
Bisakerja is positioned as a decision layer for Indonesian job seekers, not only as a job listing interface. The product combines job discovery, profile and preference context, fit scoring, explainable recommendations, CV feedback, and application tracking.
The broader platform includes:
| Service | Responsibility |
|---|---|
| Frontend UI | Presents discovery, fit analysis, CV analysis, preferences, and tracking UI |
| Backend API | Owns business workflows, auth, persistence access, and API contracts |
| Scraper API | Collects and normalizes external job data |
| Model API | Produces fit scores, explanations, skill gaps, and CV analysis output |
| PostgreSQL | Stores users, jobs, preferences, applications, and AI result snapshots |
The frontend calls the Backend API for product workflows. It must not call Scraper API or Model API directly.
The Backend API owns:
- Authentication and authorization decisions
- User profile, preference, bookmark, and application tracker workflows
- Backend-facing validation with Zod
- API response formatting and error mapping
- Prisma-based database access for backend-owned records
- Product-friendly formatting of Model API outputs
- Read access to normalized job records required by search, detail, and AI workflows
The Backend API does not own:
- Frontend rendering or client-side state
- Scraping, parsing, and normalization logic for external job platforms
- Model training, model artifact management, or low-level inference internals
- Central platform documentation governance outside service-owned backend docs
| Module | Responsibility |
|---|---|
| Auth | Register, login, logout, refresh/session, password reset, email verification, and Google OAuth |
| Users | Account profile, onboarding state, career background, skills, and education |
| Preferences | Career status, target roles, locations, work types, salary, and notifications |
| Jobs | Search, filter, sort, list, detail, company data, source data, and apply links |
| Bookmarks | Save, remove, list saved jobs, duplicate handling, and ownership checks |
| Applications | Track user-specific applications, status changes, and status history |
| CV Files | Upload reusable user CV PDFs and expose safe active-file metadata |
| AI CV Analyzer | Analyze uploaded or stored CV PDFs against target job roles; return fit alignment, ATS feedback, section reviews, top actions, and embedded job recommendations |
| AI CV Generate | Generate safe markdown HTML CV content from an owned stored CV, structured summary, and sanitized template input |
| Shared CV Evidence | Keep Analyzer wrapper and Generate flows on one sanitized evidence schema without retaining raw CV text, prompts, provider payloads, or secrets |
| AI Job Fit | Retired standalone route; fit-style output now lives in CV Analyzer analysisResult.jobFitAlignment |
| AI Job Recommendations | Retired standalone route; compact recommendations now live in CV Analyzer analysisResult.jobRecommendations |
| Internal | Accept scraper job sync payloads and notification handoff events through service-token auth |
| Health | Liveness and readiness endpoints |
Future modules such as mentoring, notification expansion, analytics, payments, and direct ATS integration are documented as future scope and should not block MVP behavior.
Default local base URL:
http://localhost:3000/api/v1
Key route groups:
| Route group | Prefix | Auth class |
|---|---|---|
| Health | /health/live, /health/ready |
Public or infrastructure-restricted |
| Auth | /api/v1/auth |
Public plus authenticated session routes |
| Users | /api/v1/me |
Authenticated |
| Preferences | /api/v1/me/preferences |
Authenticated or onboarding access token |
| Jobs | /api/v1/jobs |
Public for search and detail |
| Bookmarks | /api/v1/me/bookmarks |
Authenticated and ownership-protected |
| Applications | /api/v1/me/applications |
Authenticated and ownership-protected |
| CV Files | /api/v1/me/cv-files |
Authenticated or onboarding access token |
| AI CV Analyzer | /api/v1/ai/cv-analyzer |
Authenticated |
| AI CV Generate | /api/v1/ai/cv-generate |
Authenticated and ownership-protected |
| Internal | /api/v1/internal |
Service-token protected |
JSON responses use a consistent envelope with success, message, data, meta, and error fields. See docs/api-response-standard.md for the full response contract.
Use this reading order when onboarding or reviewing changes:
docs/overview.mdfor service purpose, scope, and ownership boundaries.docs/api-reference.mdfor route groups, auth classification, API docs, and generated contract workflow.docs/project-structure.mdfor folder layout, module anatomy, and dependency direction.docs/environment.mdfor runtime variables and environment separation.docs/database.mdfor Prisma, PostgreSQL, entity ownership, and migration policy.docs/operations/testing.mdfor test strategy and verification commands.docs/operations/deployment.mdfor runtime deployment assumptions.docs/modules/*.mdfor module-specific behavior and endpoint details, includingdocs/modules/shared-cv-evidence.mdfor AI evidence safety rules.docs/generated/openapi.jsonanddocs/generated/routes.mdfor generated API artifacts.
| Area | Choice |
|---|---|
| Runtime | Bun 1.3.3 |
| Language | TypeScript |
| HTTP framework | Express.js 5 |
| Database | PostgreSQL |
| ORM | Prisma 7 |
| Validation | Zod |
| Logging | Pino |
| Security | Helmet, CORS, rate limits |
| Auth | JWT, refresh-token cookies |
| Async jobs | Redis + BullMQ + DB outbox |
| Fake provider or Resend | |
| API docs | OpenAPI and Scalar |
| Testing | bun test |
| Formatting | Prettier |
| Linting | ESLint |
Install dependencies:
bun installCreate local environment files:
cp .env.example .env
cp .env.test.example .env.testGenerate the Prisma client:
bun run prisma:generateApply migrations to your configured development database:
bun run prisma:migrate:devSeed deterministic local data:
bun run prisma:seedStart the API in watch mode:
bun run devStart the API without watch mode:
bun run startStart the async worker:
bun run worker:asyncThe default local API runs at:
http://localhost:3000
The app validates environment variables at startup. Missing required values or invalid values should fail before the server accepts requests.
Environment contract is explicit:
DATABASE_URLandDIRECT_DATABASE_URLmust both be set.SEED_USER_PASSWORDmust be set before runningbun run prisma:seed.MODEL_API_SERVICE_TOKEN,SCRAPER_API_SERVICE_TOKEN, andRESEND_API_KEYmust stay non-empty even in local fake/mock setups; use safe placeholders when the real integration is disabled.- The repository does not rely on env fallback values anymore.
Important local files:
| File | Purpose |
|---|---|
.env.example |
Local development baseline |
.env.test.example |
Test environment baseline |
.env.production.example |
Deployment-oriented baseline for Docker Compose |
Important variable groups:
- Application:
APP_ENV,NODE_ENV,PORT,API_PREFIX,APP_URL,FRONTEND_URL - Database:
DATABASE_URL,DIRECT_DATABASE_URL,SEED_USER_PASSWORD,RUN_DATABASE_TESTS - Auth: access-token secret, refresh-token secret, TTLs, cookie settings
- Security: CORS origins, trusted proxy, body limit, rate limit settings
- Async workloads:
REDIS_URL, queue name/prefix, concurrency, retry/backoff, recovery interval - Integrations: Model API URL/token, Scraper API service token, email provider, job freshness threshold
- Uploads: storage driver, upload path, CV limits, retention
- Observability: log level, request id header, health timeout
Do not commit real secrets or production database credentials.
PostgreSQL is the durable source of truth. Runtime environments are expected to use managed or externally hosted PostgreSQL through DATABASE_URL.
For providers such as Neon or Supabase:
- Use
DATABASE_URLfor the runtime connection, usually the provider pooler URL. - Use
DIRECT_DATABASE_URLfor Prisma migrations and seed flows. - Use a separate database for tests.
- Keep
RUN_DATABASE_TESTS=falsefor ordinary local test runs unless an isolated test database is prepared.
Common commands:
bun run prisma:validate
bun run prisma:generate
bun run prisma:migrate:dev
bun run prisma:migrate:deploy
bun run prisma:seed
bun run prisma:verify:migrations| Script | Purpose |
|---|---|
bun run dev |
Start the API in watch mode |
bun run start |
Start the API |
bun run typecheck |
Run TypeScript contract checks |
bun run lint |
Run ESLint |
bun run format |
Format files with Prettier |
bun run format:check |
Check formatting without writing |
bun test |
Run the default test suite |
bun run test:unit |
Run unit tests |
bun run test:routes |
Run route/API contract tests |
bun run test:integration |
Run integration tests |
bun run test:contracts |
Run downstream contract tests |
bun run test:smoke |
Run smoke tests |
bun run test:coverage |
Run full suite with coverage report |
bun run docs:generate:openapi |
Regenerate OpenAPI artifact |
bun run docs:generate:routes |
Regenerate route inventory |
bun run docs:generate:sync-readiness |
Regenerate sync-readiness inventory |
bun run docs:prepare:sync-bundle |
Prepare documentation sync bundle |
bun run docs:check |
Verify documentation metadata and examples |
bun run docs:scalar:check-config |
Validate Scalar Docs configuration |
bun run docs:scalar:preview |
Preview repo documentation through Scalar Docs |
bun run cleanup:cv-uploads |
Enqueue expired temporary CV cleanup job |
bun run worker:async |
Start Redis-backed async worker |
bun run prisma:reset |
Reset local database without reseeding |
bun run prisma:reset:seed |
Reset local database and reseed deterministic data |
Fast local verification:
bun run typecheck
bun run lint
bun run format:check
bun test
bun run test:coverage
bun run docs:checkDatabase-backed tests require an isolated test database and RUN_DATABASE_TESTS=true. Do not point these tests at development, staging, or production data.
Schema changes that affect async jobs also require applying Prisma migrations to the test database before DB-backed verification.
Documentation or route changes may also require:
bun run docs:generate:openapi
bun run docs:generate:routes
bun run docs:generate:sync-readiness
bun run docs:scalar:check-configMigration-affecting changes should run:
bun run prisma:verify:migrationsThe backend exposes generated API documentation in repo and at runtime:
| Surface | Path |
|---|---|
| Generated OpenAPI file | docs/generated/openapi.json |
| Generated route inventory | docs/generated/routes.md |
| Runtime OpenAPI endpoint | /openapi.json |
| Runtime Scalar viewer | /docs/api |
| Scalar Docs config | scalar.config.json |
Recommended local workflow:
- Start the backend with
bun run dev. - Open
http://localhost:3000/docs/api. - Use
http://localhost:3000/openapi.jsonwhen tooling needs the raw OpenAPI document.
When routes or contracts change, update the implementation, update relevant module docs, regenerate generated artifacts, and run docs verification before merging.
.
|-- prisma/
| |-- migrations/
| |-- schema.prisma
| |-- seed-data.ts
| `-- seed.ts
|-- src/
| |-- app.ts
| |-- server.ts
| |-- config/
| |-- core/
| |-- modules/
| `-- shared/
|-- tests/
| |-- unit/
| |-- integration/
| |-- smoke/
| `-- fixtures/
|-- docs/
| |-- generated/
| |-- modules/
| `-- operations/
|-- docker-compose.yml
|-- Dockerfile
|-- package.json
`-- scalar.config.json
Feature modules live under src/modules/<module>/ and generally follow this shape:
<module>.route.ts
<module>.controller.ts
<module>.service.ts
<module>.repository.ts
<module>.schema.ts
<module>.types.ts
<module>.constants.ts
index.ts
Routes define the HTTP shape, controllers coordinate input/output, services own business rules, repositories own Prisma queries, and schemas own Zod validation.
The repository includes Docker and Compose support for running the application container. The active Compose file runs app, worker, and redis; PostgreSQL is still expected to be provided externally.
Deployment expectations:
- Build a reproducible image from committed source and
bun.lock. - Provide runtime secrets through environment variables or a deployment env file.
- Run Prisma migrations explicitly before serving production traffic.
- Keep the async worker deployed as a separate long-running process from the HTTP app.
- Keep Redis durable enough for queue availability and let PostgreSQL outbox remain the source of truth for missed publishes.
- Keep upload storage outside the application artifact.
- Use
/health/livefor process liveness and/health/readyfor dependency readiness. - Keep staging and production databases, secrets, and CORS origins separate.
Compose commands:
bun run docker:compose:config
bun run docker:compose:pull
bun run docker:compose:up
bun run docker:compose:downBefore changing behavior:
- Read the relevant module doc in
docs/modules/**. - Keep module boundaries aligned with
docs/project-structure.md. - Update tests with the smallest coverage that proves the changed behavior.
- Update all related documentation in the same work item when adding a feature, fixing a bug, changing an env contract, or changing runtime/ops behavior.
- Update generated OpenAPI and route inventory when route contracts change.
- Update environment examples and
docs/environment.mdwhen variables change. - Update database docs and migration verification when Prisma schema changes.
Before opening a PR or handing off a change, run the relevant verification commands for the touched area and document any checks that require external credentials or a live database.