Skip to main content
Bloom is a client/server monorepo. The four core packages each own a distinct layer of the system, and two shared UI libraries are consumed across the frontend applications.

High-level structure

bloom/                          # Yarn workspace root
├── api/                        # NestJS backend            :3100
│   ├── src/
│   │   ├── controllers/        # Route handlers
│   │   ├── services/           # Business logic + Prisma calls
│   │   ├── dtos/               # Data Transfer Objects (class-validator)
│   │   ├── modules/            # NestJS module wiring
│   │   ├── guards/             # Auth + permission guards (Casbin)
│   │   ├── passports/          # Passport JWT/MFA strategies
│   │   ├── decorators/         # Custom NestJS decorators
│   │   └── enums/              # API-level enums
│   └── prisma/
│       ├── schema.prisma       # Single source of truth for DB schema
│       └── migrations/         # Versioned migration SQL files
├── sites/
│   ├── public/                 # Applicant-facing Next.js app  :3000
│   └── partners/               # Admin Next.js app             :3001
└── shared-helpers/             # Shared types, functions, components

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

HTTP Request


Passport Guard          # jwt.guard.ts / mfa.guard.ts / optional.guard.ts
    │                   # Verifies JWT cookie or MFA credentials

Permission Guard        # permission.guard.ts (Casbin)
    │                   # Verifies requester has access to resource + action

Controller              # src/controllers/*.controller.ts
    │                   # Validates input via DTOs (class-validator)

Service                 # src/services/*.service.ts
    │                   # Business logic

Prisma Client           # Generated from prisma/schema.prisma


PostgreSQL (bloom_prisma database)

Key backend directories

DirectoryPurpose
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.prismaPrisma 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=5 digits, valid for MFA_CODE_VALID=60000 ms by default).
Permission enforcement is handled by Casbin, a policy-based access control library. Anonymous users can, for example, submit applications but cannot create listings.

Swagger / OpenAPI

Swagger UI is auto-generated by NestJS and available at:
  • Local: http://localhost:3100/api
  • Any environment: append /api to the API’s base URL
The spec is derived from the DTOs and controller decorators — no separate spec file to maintain.

Cron jobs

Several background jobs run on configurable cron schedules (set in api/.env):
VariableDefault schedulePurpose
LISTING_PROCESSING_CRON_STRING0 * * * *Process listing state changes
LOTTERY_PROCESSING_CRON_STRING0 * * * *Run lottery logic
LOTTERY_PUBLISH_PROCESSING_CRON_STRING58 23 * * *Auto-publish lottery results
PII_DELETION_CRON_STRING0 * * * *Delete expired applicant PII
USER_DELETION_CRON_STRING0 * * * *Remove inactive users
DUPLICATES_PROCESSING_CRON_STRING15 * * * *Detect duplicate applications

External UI packages

PackageDescription
@bloom-housing/ui-seedsSeeds design system. React components and design tokens. The current, actively maintained library.
@bloom-housing/ui-componentsLegacy component library. Being incrementally replaced by ui-seeds.

Tech stack summary

LayerTechnologyNotes
Frontend frameworkNext.jsSSR + SSG; CACHE_REVALIDATE controls listing page cache TTL
Backend frameworkNestJSModular, decorator-driven
ORMPrismaSchema-first; migrations via prisma migrate dev
DatabasePostgreSQL 15Database name: bloom_prisma
LanguageTypeScript 4.9Shared across all packages
StylingSASS + @bloom-housing/ui-seedsCSS variables for design tokens
AuthPassport.js (JWT + MFA) + CasbinCookie-based JWT; Casbin for RBAC
EmailSendGrid (EMAIL_API_KEY)Transactional email
SMS / MFATwilioOTP delivery
Image hostingCloudinaryCLOUDINARY_CLOUD_NAME=exygy
ContainerizationDocker Composenginx 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 to main:
  1. API unit tests — Jest, mocked Prisma
  2. API integration tests — Jest, real PostgreSQL
  3. Public unit/integration tests — Jest + Testing Library
  4. Public Cypress tests — Full browser E2E
  5. Partners unit/integration tests — Jest + Testing Library
  6. Partners Cypress tests — Full browser E2E
Workflow configuration lives in .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/, and sites/public/.

Build docs developers (and LLMs) love