A production-style TypeScript backend that acts as the source of truth for notification preferences, quiet hours, regional policy overrides, and delivery evaluation.
- Node.js 22
- TypeScript in strict mode
- Fastify
- Prisma ORM
- PostgreSQL
- Zod
- Luxon
- Pino
- Vitest
- Docker Compose
- OpenAPI Swagger UI
The service follows clean architecture:
src/domain: domain enums, entities, errors, notification classification, quiet-hours rulessrc/application: use-case services and repository portssrc/infrastructure: Prisma client, mappers, repository adapterssrc/api: Fastify app, routes, validation, error handling, composition rootsrc/shared: logger and reference datasrc/tests: unit and API tests with in-memory repositories
Application services depend on repository interfaces. Prisma is isolated in infrastructure so business rules can be tested without a database.
Create .env from .env.example:
cp .env.example .envRequired values:
DATABASE_URL="postgresql://user:password@localhost:5432/notification_preferences"
PORT=3000
NODE_ENV=development
LOG_LEVEL=infoOptional Docker Compose values:
POSTGRES_USER=user
POSTGRES_PASSWORD=password
POSTGRES_DB=notification_preferences
POSTGRES_PORT=5432
HOST=0.0.0.0Keep real credentials only in .env. The file is ignored by git.
Install dependencies:
npm installGenerate Prisma Client:
npx prisma generateApply migrations:
npx prisma migrate devSeed demo data:
npm run seedRun the API:
npm run devThe service starts at http://localhost:3000.
Swagger UI is available at http://localhost:3000/docs.
Set the Docker values in .env, then run:
docker compose up --buildThe app container runs Prisma migrations before starting.
npm run build
npm run typecheck
npm test
npm run prisma:generate
npm run prisma:migrate
npm run prisma:deploy
npm run seedcurl http://localhost:3000/healthcurl http://localhost:3000/users/user-1/preferencesReturns default preferences merged with user-specific overrides and quiet hours.
curl -X POST http://localhost:3000/users/user-1/preferences \
-H "Content-Type: application/json" \
-d '{"notificationType":"marketing_email","channel":"email","enabled":false}'curl -X POST http://localhost:3000/users/user-1/preferences \
-H "Content-Type: application/json" \
-d '{"quietHours":{"start":"22:00","end":"08:00","timezone":"Europe/Berlin"}}'curl -X POST http://localhost:3000/evaluate \
-H "Content-Type: application/json" \
-d '{"userId":"user-1","notificationType":"marketing_email","channel":"email","region":"EU","datetime":"2026-05-21T21:30:00Z"}'Example response:
{
"decision": "deny",
"reason": "blocked_by_quiet_hours"
}Evaluation priority:
- Global policy
- User preference
- Quiet hours
- Default preference
Global policies override all lower-priority rules. Quiet hours block marketing notifications only; transactional notifications continue to default or policy evaluation. Preference and quiet-hours updates are idempotent.
npm testCoverage includes defaults for new users, user preference overrides, quiet-hours evaluation, global policy blocking, idempotency, and API validation.
- Add authentication and authorization.
- Add admin endpoints for default preferences and global policies.
- Add audit tables for preference and policy changes.
- Add request IDs, metrics, and distributed tracing.
- Add rate limits and abuse protection.
- Publish preference-change events.
- Add CI checks for build, typecheck, tests, Prisma validation, and Docker build.