DEV Community

Mattia Carcione
Mattia Carcione

Posted on Edited on

What if you could change your HTTP server in Node.js without touching a single line of business logic?

What if you could change your HTTP server in Node.js without touching a single line of business logic?

In the world of backend development with TypeScript and Node.js, we are often used to tightly coupling our application logic to the HTTP frameworks we choose (whether it's Express, Fastify, or native configurations). If one day you want to change engines or migrate your infrastructure, the result is almost always the same: a nightmare of rewriting routes, adapting middleware, and refactoring controllers.

But what if we applied a rigorous, .NET-style architecture (clean, modular, and explicit) to the TypeScript ecosystem?

Here is how you can structure a full-stack application where the HTTP server becomes a simple interchangeable plugin.


The problem of coupling in Node.js monoliths

Often, business logic, validations, and data management end up mixed directly with the HTTP request and response objects of the framework in use. This makes the code rigid, hard to test, and heavily dependent on technological choices that might change over time.

The solution: Total separation between Core and Transport

By adopting the principles of Clean Architecture and Hexagonal Architecture, we can cleanly separate responsibilities:

  1. The Core Logic (core-logic): The beating heart of the application. It contains domain models, entities, CQRS handlers (commands and queries), validation rules (via Zod), and persistence with Drizzle ORM. This layer is completely agnostic of the transport protocol: it knows nothing about Node's IncomingMessage or Fastify's reply object.

  2. The Presentation & Transport Layer: A simple peripheral adapter that receives external requests and delegates them to the core.


Changing your HTTP server with a single plugin

The true strength of this approach is transport interchangeability.

If you want to switch from Node.js's native HTTP server to a high-performance framework like Fastify, you don't have to change a single line of your handlers, routes, or repositories. You just swap out the plugin registration in the server's bootstrap file:

  • Running with native Node.js HTTP (server-node):
import { GetXenoBuilder } from '@ecommerce/core-logic';
import { withNodeHttpServer } from '@xeno-js/node';

export async function bootstrap(mode: 'api' | 'worker') {
    const builder = GetXenoBuilder(mode);
    builder.addHttpServer(withNodeHttpServer()); // <--- Native Node HTTP
    return builder.build();
}

Enter fullscreen mode Exit fullscreen mode
  • Immediate switch to Fastify (server-fastify):
import { GetXenoBuilder } from '@ecommerce/core-logic';
import { withFastify } from '@xeno-js/fastify';

export async function bootstrap(mode: 'api' | 'worker') {
    const builder = GetXenoBuilder(mode);
    builder.addHttpServer(withFastify()); // <--- Fastify HTTP Adapter
    return builder.build();
}

Enter fullscreen mode Exit fullscreen mode

Both servers expose the exact same API contracts (http://localhost:3000/api/v1), allowing the isomorphic frontend application (built with Vue 3 and Pinia) to communicate seamlessly without noticing any underlying changes.


Explicit Dependency Injection & CQRS Without Magic

No magic decorators or implicit runtime scans. Dependency Injection is explicit: services are registered by clearly declaring their lifecycles (Singleton, Scoped, Transient) within the container.

Execution pipelines declaratively handle cross-cutting concerns:

  • Strict payload validation with Zod.

  • Distributed query caching with automatic pattern-matching invalidation.

  • Authorization policies and concurrency management.


Final Thoughts

Having the freedom to change your networking infrastructure (or even your database or authentication mechanism) by only modifying configuration at the composition root completely changes how we think about software maintainability in TypeScript.

You can explore the complete e-commerce example repository here:
👉 GitHub Repository: xeno-js/ecommerce-example

Have you ever tried completely decoupling your HTTP server from your business logic in Node.js? What is your experience with these architectures? Let me know in the comments!

Explore Xeno.JS

Xeno.JS is an open-source TypeScript framework focused on explicit dependency injection, CQRS, and transport-independent application architecture.

Top comments (0)