A production-grade RESTful API for managing job postings, applications, and recruitment workflows. Built as a portfolio project targeting a Junior API Developer role.
| Resource | URL |
|---|---|
| Base URL | https://job-application-api-production.up.railway.app/api/v1 |
| Swagger UI | https://job-application-api-production.up.railway.app/api/v1/docs |
| API Overview section | [Swagger UI](https://job-application-api-production.up.railway.app/api/v1/docs) |
| Postman Collection | /postman/JobApplicationsAPI.postman_collection.json |
The API covers the full lifecycle of job recruitment:
- Employers post jobs and register webhook URLs to receive real-time status notifications
- Recruiters review applications and advance candidates through a status pipeline
- Applicants browse open jobs and submit applications
- Super Admins manage users and reassign roles
Every status change fires a signed webhook event to the employer — simultaneously notifying them while the applicant sees the update on their own dashboard.
| Feature | Implementation |
|---|---|
| JWT authentication | Access token (15 min) + refresh token (7 days) |
| Token blacklisting | Redis — invalidates tokens on logout before expiry |
| Four-role RBAC | super_admin · employer · recruiter · applicant |
| Webhook delivery | HMAC-SHA256 signed payloads, delivery logs, single retry |
| Rate limiting | Global (100 req/min) + strict auth routes (10 req/min) via Redis |
| Pagination & filtering | page, limit, title, location, status, postedAfter |
| Consistent response envelope | { data, meta, error } on every endpoint |
| Database migrations | TypeORM migrations — no synchronize: true |
| OpenAPI docs | Auto-generated Swagger UI from NestJS decorators |
NestJS + TypeScript — framework
PostgreSQL — primary database
TypeORM — ORM + migrations
Redis — rate limiting + token blacklist
Docker Compose — local development environment
Railway — deployment
Swagger / OpenAPI — API documentation
Jest — unit tests
- Node.js 20+
- Docker + Docker Compose
git clone https://github.com/richiekaroki/job-applications-api.git
cd job-applications-apicp .env.example .env
# Edit .env with your valuesdocker-compose up -dnpm run migration:runnpm run seednpm run start:devAPI is now running at http://localhost:3000/api/v1.
Swagger UI at http://localhost:3000/api/docs.
See .env.example for the full list. Key variables:
NODE_ENV=development
PORT=3000
DB_HOST=localhost
DB_PORT=5432
DB_USERNAME=jobapi
DB_PASSWORD=jobapi_pass
DB_NAME=job_applications
REDIS_HOST=localhost
REDIS_PORT=6379
JWT_SECRET=your_jwt_secret
JWT_EXPIRES_IN=15m
JWT_REFRESH_SECRET=your_refresh_secret
JWT_REFRESH_EXPIRES_IN=7d
WEBHOOK_SECRET=your_webhook_hmac_secretAll routes are prefixed /api/v1. Full documentation available in Swagger UI.
POST /auth/register — create account
POST /auth/login — returns access + refresh tokens
POST /auth/refresh — issue new access token
POST /auth/logout — blacklist token, revoke refresh
GET /jobs — list jobs (paginated + filtered) — public
GET /jobs/:id — single job — public
POST /jobs — create job — employer+
PATCH /jobs/:id — update job — employer+
DELETE /jobs/:id — delete job — employer+
POST /jobs/:id/apply — submit application — applicant
GET /applications — all applications — recruiter+
GET /applications/mine — own applications — applicant
PATCH /applications/:id/status — update status → fires webhook — recruiter+
POST /webhooks/register — save webhook URL — employer+
GET /webhooks/logs — delivery history — employer+
GET /admin/users — list all users — super_admin
PATCH /admin/users/:id/role — reassign role — super_admin
The API uses JWT with refresh token rotation and Redis-backed token blacklisting.
# 1. Register
POST /auth/register
{ "email": "user@example.com", "password": "secure_pass", "fullName": "Jane Doe", "role": "applicant" }
# 2. Login — returns access_token and refresh_token
POST /auth/login
{ "email": "user@example.com", "password": "secure_pass" }
# 3. Use the access token on protected routes
Authorization: Bearer <access_token>
# 4. Refresh when the access token expires (15 min)
POST /auth/refresh
{ "refreshToken": "<refresh_token>" }
# 5. Logout — token is blacklisted in Redis immediately
POST /auth/logoutEvery endpoint returns the same envelope shape:
// Success
{
"data": { ... },
"meta": { "page": 1, "limit": 10, "total": 84, "totalPages": 9 },
"error": null
}
// Error
{
"data": null,
"meta": null,
"error": {
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action.",
"statusCode": 403
}
}The code field is machine-readable for frontend switch() logic. The message is human-readable for toast notifications.
When a recruiter updates an application status, the employer receives a signed POST to their registered URL:
{
"event": "application.status_changed",
"applicationId": "uuid",
"jobId": "uuid",
"applicantId": "uuid",
"status": "shortlisted",
"timestamp": "2026-06-13T10:00:00Z"
}Every delivery includes an X-Webhook-Signature header for verification:
X-Webhook-Signature: sha256=<HMAC-SHA256(payload, WEBHOOK_SECRET)>
Register a webhook URL:
POST /webhooks/register
Authorization: Bearer <employer_token>
{ "webhookUrl": "https://your-server.com/hooks/jobs" }View delivery logs:
GET /webhooks/logs
Authorization: Bearer <employer_token>pending → reviewed → shortlisted → rejected
↘
hired
Only recruiter, employer, and super_admin roles can advance status. Every transition fires a webhook event.
# Unit tests
npm run test
# Test coverage
npm run test:covThe API is deployed on Railway with PostgreSQL and Redis as native plugins.
# Install Railway CLI
npm install -g @railway/cli
# Login and link project
railway login
railway link
# Deploy
railway upSee railway.toml for build and deploy configuration.
src/
auth/ ← JWT strategy, guards, refresh logic
users/ ← entity, service, admin controller
jobs/ ← CRUD, pagination, filters
applications/ ← apply, status update, RBAC
webhooks/ ← emitter, delivery, HMAC signing, logs
common/ ← decorators, guards, interceptors, filters
config/ ← env validation, DB/Redis config
app.module.ts
main.ts
migrations/
postman/
docker-compose.yml
railway.toml
.env.example
Full architecture decisions, schema, auth flow, webhook design, and frontend integration contract documented in:
Covers: tech stack rationale · RBAC matrix · database schema · all API routes · JWT + Redis auth flow · webhook HMAC signing · rate limiting · response envelope · error codes · filtering params · field naming convention · CORS config · folder structure · Docker Compose · environment variables · Next.js integration contract · 5-day build plan.
Richard Kabue Karoki
Backend / Full Stack Developer — Nairobi, Kenya
github.com/richiekaroki · linkedin.com/in/richard-karoki007