High-level structure
Packages
api
NestJS + Prisma + PostgreSQL. Handles all data persistence and business logic. Exposes a REST API consumed by both frontend sites. Auto-generates OpenAPI/Swagger documentation served at
/api on port 3100.Request lifecycle: HTTP request → Guard (auth/permission) → Controller → Service → Prisma → PostgreSQL → response.sites/public
Next.js applicant-facing portal on port 3000. Allows housing seekers to browse listings, submit applications via the Common Application (built and maintained by Bloom) or an external link, and manage their accounts.
sites/partners
Next.js admin dashboard on port 3001. Provides housing developers, property managers, and city/county employees with tools to create, edit, and publish listings; view, edit, and export applications; and manage lottery workflows. Login required.
shared-helpers
Workspace package consumed by both
sites/public and sites/partners. Contains shared TypeScript types, utility functions, and UI components, preventing duplication between the two portals.Backend (api) deep-dive
Request lifecycle
Key backend directories
| Directory | Purpose |
|---|---|
src/controllers/ | One controller per resource (e.g., listing.controller.ts). Follows NestJS conventions. |
src/services/ | Business logic and all Prisma database calls (e.g., listing.service.ts). |
src/dtos/ | Data Transfer Objects using class-validator and class-transformer. Controls request shape and response schema. |
src/modules/ | NestJS modules connecting controllers to services (e.g., listing.module.ts). |
src/guards/ | Passport guards (jwt.guard.ts, mfa.guard.ts, optional.guard.ts) and permission guards (permission.guard.ts). |
src/passports/ | Passport strategies that implement the actual credential verification logic. |
src/decorators/ | Custom NestJS parameter and class decorators. |
src/enums/ | API-level TypeScript enums (distinct from Prisma-managed database enums). |
prisma/schema.prisma | Prisma schema: models, relations, enums, and DB connection config. |
prisma/migrations/ | Versioned SQL migration files generated by prisma migrate dev. |
Authentication
The API uses two Passport strategies:- JWT (
jwt.strategy.ts) — Reads a JWT from the request cookie and resolves the user. - MFA (
mfa.strategy.ts) — Validates email, password, and a time-limited MFA code (MFA_CODE_LENGTH=5digits, valid forMFA_CODE_VALID=60000ms by default).
Swagger / OpenAPI
Swagger UI is auto-generated by NestJS and available at:- Local:
http://localhost:3100/api - Any environment: append
/apito the API’s base URL
Cron jobs
Several background jobs run on configurable cron schedules (set inapi/.env):
| Variable | Default schedule | Purpose |
|---|---|---|
LISTING_PROCESSING_CRON_STRING | 0 * * * * | Process listing state changes |
LOTTERY_PROCESSING_CRON_STRING | 0 * * * * | Run lottery logic |
LOTTERY_PUBLISH_PROCESSING_CRON_STRING | 58 23 * * * | Auto-publish lottery results |
PII_DELETION_CRON_STRING | 0 * * * * | Delete expired applicant PII |
USER_DELETION_CRON_STRING | 0 * * * * | Remove inactive users |
DUPLICATES_PROCESSING_CRON_STRING | 15 * * * * | Detect duplicate applications |
External UI packages
| Package | Description |
|---|---|
@bloom-housing/ui-seeds | Seeds design system. React components and design tokens. The current, actively maintained library. |
@bloom-housing/ui-components | Legacy component library. Being incrementally replaced by ui-seeds. |
Tech stack summary
| Layer | Technology | Notes |
|---|---|---|
| Frontend framework | Next.js | SSR + SSG; CACHE_REVALIDATE controls listing page cache TTL |
| Backend framework | NestJS | Modular, decorator-driven |
| ORM | Prisma | Schema-first; migrations via prisma migrate dev |
| Database | PostgreSQL 15 | Database name: bloom_prisma |
| Language | TypeScript 4.9 | Shared across all packages |
| Styling | SASS + @bloom-housing/ui-seeds | CSS variables for design tokens |
| Auth | Passport.js (JWT + MFA) + Casbin | Cookie-based JWT; Casbin for RBAC |
SendGrid (EMAIL_API_KEY) | Transactional email | |
| SMS / MFA | Twilio | OTP delivery |
| Image hosting | Cloudinary | CLOUDINARY_CLOUD_NAME=exygy |
| Containerization | Docker Compose | nginx LB + db + api + public + partners |
Testing strategy
API unit tests
Located in
api/test/unit/ with .spec.ts extensions. Mock Prisma using Jest mocks — no real database required. Test individual service functions and business logic. Run with yarn test from api/.API integration tests
Located in
api/test/integration/ with .e2e-spec.ts extensions. Use a real PostgreSQL database. Test the full controller-to-database path. Database starts empty and is cleaned up after each suite. Run with yarn test:e2e from api/.Frontend unit tests
Jest + Testing Library suites inside
sites/public and sites/partners. Run with yarn test:unit or yarn test:unit:coverage from within each site directory.Cypress E2E tests
End-to-end browser tests for both
sites/public and sites/partners. Requires the application to already be running. Run with yarn test from within each site directory.API test coverage benchmarks are enforced in CI. Both unit and integration test runs contribute to the coverage report. Run
yarn test:cov from api/ to generate a coverage report locally.CI/CD
GitHub Actions runs the following jobs on every pull request tomain:
- API unit tests — Jest, mocked Prisma
- API integration tests — Jest, real PostgreSQL
- Public unit/integration tests — Jest + Testing Library
- Public Cypress tests — Full browser E2E
- Partners unit/integration tests — Jest + Testing Library
- Partners Cypress tests — Full browser E2E
.github/workflows/. Cypress test recordings can be enabled by setting record: true in the respective workflow YAML and viewed in Cypress Cloud.
Additional automated checks:
- GitLeaks — Scans all PRs for accidentally committed secrets.
- Dependabot — Weekly dependency version and security PRs across
api/,shared-helpers/,sites/partners/, andsites/public/.