NestJS in Production · Capstone
Capstone: A Subscriptions Service in NestJS
Design a NestJS service for subscriptions and invoices: module boundaries, validation, auth, transactions, testing and operations.
Scenario
A SaaS company is extracting billing from its monolith into a NestJS service. It manages plans, customer subscriptions (upgrade, downgrade, cancel) and monthly invoices, and receives webhooks from a payment provider. Other services call it over HTTP; finance staff use an admin UI.
Requirements
- Subscription changes and their invoice adjustments are consistent.
- Webhooks can arrive twice or out of order.
- Finance staff can see all customers; customers only their own data.
- Errors are consistent and documented.
Constraints
- NestJS 12, PostgreSQL, Redis available; deployed as 3 replicas on Kubernetes.
- The payment provider occasionally times out.
Deliverables
- Module structure and dependency rules.
- Request pipeline: validation, auth, errors, interceptors.
- Data and consistency: transactions, idempotent webhooks, outbox.
- Testing.
- Operations: configuration, context, logging, health, shutdown.
Hints
- Payment calls and database transactions shouldn't be mixed.
- Webhooks need idempotency keys.
Draft each deliverable below before revealing the model answer and rubric.
Further reading: NestJS documentation · Transactional outbox
Deliverables
Describe the module structure.
Model answer
PlansModule,SubscriptionsModule,InvoicesModule,WebhooksModule, plus infrastructure:ConfigModule(global, validated),DatabaseModule(transactions),PaymentsModule(gateway behind a token),AuthModule.- Dependencies point inwards: Subscriptions uses Invoices and Payments through exported services; no circular imports; Webhooks calls Subscriptions' service API, not its repositories.
Rubric
Look for feature modules with explicit exports, infrastructure modules, a gateway token for the payment provider and no circular dependencies.
Describe the request pipeline.
Model answer
- Global ValidationPipe (whitelist, forbidNonWhitelisted, transform) with DTOs per endpoint.
- Global AuthGuard (JWT for customers and staff, service tokens for internal callers) with
@Public()for the webhook route (verified by signature instead), and a RolesGuard; services enforce ownership for customers. - Domain errors mapped by one global filter to a documented format; interceptors for request logging, metrics and timeouts.
Rubric
Strong answers combine global validation, secure-by-default guards with explicit public routes, ownership checks and a single error format.
Describe data and consistency.
Model answer
- A subscription change and its invoice adjustment are written in one transaction, together with an outbox row describing the payment action.
- A worker reads the outbox and calls the payment provider with an idempotency key; retries with backoff on timeouts.
- Webhooks are stored by provider event ID with a unique constraint (duplicates ignored), and applied only if newer than the subscription's last processed event.
Rubric
Look for transactions without external calls, an outbox with idempotent payment calls, and webhook deduplication plus ordering.
Describe the testing approach.
Model answer
- Unit tests of services with the testing module and a fake payment gateway.
- End-to-end tests with createNestApplication, the shared configureApp(), and a real PostgreSQL in a container; cases for duplicate and out-of-order webhooks and authorisation boundaries.
- Contract tests for the API consumed by other services.
Rubric
Strong answers test the full pipeline against a real database and cover the hard cases: duplicate webhooks, ordering, and access control.
Describe operations.
Model answer
- Configuration validated at startup; secrets from the platform.
- AsyncLocalStorage request context (request ID, caller, tenant) used by the logger; JSON logs.
- Health endpoints (database, Redis) for readiness; enableShutdownHooks so workers finish jobs and pools close on SIGTERM within the grace period.
Rubric
Look for validated configuration, cheap request context, structured logs, readiness checks and clean shutdown.
Open in the app to mark lessons complete, take notes and track your progress.