Node.js app in the real world : structure, dependency injection, events

This is Part 1 of a 5-part series.

Part 0 was the introduction, now let’s move to our first real world questions about our REST API written in JavaScript under Node.js with a Koa server, a Mongodb database and the Mongoose ODM.

Let’s get cleanly organized ! Photo by Gabriel Beaudry on Unsplash

How to organize code, name files and write simple, readable, intuitive code, under good coding guidelines ?

Folder structure

I talked about using the Domain-Driven Design (DDD) approach, or at least be inspired by it. This means first our code will be organized into 4 layers :

  • The domain layer, containing our entity classes and their associated business logic,
  • The app layer, containing our application services : it will be as thin as possible,
  • The infrastructure layer, containing the concrete implementations needed by our app (data access, encryption, logging… you name it),
  • The presentation layer, containing our server code, routes and controllers for the HTTP interface, and our commands for the CLI interface.

Folder structure will reflect this approach :

config/
dist/
docs/
logs/
src/
app/
content/
listeners/
user/
utils/
app.js
domain/
infra/
database/
encryption/
logger/
utils/
interfaces/
http/
api/
auth/
cache/
errors/
logger/
routes/
server.js
console/
commands/
cli.js
container.js
index.js
test/

Of course you could also organize your code into modules first, then secondly into layers, as micro-services of sorts. But for simplicity we’ll use a single module here.

index.js will be our main entry point ; container.js will be our service container (see next part) and the app layer will be organized into modules (here is shown a module for managing content, another for managing users), with a main file called app.js.

index.js will have the following, simple code (notice how we’re using a container to get what we want) :

src/index.js

More on the container (and its cradle) as well as on coding style and conventions later, don’t worry.

app.js (the app service from our container) will have the following, simple code (we are again using dependency injection here) :

src/app/app.js

Our db service can be anything at this point. Our server service too. They just need to implement certain methods and have certain properties (such as logger). That’s the beauty of it.

Now why do we need to inject our container in the server service’s start() method ? Because our server code, in order to get all the other modularized services, will need the same container that the one we fetched our app service from (more on this in Part 2) and we only want to import the container at one place and preferably at the highest level.

Naming files (and classes)

In a typical app module, I recommend the following :

user/
create.js
get.js
remove.js
search.js
update.js

Which will map to the following classes in our user module :

create.js: class CreateUser {}get.js: class GetUser {}remove.js: class RemoveUser {}search.js: class SearchUser {}update.js: class UpdateUser {}

Of course we could go for a single ManageUser class, but it will get bloated quickly, and remember, we’ll try to adhere to the Single Responsibility Principle (SRP) as much as possible (while not being rigorist about it though). This means functions and classes should ideally do one thing (or have one responsability to be more precise), not several.

But this doesn’t mean will never create a (simplified) ManageUser class, and with good reasons to do so, as we’ll see a bit later.

That makes a lot of files, yes, but really that’s OK. It will be better for unit testing too.

Notice how I use verbs (and CreateUser instead of UserCreator for instance). This is to some extent a matter of personal preference certainly, but it also the advantage of being universally understood and easily replicable for better coherence.

Coding style

You’ll notice I adopt coding guidelines greatly inspired by the ones you’ll see in Vue projects. “Traditional” coding style will probably recommend the use of semicolons and not promote putting a space between a function definition and its parenthesis.

I myself was reluctant at first about dropping semicolons but this convinced me, and I now consider they only bloat JavaScript code unnecessarily. Of course put them back in if you prefer. As for the extra space after function definition, it might be purely cosmetic but has a right feel to it somehow.

To be thorough, here are some Eslint rules that I recommend :

"indent": ["error", 2, { "SwitchCase": 1 } ],
"linebreak-style": ["error", "unix"],
"comma-spacing": ["error", { "before": false, "after": true }],
"space-before-function-paren": ["error", "always"],
"no-multi-spaces": 2,
"no-trailing-spaces": 2,
"quotes": ["error", "single"],
"semi": ["error", "never"],
"one-var": ["error", "never"],
"no-return-await": 2

Otherwise just follow responsible coding guidelines, get inspiration here and there and pick what suits you best.

Among questions that may arise often when writing JavaScript are :

1. Should I write :

{ one, two }

or

{
one,
two
}

but if we choose the latter, should I write :

{ one }

or

{
one
}

and ultimately the real question is how many object properties before inserting line breaks ?

Again, use your best judgement here. You could go for inline up to two (or even only one) properties and inserting line break after that. Just be consistent.

2. Should I insert a line break after an arrow (=>) or when writing ternary operations (? :). Again, I find it very situational but you could follow a definite rule for those too.

You’ll want to write clean code, so for further reading have of course a look at this excellent reference book (examples are not in JavaScript but everything is still relevant). In short, choose simple, descriptive variable names, keep functions short (SRP again), write meaningul comments only when needed (more on this in Part 4) and follow good architectural principles when it comes to abstraction, as we are trying to do here.

How to minimize dependencies by implementing a service container for dependency injection ?

We’ll use the excellent Awilix library and create a service container responsible for instantiating our classes (with the asClass() method) or provide already instantiated services as is (with the asValue() method). That way we will have a mechanism to inject our needed app dependencies without having them hard coded in our app layer.

We’ll have our app services resolved everytime whereas other services will be cached (with the singleton() method).

The following container.js code is thus pretty straighforward :

src/container.js

Every time we’ll need a service from our container, will simply use (where cradle is in fact a JavaScript Proxy) :

container.cradle[serviceName]

When and how to use events ?

Let’s recap what we’ve done so far : we have created a service container and an entry point for our app. We have devised a folder structure and set up some coding guidelines. Now we have to write our actual app and of course take care of the infrastructure and presentation layer, including the server.

Our application layer will rely solely on a domain layer and dependency injection. Meaning, I insist, there will be zero hard dependencies, apart from the domain, in all app services. This is very good news for code re-use.

To further decouple, we’ll use events to communicate between different app services. Let’s say for instance that when creating a user, we need to update another one (to grant him a privilege because he invited the new user for instance). We’ll call this updated user the sponsor.

To do that, we’ll emit an event in our CreateUser service which could go broadly like this :

src/app/user/create.js

Taking advantage of Node.js EventEmitter class, from which our service is now inheriting from (and in fact we’ll make all our services that way).

Now what we’ve done is emitting an event, but we have also to listen to it of course. And because we’ll not react to the event in the same service, we’ll need a mechanism to tie them together, but we’ll want this mechanism to be entirely decoupled from both the service emitting and the service reacting (here, UpdateUser).

That’s why we’ll be implementing an event listener class, UserListener, which will attribute a new badge to the sponsor’s badges array (of course in our domain model a User entity will need to have a badges array property) :

src/app/listeners/userListener.js

And finally, in our controller, we can write code like this :

When our event will be fired, it will then be listened to and reacted upon accordingly.

Notice that we have to bind the event handler function to its instance to make sure the latter works properly, with the right this context. This is due to how JavaScript works. In Part 5 we’ll see how to make use of an @autobind decorator in our listener class function to get rid of this bind() call and not only write cleaner code, but also gain much more flexibility in the process.

All is well so far and as you can see, our code is not fancy at all. In fact, it is dead simple. But also highly expressive and reusable.

Yet something is not quite right, don’t you think ?

Why should we delegate event listening to the controller (even if our listener is in the app layer) ? Shouldn’t the whole process be in the app layer ? And indeed if feels more adequate to do so.

One way of coping with that problem is to write a tie-all class like this (oh, here comes back a — much simpler though — version of the previously dreadful ManageUser service) :

Then we’ll simply use ManageUser.createUser(…), etc. in our controller (see Part 2).

Of course the bind() calls are still terrible. We’ll really need to get rid of them one way or another in Part 5.

Now do we need to use events everytime we have to pass data from one app service to another ? The answer is no. If operating on an entity has consequences on a joined entity, there are other ways to propagate the effect, either at ORM level (meaning in the infrastructure layer, we’ll see that in Part 2), or simply by injecting the joined entity’s repository into our main entity’s service.

Events are only really needed, as a rule of thumb, when you can’t foresee how many side effects involving other services there will be. Or if they are too many of them (use your best judgement here, but keep in mind the two magic words : decoupling and simplicity).

Now you may have heard of event sourcing which is a much more sophisticated way (and one easy to mess up with) of using events. It often goes with a Command Query/Read Segregation (CQRS) approach, which we are not taking here, so we‘ll leave this aside in this series.

In Part 2, we’ll dive into how to implement the repository pattern, separate domain classes from database models and add some automated persistence logic in accordance with the domain. We will also talk about how to set up the HTTP server, write proper API routes and manage controllers efficiently. To be continued !

Founder of Iperiago, creator of content and technology. https://www.iperiago.com/enhttps://www.parisparcours.com/en

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store