SearchOps is a portfolio-grade job platform demo for SEO-ready public job pages, job search APIs, conversion tracking, A/B testing, observability, and automated verification.
Job marketplaces need crawlable job detail pages, reliable search/filter APIs, measurable apply funnels, and basic operational signals. This project demonstrates those concerns end to end with a TypeScript backend, a Next.js frontend, PostgreSQL data, Docker Compose, CI, Swagger/OpenAPI documentation, and Playwright E2E coverage.
Live demo: not deployed yet. Local Docker Compose is the supported demo path for now.
flowchart LR
browser[Browser or crawler] --> frontend[Next.js App Router frontend<br/>localhost:3000]
frontend --> api[Express TypeScript API<br/>localhost:5000]
api --> db[(PostgreSQL 15)]
api --> logs[Pino JSON logs]
api --> metrics[/GET /metrics/]
api --> docs[/GET /api-docs/]
ci[GitHub Actions CI] --> api
ci --> frontend
ci --> e2e[Playwright E2E]
| Area | Version / tool |
|---|---|
| Runtime | Node 20 (node:20-alpine in backend/frontend Dockerfiles, Node 20 in CI) |
| Backend | Express ^4.18.2, TypeScript ^5.1.3, Pino ^10.3.1 |
| Database | PostgreSQL 15-alpine, Prisma / @prisma/client ^6.19.3 |
| Frontend | Next.js 16.2.6, React 18.3.1, TypeScript 5.6.3 |
| Tests | Vitest backend ^0.34.4, Vitest frontend 1.6.1, Supertest ^6.3.3, Playwright ^1.60.0 |
| E2E runtime | mcr.microsoft.com/playwright:v1.60.0-noble via frontend-e2e service |
| Docs | OpenAPI 3.0.3, Swagger UI via /api-docs |
| Local orchestration | Docker Compose |
Backend APIs:
GET /healthGET /metricsGET /api/jobswith search, filters, sorting, and paginationGET /api/jobs/:slugPOST /api/eventsGET /api/analytics/summaryPOST /api/experiments/apply-cta-assignment
SEO:
- Server-rendered
/jobslisting and/jobs/[slug]detail pages - JobPosting JSON-LD
- Canonical and Open Graph metadata
sitemap.xmlandrobots.txt
Analytics and A/B testing:
- Job view, apply click, search, and filter event recording
- Stable anonymous apply CTA assignment
- Variant A:
Apply now - Variant B:
Start your application - Lightweight analytics summary and variant conversion calculation
Observability:
- Pino JSON request logs with request IDs and duration
- Prometheus-style
/metrics - HTTP request/error/duration metrics
- Local-only search latency simulation guarded by environment flags
Testing:
- Backend unit and integration tests
- Frontend rendering/SEO tests
- Playwright E2E journey from job listing to detail to apply CTA click
- GitHub Actions CI with PostgreSQL service container
Docker Compose is the supported local app demo path.
docker compose down -v
docker compose up -d postgres
docker compose build backend frontend
docker compose run --rm backend sh -lc "npx prisma generate && npx prisma migrate deploy && npx prisma db seed"
docker compose up --buildOpen:
- Frontend:
http://localhost:3000/jobs - API health:
http://localhost:5000/health - Metrics:
http://localhost:5000/metrics - Swagger UI:
http://localhost:5000/api-docs
docker compose up -d postgres
docker compose run --rm backend sh -lc "npx prisma generate && npx prisma migrate deploy && npx prisma db seed"
docker compose run --rm backend sh -lc "npm run lint && npm test && npm run build"
docker compose run --rm frontend sh -lc "npm run lint && npm test && npm run build"Recommended Docker E2E workflow:
docker compose up -d postgres
docker compose run --rm backend sh -lc "npx prisma generate && npx prisma migrate deploy && npx prisma db seed"
docker compose up -d backend frontend
docker compose build frontend-e2e
docker compose run --rm frontend-e2eOr run the E2E script explicitly inside the Playwright container:
docker compose run --rm frontend-e2e sh -lc "npm run test:e2e"Do not run Playwright E2E inside the normal Alpine frontend service. The dedicated frontend-e2e service uses Microsoft’s Playwright image with the required browser runtime.
Optional host-machine workflow, when Node 20 and Playwright dependencies are available locally:
cd frontend
npm ci
npx playwright install chromium
PLAYWRIGHT_BASE_URL=http://localhost:3000 npm run test:e2ePowerShell equivalent for the final command:
$env:PLAYWRIGHT_BASE_URL="http://localhost:3000"; npm run test:e2eA local curl smoke-check log is included as evidence:
docs/screenshots/API-check.txt
It verifies:
GET /healthGET /api/jobs?limit=3GET /api/jobs?q=backend&limit=3GET /api/jobs/backend-software-engineer-melbourne-seekPOST /api/experiments/apply-cta-assignmentPOST /api/eventsforJOB_VIEWPOST /api/eventsforAPPLY_CLICKGET /api/analytics/summary
The captured run ends with:
===== API SMOKE CHECK PASSED =====Recommended command if re-running locally:
./scripts/api-smoke-check.shIf the script is not present, the same checks can be reproduced with curl against http://localhost:5000.
GitHub Actions workflow: .github/workflows/ci.yml
It runs on pull requests and pushes to main with a clean PostgreSQL service container. The job performs:
backend npm ci
backend prisma generate
backend prisma migrate deploy
backend prisma db seed
backend lint
backend tests
backend build
frontend npm ci
frontend lint
frontend tests
frontend build
Playwright E2ENo passing CI result is claimed here until GitHub Actions has actually run on the repository.
Source contract: docs/openapi.yaml
When the backend is running:
- Swagger UI:
http://localhost:5000/api-docs - Raw YAML through backend:
http://localhost:5000/api-docs/openapi.yaml
/api-docs is developer documentation only, not a product API endpoint.
Screenshot and verification evidence is stored under docs/screenshots/.
| Evidence | File | Status |
|---|---|---|
/jobs listing page |
docs/screenshots/jobs-listing-page.png |
Captured |
/jobs/[slug] detail page |
docs/screenshots/jobs-[slug]-detail-page.png |
Captured |
| Apply CTA status after click | docs/screenshots/apply-cta-clicked.png |
Captured |
/metrics endpoint output |
docs/screenshots/metrics-endpoint.png |
Captured |
| Swagger UI overview | docs/screenshots/Swagger-UI-1.png |
Captured |
| Swagger UI schema/detail view | docs/screenshots/Swagger-UI-2.png |
Captured |
| Playwright E2E passing | docs/screenshots/playwright-e2e-passed.png |
Captured |
| curl API smoke check output | docs/screenshots/API-check.txt |
Captured |
| GitHub Actions passing CI | docs/screenshots/github-actions-ci-passed.png |
Captured |
Evidence checklist: docs/screenshots/README.md
Only real screenshots or real command outputs from a local run, CI run, or deployed environment should be committed. Generated placeholders, fake CI results, and fake deployment screenshots are intentionally excluded.
Postman collection: docs/postman/SearchOps.postman_collection.json
Postman is optional and is not part of CI. The committed curl smoke-check output is the current API verification evidence.
Backend:
PORTdefault5000DATABASE_URLPostgreSQL connection stringNODE_ENVLOG_LEVELSLOW_REQUEST_THRESHOLD_MSENABLE_INCIDENT_SIMULATION
Frontend:
BACKEND_API_URLdefaulthttp://localhost:5000FRONTEND_SITE_URLdefaulthttp://localhost:3000NEXT_TELEMETRY_DISABLED
E2E:
PLAYWRIGHT_BASE_URLdefaulthttp://localhost:3000, orhttp://frontend:3000inside Docker Compose
- No production deployment yet
- No authentication, authorization, admin dashboard, or content management UI
- No real application submission flow; apply click shows a demo status message
- No full-text search index or relevance ranking
- No production monitoring stack, alerting, tracing, log aggregation, or dashboards
- A/B testing is deterministic and local-friendly, not a full experimentation platform
- GitHub Actions passing screenshot is not included until a real PR/run passes on GitHub
- No fake benchmark results, deployment URLs, or CI results are included
- Deploy frontend, backend, and PostgreSQL to a real hosting target
- Add secret management and production environment variables
- Run migrations through a controlled deployment job
- Add rate limiting, CORS policy, authentication, and admin workflows
- Add managed logs, metrics scraping, dashboards, alerts, and tracing
- Add search indexing and relevance ranking
- Add analytics warehouse/event pipeline and experiment analysis
- Add backup/restore, disaster recovery, and operational runbooks