A two-sided job and freelance marketplace for people who want hiring software that behaves like a product, not a CRUD demo.
Candidates search crawlable listings, keep a resume history, apply once per job, and watch a real pipeline. Employers claim one company, post roles, and move applicants only along legal transitions. Admins suspend users, unpublish jobs, and clear the report queue.
Independent recruiters, early-stage operators, and portfolio reviewers who need evidence of RBAC, SSR search, file uploads, and a live deploy — not another todo app with a login screen.
flowchart LR
Browser["Angular 22 SSR<br/>hirestack-web"] -->|JSON + refresh cookie| API["NestJS Fluid Function<br/>hirestack-api"]
API --> Neon["Neon Postgres<br/>Prisma + pg_trgm"]
API --> Blob["Vercel Blob<br/>resumes + logos"]
API --> Mail["Resend<br/>or structured logs"]
One pnpm monorepo. Two Vercel projects. Shared enums live in packages/shared so the UI cannot invent a status the API does not understand.
Jobs are DRAFT | PUBLISHED | CLOSED. Applications are a state machine, not a free-text dropdown:
SUBMITTED → REVIEWING → INTERVIEW → OFFER → HIRED
SUBMITTED / REVIEWING / INTERVIEW → REJECTED
Candidate may WITHDRAWN from SUBMITTED or REVIEWING only
Illegal transitions return 409 with error: ILLEGAL_TRANSITION. Apply + first ApplicationEvent run in a Prisma transaction. Duplicate (jobId, candidateId) is rejected.
Search uses ILIKE plus pg_trgm GIN indexes on Job.title and Job.location. Relevance sort ranks trigram similarity when q is present.
| Choice | Why |
|---|---|
| Angular 22 | Signal Forms, httpResource, @Service(), zoneless + OnPush default, Angular Aria |
| NestJS on Vercel Fluid | One function, zero-config src/main.ts + PORT detection. API runtime is Node 22.x (Fluid Node 24 currently crashes this Nest boot). |
| Neon + Prisma adapter | Serverless pooling without a standing Node process |
| Vercel Blob | Serverless uploads; no local disk |
| Postgres search | Honest v1. Dedicated search is the upgrade, not the starting point |
corepack enable
pnpm install
cp .env.example apps/api/.env
# fill DATABASE_URL, DATABASE_URL_UNPOOLED, JWT secrets
pnpm prisma:migrate:dev
pnpm prisma:seed
pnpm dev- API: http://localhost:3000/api/health and http://localhost:3000/api/docs
- Web: http://localhost:4200 (
environment.development.tspoints at the local API)
Password for every seed user: HireStack!2026
| Role | |
|---|---|
| Admin | admin@hirestack.dev |
| Employer (Northwind Labs) | employer.northwind@hirestack.dev |
| Employer (Atlas Freight) | employer.atlas@hirestack.dev |
| Employer (Lumen Studio) | employer.lumen@hirestack.dev |
| Candidate | candidate.alex@hirestack.dev … candidate.quinn@hirestack.dev |
Seed load: 1 admin, 3 employers/companies, 8 candidates, 25 skills, 20 published jobs, 18 applications.
- Neon MCP — project
hirestack(divine-moon-46584975). Schema applied with Prisma migrate; trigram indexes inprisma/migrations/20260819160618_trgm_indexes. Seed verified throughrun_sql/inspect_database. - Wire
apps/api/.envwith the pooledDATABASE_URLand unpooledDATABASE_URL_UNPOOLED. - Vercel MCP / CLI — projects
hirestack-api(prj_xDMCF55ThMXgZyVqeKVmuht4br7n, rootapps/api) andhirestack-web(prj_vg09GADHx67h4aBvEsAgc5FpoUlZ, rootapps/web) live on teamcriscode2022s-projects. Cursor Origin git linking needs a Vercel Login Connection. There is no env-var MCP tool; set secrets withvercel env addafterVERCEL_TOKENis available:export VERCEL_ORG_ID=team_XDogXucjsiIPPOiJSxbMGbSc npx vercel env add DATABASE_URL production --yes npx vercel env add DATABASE_URL_UNPOOLED production --yes npx vercel env add JWT_ACCESS_SECRET production --yes npx vercel env add JWT_REFRESH_SECRET production --yes npx vercel env add BLOB_READ_WRITE_TOKEN production --yes npx vercel env add WEB_ORIGIN production --yes npx vercel --prod --yes - Deploy API first, set
WEB_ORIGINtohttps://hirestack-web.vercel.app, then deploy web. - Confirm
/api/healthreturns{ ok: true, db: true }and/api/docsloads.
pnpm test # API unit tests (no live database)
pnpm test:smoke # health, jobs, billing, seed login against local API + Neon
pnpm test:e2e # Playwright against http://localhost:4200
pnpm typecheckCI runs install, Prisma generate, unit tests, API lint/typecheck, and a development Angular build.
- Web (production): https://hirestack-web.vercel.app
- API (production): https://hirestack-api.vercel.app/api/health
- Swagger: https://hirestack-api.vercel.app/api/docs
- SaaS Git preview (this branch): https://hirestack-angular-web-git-cursor-81b4d2-criscode2022s-projects.vercel.app
- Neon project:
hirestack(divine-moon-46584975) - Vercel dashboards: hirestack-api · hirestack-web
Production Angular calls /api. On this branch apps/web/vercel.json rewrites that path to the Git preview Nest host until hirestack-api is promoted with billing. scripts/deploy-vercel.sh needs VERCEL_TOKEN. GitHub also deploys preview apps hirestack-nestjs-api and hirestack-angular-web; copy DATABASE_URL, JWT secrets, and WEB_ORIGIN to Preview or the Nest function boots without a database.
- SSR vs CSR. Public job list, job detail, company profile, and landing render on the server so listings stay crawlable. Authenticated inbox and forms stay client-rendered.
- Serverless Nest cold starts. One Fluid Function is operationally simple. The cost is a wake-up on idle. Prisma uses the Neon adapter so the function does not hold a brittle TCP pool.
- Postgres search vs dedicated search. Trigram +
ILIKEis enough for tens of thousands of jobs and keeps ranking next to relational filters. At marketplace scale, embeddings or OpenSearch would replace the relevance path only.
Stripe Checkout for live cards, custom domains, and embedding search. Messaging, featured inventory, and demo billing are in this build.
Private portfolio project.