In this lesson, you will learn how to decouple your business logic from UI rendering engines so your routes remain agnostic to whether you use EJS, Pug, or a modern component library.
The Problem: Hard-Coded Views
In a typical Node.js application, developers often bind their routes directly to a specific template engine. For example, calling res.render('index', data) forces that route to know that index is a specific file type (like .ejs) managed by a specific engine. If you decide to switch your frontend stack, you have to hunt down and update every route in your application—a maintenance nightmare.
The Solution: The View Adapter
To solve this, we apply the Adapter Pattern for Service APIs. Instead of calling the template engine directly in your route handler, you introduce a "View Service" or an "Adapter." The route only knows it needs to return a "Dashboard View"; it has no idea how that view is constructed.
Consider this decoupled approach:
// The Route Handler (Logic remains clean)
app.get('/dashboard', async (req, res) => {
const data = await userService.getStats(req.user.id);
// We delegate the rendering to a generic view adapter
const html = await viewAdapter.render('dashboard', data);
res.send(html);
});
// The View Adapter (The bridge)
const viewAdapter = {
render: (viewName, data) => {
// We can swap the engine here without touching the route
return engine.compile(viewName, data);
}
};
Why This Matters
By treating the template engine as an implementation detail behind a standardized interface, your business logic stays pure. The route handler is concerned only with what data to fetch and which logical view to display, not how the HTML string is generated. If you move from EJS to a React-based SSR (Server-Side Rendering) engine, you only update the viewAdapter logic. Your routes, controllers, and middleware remain completely untouched. This is the essence of Template Engine Decoupling: your application core remains stable even when your presentation technology evolves.
Key Takeaway: By wrapping your UI generation in an adapter, you protect your business logic from being tightly coupled to specific template engine syntax or dependencies.