[01]Case study
Low-Code Rendering Engine
A senior architect built the graph engine. I built the rendering layer that made it usable — the part that turns configuration into interactive UI. Then I led the delivery when it sold into a global payments company, and supported its reuse for an emergency NHS deployment.
- Role
- Renderer developer, then tech lead (12 devs)
- Period
- 2019 - 2021
- Stack
- JavaScript, Angular, React, Vue, Node.js
[02]The problem
A powerful engine with no face.
The core graph engine could represent user journeys as directed graphs — nodes for activities, edges for transitions, declarative validation rules. But it was a library, not a product. Someone needed to build the layer that takes a graph node's JSON schema and turns it into actual UI: dynamic component generation, recursive rendering for nested structures, validation feedback, state binding, and a packaging model that other developers could actually consume.
That was the gap. The engine could think. It couldn't speak.
[03]The journey
From spare time to three production deployments.
This wasn't a planned product. It started with spare capacity and a senior architect's prototype, evolved through my thesis research, got battle-tested at a payments company, and proved its portability under the most extreme deadline I've worked to.
[2019]Side-of-desk start
While team-leading at a financial services client, I was introduced to a senior architect who had built a graph-based journey engine — a core library that could represent user journeys as directed graphs in JSON. What was missing was the rendering layer: the part that takes a graph node and turns it into interactive UI. I started building the Angular implementation in spare time.
[2019]The rendering problem
The core engine handled graph traversal, validation rules, and state management. My job was to make it visible: dynamically generating Angular components from JSON schemas, handling nested structures through recursive rendering, managing the binding between engine state and the DOM, and packaging it all as a consumable module other developers could drop into their projects.
[2020]Sold into a payments company
A working prototype was demoed to a global payments company. They bought in — the idea of accelerating their complex frontend delivery with configurable journeys instead of hand-coded forms was compelling enough to greenlight an engagement. I was brought in to lead the delivery.
[2020]Payments delivery
I led a team of 12 developers. My job was to integrate the platform with their existing application, upskill the team on the framework, and ship production features. The platform cut their frontend delivery estimate from a 2022 completion to 2020. Business analysts could now define journey logic without developer intervention for the common cases.
[2020 - 2021]NHS emergency deployment
Two weeks after offboarding from the payments engagement, the NHS needed an emergency MVP for the Covid test-and-trace centre finder. The platform was portable enough to reuse. I supported a two-week production deployment, adapted it for their requirements, and upskilled their junior developers to maintain it independently.
[2021]Bank of England
Moved on to define frontend architecture for a central bank portal — React, redux-sagas, atomic design. A different kind of problem, but the pattern recognition from building a rendering engine carried over: thinking about how configuration drives UI, how state flows through a system, how to make complex interactions manageable.
[04]What I built
Dynamic rendering, recursive components, and an override escape hatch.
[--]Dynamic component generation
The rendering implementation dynamically creates Angular components at runtime based on the node schema. A generator service reads field types (string, password, table, enum, object) and instantiates the corresponding component, binding data and validation state from the core engine in real-time.
[--]Recursive rendering
Nested schemas (objects containing objects) require components that can generate themselves. This creates circular dependencies in Angular's module system. I solved it with an abstract base class for DOM manipulation, a render interface for decoupling, and dependency injection — the default render service extends the base and implements the interface, so components reference the interface, not each other.
[--]Override system
Developers can override any default component type with their own implementation. The abstract render service exposes a createComponent method that maps schema types to components — swapping in a custom component means providing a new service that extends the base class with a different type mapping. This was critical for client adoption: teams could use defaults for 80% of fields and customise the rest.
[--]Packaging as a module
Started on Angular 2, restructured to Angular 8, and repackaged the whole renderer as a distributable Angular module. A consuming application just drops in a single tag with a workflow configuration. I documented the setup, override patterns, and styling customisation on the package README.
[--]Multi-framework rendering
The core graph engine was pure JavaScript with no framework dependency. I built the primary renderer in Angular, with implementations also created for React and Vue to prove framework-agnostic viability to different client teams during sales conversations.
[05]Honest critique
What worked, what didn't, and what I'd change.
Building something that ships is one thing. Being honest about its limitations is where the real learning lives. The platform worked — three production deployments prove that. But it has real architectural tradeoffs worth naming.
[--]Unit testing a DOM renderer
A rendering engine that directly manipulates the DOM at a low level is inherently difficult to unit test. You can run it server-side with a DOM shim, but the coupling between engine state and render output means unit tests give false confidence. A green test suite doesn't tell you the journey actually works. The real verification comes from E2E tests running complete user journeys. This is a genuine architectural characteristic that shifts where your testing investment needs to go.
[--]Abstraction vs. flexibility
The platform optimises for the common case: linear and branching form journeys with validation. It handles 80% of typical enterprise user journeys well. The remaining 20% — highly custom interactions, complex conditional rendering, unusual state patterns — require dropping down to the override system, which is functionally just writing a normal component. The abstraction helps until it doesn't, and knowing where that line is matters more than the framework itself.
[--]What I'd do differently
If I built this today, I'd use a typed schema language instead of raw JSON, invest more heavily in the E2E test infrastructure from day one rather than retrofitting it, and consider a server-driven UI approach where the graph evaluation happens server-side and the client is purely a renderer. The 2019 version was shaped by the constraints of the time — no budget, side-of-desk, Angular 2 as a starting point.
[06]What it taught me
The best product engineering happens at the seams.
This project shaped how I think about building software. The rendering layer sat between the graph engine's abstract model and the developer's concrete application — making one useful to the other. Getting it right meant understanding the engine's capabilities, the developers' constraints, and the end users' expectations simultaneously.
It also taught me that the most valuable work sometimes starts without a budget, a deadline, or a formal brief. Spare capacity and a good collaborator produced something that sold itself, shipped under pressure, and proved portable across three completely different contexts.