Turning Swagger Contracts into Production-Ready BFF Services
A generated-vs-custom-code boundary that survives contract changes
Problem
Every new backend-for-frontend service on UMTB's banking platform meant a developer rebuilding the same framework and application plumbing around a strict banking API contract: DTOs, controllers, and the scaffolding to wire them together, before writing a line of actual business logic. That repeated, mechanical work cost real time per service and left room for the generated plumbing to drift from the contract it was supposed to match.
Context
I joined after the initial basics of the BFF infrastructure were already in place, then took ownership of the majority of that layer, contributing roughly 70% of its codebase. The platform served banking flows with strict, contract-driven API definitions that changed over time as Swagger/OpenAPI specs evolved.
Constraints
- Contracts were strict and bank-defined; the generated code had to match the Swagger/OpenAPI spec exactly.
- Specs changed over time, and regenerating a service couldn't be allowed to destroy a developer's existing business logic.
- Developers needed an explicit, pattern-based way to mark which files, such as
CustomService,runLogic, and custom interceptors, had to survive a regeneration untouched. - The platform had to keep producing working NestJS services, not just DTOs and stubs.
My Role
I took end-to-end ownership of the majority of the BFF infrastructure layer, contributing approximately 70% of its codebase: service generation, configuration, and compliance embedding. I built the platform that generates NestJS services from Swagger specs, including the regeneration flow and its regenerate.keep preservation policy that let a service move to a newer Swagger version without losing a developer's own code.
Architecture / Approach
Each generated service draws a clear line between contract-driven generated code and developer-owned custom code. DTOs and controllers are generated directly from the Swagger/OpenAPI specification, giving every service a structure that matches its contract by construction. When a newer Swagger version became available, for example moving from 1.0.0 to 1.0.1, a developer ran the platform's standard regeneration flow rather than hand-patching the service. Regeneration reads a regenerate.keep file that defines, by pattern, which developer-owned files, CustomService, runLogic, and custom interceptors among them, must survive untouched; those files are copied forward as-is into the newly generated service. Everything outside that preservation boundary, generated DTOs and controllers, consumed Swagger definitions, infrastructure templates, and dependency configuration such as package.json when it wasn't listed in regenerate.keep, was regenerated from the latest platform sources. A small React frontend sat alongside the generation platform, giving developers a lightweight interface for triggering generation and regeneration rather than working through the underlying tooling directly.
Key Decisions
- A pattern-based preservation boundary, not an ad-hoc diff.
regenerate.keepgave the platform an explicit list of developer-owned files and patterns,CustomService,runLogic, and interceptors among them, to carry forward untouched, instead of trying to diff arbitrary changes across an entire generated service. - Developer-initiated regeneration over hand-patching. Moving a service to a newer Swagger version meant running the standard regeneration flow rather than manually editing generated files in place, which kept the generated layer honest to the latest contract instead of quietly drifting from it.
- Preserving interceptors as their own pattern in
regenerate.keep. Custom interceptors were listed alongsideCustomService, so cross-cutting developer logic didn't get treated as disposable just because it wasn'trunLogic.
Trade-offs
Generating DTOs, controllers, and the contract-driven structure removed the repeated plumbing work, but it meant the platform, not the individual developer, now owned getting that generation right for every contract shape the bank's Swagger specs could produce. Maintaining a pattern-based preservation policy in regenerate.keep added real complexity to the regeneration flow itself, complexity that paid for itself: a developer could run a regeneration against a newer Swagger version and trust that only what fell outside the preservation boundary would actually change.
Impact
New BFF service creation dropped from roughly 4 days to under 4 hours, a reduction of about 90%. Developers kept their runLogic implementation and custom interceptors intact across spec regenerations instead of rewriting them whenever a contract changed, and the BFF infrastructure layer became a platform other engineers built new services on top of rather than a pattern each team reimplemented from scratch.
Lessons
Reusable patterns are worth the up-front investment when they remove real, repeated accidental complexity, not just theoretical duplication. Generated systems need a protected boundary for human-owned code, or every contract change turns into a manual reconciliation exercise. Building this platform was an early, concrete step toward thinking in terms of templates and reusable platform pieces instead of one-off services.