Clean Node.js Architecture | Enterprise Node.js + TypeScript

Last updated Jun 6th, 2019
In this article, we'll look at the various different versions of the "clean architecture", simplify it, and understand how we can use it to write more flexible Node.js code with TypeScript.

Have you ever heard of the "clean architecture"?

Maybe you've heard it by a different name...

Clean Architecture, the Onion Architecture, Ports & Adapters, Hexagonal Architecture, the Layered Architecture, DCI (Data, Context and Interaction), etc.

They're all a little bit different in implementation, but for our understanding: they all mean the same thing.

Separation of concerns at the architectural level

I first discovered the term when I read "Clean Architecture" by Robert C. Martin (Uncle Bob) (which, despite some negative reviews is actually an incredible read and I highly recommend you check it out).

After reading his book and spending some time learning the SOLID principles, not only did I enjoy the fact that the flexibilty and testability of my code improved, but I became way more confident tackling complex software development problems with TypeScript and Node.js.

In this article, I'll cover:

  • How the clean architecture separates the concerns of your code
  • How it enables you to write testable code
  • How it also enables you to write flexible code

Understanding the Clean Architecture

Policy vs. Detail

When we're writing code, at any given time, we're either writing Policy or Detail.

Policy is when we're specifying what should happen, and when.

Policy is mostly concerned with the business-logic, rules and abstractions that exist in the domain that we're coding in.

Detail is when we specify how the policy happens.

Details actually enforce the policy. Details are implementations of the policy.

An easy way to figure out if the code you're writing is detail or policy is to ask yourself:

  • does this code enforce a rule about how something should work in my domain?
  • or does this code simply make something work

For that reason: frameworks (like Nest.js and Express.js), npm libraries (like lodash, RxJs or Redux) are details.


The ultimate goal of the Clean Architecture is to separate Policy vs. Detail at the architectural level.

So let's see what that looks like:

Layered Architecture

Those small half-circles are meant to signify writing interfaces (at the policy level) to be implemented by the detail level.

This diagram is a sort of simplification of all of the other diagrams I found. There's more than just these two layers (read "Organizing App Logic with the Clean Architecture" for a more detailed depiction). But for our understanding of the concept, its much easier to think about a clean architecture like this.

So what does this mean?

In one layer (domain) we have all of the important stuff: the entities, business logic, rules and events. This is the irreplaceable stuff in our software that we can't just swap out for another library or framework.

The other layer (infra) contains everything that actually spins up the code in the domain layer to execute.

You'll recall that this is the biggest challenge in MVC, figuring out what the "M" should do and how it does it. Well, this is it. The "M" = that Domain Layer.

Here's an illustration how a RESTful HTTP call might cause code to be executed across our entire architecture.

There's a pattern here with respect to the direction of dependencies.

The Dependency Rule

In Uncle Bob's book, he describes the dependency rule.

That rule specifies that something declared in an outer circle must not be mentioned in the code by an inner circle.

In other diagrams, there are many more layers. The rule still applies.

That means that code can only point inwards.

Domain Layer code can't depend on Infrastructure Layer code.

But Infrastructure Layer Code can depend on Domain Layer code (because it goes inwards).

When we follow this rule, we're essentially following the Dependency Inversion rule from the SOLID Principles.

Ports and Adapters way of thinking about Clean Architecture

The ports and adapters approach to thinking about this is that the interfaces and abstract classes are the Ports and the concrete classes (ie: the implementations) are the adapters.

Let's visualize it.

Let's say I was to design an IEmailService interface. It specifies all of the things that an Email Service can do. But it doesn't actually implement any of those things specifically.

export interface IEmailService {
  sendMail(mail: IMail): Promise<IMailTransmissionResult>; 

Here's my little Port.

And let's say I'm just wiring up some code that relies on an IEmailService.

class EmailNotificationsService implements IHandle<AccountVerifiedEvent> {
  private emailService: IEmailService;
  constructor (emailService: IEmailService) {

  private onAccountVerifiedEvent (event: AccountVerifiedEvent): void {
      from: '',
      message: "You're in, my dude"

Because I'm referring to policy, all that's left to do is to create the implementation (the details).

// We can define several valid implementations.
// This infra-layer code relies on the Domain layer email service.
class MailchimpEmailService implements IEmailService {
  sendMail(mail: IMail): Promise<IMailTransmissionResult> {
    // alg

class SendGridEmailService implements IEmailService {
  sendMail(mail: IMail): Promise<IMailTransmissionResult> {
    // alg

class MailgunEmailService implements IEmailService {
  sendMail(mail: IMail): Promise<IMailTransmissionResult> {
    // alg

When I go to hook this thing up, I have several options available now.

// index.js
import { EmailNotificationsService } from './services/EmailNotificationsService'
import { MailchimpEmailService } from './infra/services/MailchimpEmailService'
import { SendGridEmailService } from './infra/services/SendGridEmailService'
import { MailgunEmailService } from './infra/services/MailgunEmailService'

const mailChimpService = new MailchimpEmailService();
const sendGridService = new SendGridEmailService();
const mailgunService = new MailgunEmailService();

// I can pass in any of these since they are all valid IEmailServices
new EmailNotificationsService(mailChimpService) 

Look! The port fits the adapter ❤️.

Hopefully we're starting to see how this can make our code more testable and flexible.

Code is testable

If you follow the dependency rule, domain layer code has 0 dependencies.

You know what that means?

We can actually test it

Next time you're writing code, think about it like this...

Before you get too far along working on some classes, ask yourself: "can I mock what I just wrote?"

If you were following SOLID and referring to interfaces or abstract classes, the answer will be yes.

If you've been referring to concretions, it'll be considerably more challenging to write tests for it. This is caused by coupling.

Code is flexible

When we've separated the concerns between Policy and Detail, we create an explicit relationship that we know how to deal with.

If we change the policy, we might end up affecting the detail (since detail depends on policy).

But if we change the detail, it should never affect the policy because policy doesn't depend on detail.

This separation of concerns, combined with adhering to the SOLID principles makes changing code a lot easier.


The only way we can be certain about changing code is to have written tests for it.

Domain code is incredibly easy to test (since it has 0 dependencies) and refers to abstractions. We can really easily create mocks for things by implementing the interface with our mock classes.

Infrastructure layer code is a little bit more challenging (and slower) to test because it has dependencies (web servers, caches, key-value object stores like Redis, etc).

Too clean?

The more complex the software you're building, and the more robust it needs to be, the more you need to build into it, the mechanisms for flexibility.

For example: if you're writing a quick Node.js script to scrape a particular web page periodically so that you can automate something, don't spend too much time trying to make your code SOLID.

But, if you're building a web scraper that needs to know how to scrape all of the 100 most popular job boards in the world, then you might want to consider coding it for flexibility.

// Define the abstraction to implement the algorithms
abstract class BaseScraper {
  constructor () {
    this.puppeteer = new Puppeteer();
  abstract getNumberPages (): Promise<number>;
  abstract getJobTitle (): Promise<HTML>;
  abstract getJobDescription(): Promise<HTML>;
  abstract getJobPaymentDetails(): Promise<HMTL>;

// Implement the algorithms
class LinkedInScraper extends BaseScraper {
  constructor () {

  getNumberPages (): Promise<number> {
    // implement algorithm

  getJobTitle (): Promise<HTML> {
    // implement algorithm

  getJobDescription(): Promise<HTML> {
    // implement algorithm

  getJobPaymentDetails(): Promise<HMTL> {
    // implement algorithm

class GlassdoorScraper extends BaseScraper {
  constructor () {
  getNumberPages (): Promise<number> {
    // implement algorithm

  getJobTitle (): Promise<HTML> {
    // implement algorithm

  getJobDescription(): Promise<HTML> {
    // implement algorithm

  getJobPaymentDetails(): Promise<HMTL> {
    // implement algorithm


Liked this? Sing it loud and proud 👨‍🎤.


Commenting has been disabled for now. To ask questions and discuss this post, join the community.

4 years ago

Nice post. Thanks for sharing.

4 years ago

I read your post a long time ago and I enjoyed it. But after a few months I wanted to come back to the post, and the only thing/keyword I remembered was - Hexagonal Architecture.

However when I searched for this term in this post I couldn't find it... so i was consed..

It turns out you have a Typo: Hexigonal 

Khalil Stemmler
4 years ago

Damn, sorry mate! I fixed that.

4 years ago

Nice article, I'd love more detail about why mocking interfaces and abstract classes are easier than concrete classes - maybe an example?

4 years ago

Hi, I love your content.. but I have to say I find a little annoying that all your text is "boldish", it's dense to read. And because you can't use bold/strong to emphasize, you need to underline, but underlines look like a link so I keep trying to click your emphasized words thinking they ARE links. :)

Bold body text is a bad choice, it doesn't reflect the quality of the text and the ideas themselves.

Elton Santana
4 years ago

Hey Great article! Do you have a sample project of it?

tan nguyen
4 years ago

Great article! Do you have git for sample codes

4 years ago

Great post, thanks! One question:

”Infrastructure layer code is a little bit more challenging (and slower) to test because it has dependencies (web servers, caches, key-value object stores like Redis, etc).”

What’s considered to be the typical way to test infrastructure code? My concern is that this layer (as you pointed out) usually has dependencies.

Let’s suppose there’s an Express app which communicates with a database to (for the example’s sake) create entities. Without providing any details on the concrete implementation, how’d you test this scenario? Would you involve a library (e.g. `proxyquire`) to mock the imported dependences?

Thanks in advance.

Vincent Guo
4 years ago

Just follow you and begin learning DDD from your post. thanks for your sharing

3 years ago

Excellent article!

2 years ago

Amazing and clear explanation! Would great a little app as example.

Blake Browns
2 years ago

Highly informative blog. The topics have really enhanced my interest level and I will be keeping a close eye on the upcoming scheduled Webinars. Previously I was following the blogs presented by online research paper help, their topics were also interesting and based on the current trends.

Stay in touch!

About the author

Khalil Stemmler,
Software Essentialist ⚡

I'm Khalil. I turn code-first developers into confident crafters without having to buy, read & digest hundreds of complex programming books. Using Software Essentialism, my philosophy of software design, I coach developers through boredom, impostor syndrome, and a lack of direction to master software design and architecture. Mastery though, is not the end goal. It is merely a step towards your Inward Pull.

View more in Enterprise Node + TypeScript

You may also enjoy...

A few more related articles

Knowing When CRUD & MVC Isn't Enough | Enterprise Node.js + TypeScript
MVC the granddaddy of application architectures. In this article, we explore common MVC patterns, the responsibilities of the "M"-...
Use DTOs to Enforce a Layer of Indirection | Node.js w/ TypeScript
DTOs help you create a more stable RESTful API; they protect your API clients from changes made on the server.
Why Event-Based Systems? | Enterprise Node.js + TypeScript
There are so many reasons why event-based systems are the standard for large-scale enterprise applications like GitHub, Facebook, ...
Functional Error Handling with Express.js and DDD | Enterprise Node.js + TypeScript
How to expressively represent (database, validation and unexpected) errors as domain concepts using functional programming concept...

Want to be notified when new content comes out?

Join 15000+ other Software Essentialists learning how to master The Essentials of software design and architecture.

Get updates