Lessons Learned from 809++ Days of Node.js in Microservices

Kristofer Sommestad
May 6, 2016 · 6 min read

Many of us at come from a long background of programming within Java and similar languages.
However, when building the cloud solution for our connected car products, we decided to go primarily with Javascript and .

The main reason was that we wanted to use a language that enabled us to focus on delivering end-user value as fast as possible.

Also, it did seem a bit adventurous and challenging at the time, since Node was still a fairly young platform to work with. Let alone for large-scale, enterprise-grade systems. But who doesn’t like a good challenge?

In February of 2014, 2+ years ago, we added the first server-side building blocks for our “platform” for connected car eco systems.

Today, we’ve got more than 50 Github repositories of Node.js (, but not necessarily usable for everyone) and are running 15+ microservice applications on Node.js.


Lessons learned

These are some of the lessons learned so far, in no particular order of importance.

Using a linter is essential.
A good linter can catch most of the oddities that have people hate Javascript. We really like using .

Use a shared and code style that makes sense to your team.
Using a linter also makes it easier to keep the code style consistent, e.g. with a .
Prefer well-adopted standards to avoid spending time on lengthy, useless discussions about tabs vs spaces.

Have high test coverage.
Due to Javascript’s loose nature, it’s even more important to have high coverage compared to other (typed) languages. There might be an `undefined` lurking around somewhere.
We strive towards ~90% in bigger modules and even higher coverage in smaller ones. We use and with for coverage.

Prefer unit tests over integration tests.
With Javascript, you’re used to short turn-around time, so even a mere couple of minutes for a big test suite feels like ages.
Unit tests are faster and cheaper to maintain. They also put pressure on your application design, since complicated code tend to require complicated unit tests.

Only test the public API of your files/modules.
Javascript makes it easy to reach into modules and poke around with “private” functions. Don’t. It makes it very expensive to do refactoring, which is key to a pleasant codebase.

Use mocking to isolate units under test.
Mocking is needed to test isolated parts of the code. We use to mock and spy dependencies (and `Date`) in unit tests.

Be careful when mocking.
Be careful with your mocks, to ensure the mock doesn’t return a false result. Javascript has no typing to help verify that the mock is correct.
Spend extra time in the code review to ensure mocks are correct.

Use .
There are lots of great modules on , which you’ll want to use. Keeping them updated is tedious, but Greenkeeper is a real time-saver.
You might want to create something similar for your scoped NPM modules, since they are currently not supported by Greenkeeper.
Also, consider that automatically merge Greenkeeper pull requests if all tests pass.

Use promises instead of callbacks.
are easier to use and gives the bonus of catching any errors that might arise in your functions, avoiding uncaught exceptions.
The code tends to be easier to read and understand.

Avoid mixing promises with callbacks.
Mixing callback and promise code gets very messy very fast. It can also be the source of unexpected bugs, as well as weird stacktraces from assertion errors in your test suite.
If invoking a callback from within a Promise chain, use setImmediate().

Use ES6 (or later).
ES6 is making your code more expressive and powerful. Go all the way and use or similar to make it possible to run on all Node versions.

Write to migrate old code.
Code will get outdated and dirty. Instead of manually refreshing all projects, consider writing a code mod to do the work for you. Automation frees up time so that you can focus on building features for users instead.

Validate input params to all public APIs or use typing.
Working with unvalidated or untyped input is a pain and requires a lot of effort to watch out for nasty bugs. Either use typed Javascript or always validate input to exported functions.
We use a lot for validation and it’s worked out great for us.

Keep files small and respect SRP.
When the codebase grows, it might get more complicated to manage and modify. To keep it as simple and flexible as possible, keep each file small and respect the Single Responsibility Principle.
It is easier to manage many small files than few bigger ones.
We rarely create files larger than > 100 lines of code, preferably with a single exported function.
However, apply the same principles as with all modularization; don’t create a module if there’s only one consumer.

Either use full blown constructor dependency injection or don’t do it at all.
Mock globally in tests (they’re not run in parallel) and use static code analysis to avoid poor design (e.g. too many dependencies per file).
It’s Javascript; embrace it.

Keep code simple.
Not exactly specific to Javascript, but all too important. The code itself should not be complicated, it’s the combination of all your code that’s supposed to be able to solve complicated user problems.
Don’t try to be too smart.

Write web server code agnostic to choice of server.
Web server code, e.g. route handlers, should only be a layer in top of your other services. Keep that layer thin to avoid tight coupling with the web server of choice, to make migration less complicated.
We started off using but are moving over to , which is more suitable for our needs.

Keep project files organized and use names your peers can understand. As projects grow, it becomes increase important to find your way around it. Structure files in a way that makes sense to everyone in the team.

Don’t underestimate copy-pasting over making modules.
Extracting repeated code to an NPM module might be alluring. But sometimes it’s cheaper to just use the old +C, +V. Make a judgment and weigh in factors such as the potential module’s lifecycle, change frequency etc to avoid modularizing too early.

Evaluate and experiment in isolated but real parts of code.
In order to evolve, you need to try new things. Experiment with new patterns and structures, but always do an evaluation of the result to actually learn something (before adopting the change throughout the codebase). Do experiments in real use cases, but try to minimize impact, to make it a cheap decision to change your mind.

Never stop evolving the codebase.
You’ll keep improving how you write your code. Feed new learnings back into the codebase to make it easier to work with over time and to keep the amount of “legacy code” to a minimum. (Minimum does not mean zero.)

Use a highly capable IDE, e.g. Jetbrains .
It’s up to each developer to pick the tools in their toolbox. However, on our team, everyone’s realized that using a great IDE accelerates the workflow, makes it easier to refactor, test, format, lint etc etc. Well worth the money.
Even hardcore and fanatics have realized this (there’s a Vim plug for IntelliJ that helps out too).

Use strict versions in `package.json`.
You’ll want to know which code that’s actually running in production. Pinning version numbers is a good strategy, paired with Greenkeeper.
Consider (`^1.2.42`) for dev dependencies if you want to avoid updating `package.json` when it’s not necessarily needed.


It’s been great to see the incredible Node and Javascript community development the past years. Historically, Javascript got a lot of blame for being a quick-hack language without much quality. Today, you rarely come across an NPM module that hasn’t got full test coverage, just to mention one positive metric.

Also, the developments with ES6 and have also taken the language in a great direction.

In total, we’re very happy with choosing Javascript and Node.js as our primary language and runtime and are looking forward to learning more.

👋🏼

Springworks Engineering

Stories from building the ultimate platform for connected cars.

Kristofer Sommestad

Written by

CTO @ Springworks

Springworks Engineering

Stories from building the ultimate platform for connected cars.