A minimalist backend REST API in NodeJS

Why we used a minimalist backend REST API powered by an OpenAPI spec

Victor
Victor  

# ⚡ Tl;dr

  • Align your team with the high-level technical tradeoffs you should be making based on your business goals and company culture.
  • Ensure your team considers the long-term implications of building your backend on top of a "kitchen-sink included" framework.
  • Check out our example NodeJS REST API (opens new window) repository on GitHub.

# 🚀 Let’s kick-off

During the early days of IcePanel, we had to make important decisions about which core technologies we’d use to build the product. This can be daunting as it can be significantly time-consuming and expensive to change these technologies later. Also, you can make your best-informed decision at the time, but there’s no knowing how these choices will affect you in the future. For the most part, you’re strapped in for the ride.

We decided early on to optimize our tech choices for simplicity; let’s discuss why.

# 🟩 Optimizing for simplicity

Most decisions and choices in software are about weighing up tradeoffs and choosing an option that most favours the desired goal you’re optimizing for. There’s usually no right or wrong answer, even though some engineers may say otherwise 😉.

It may not be clear to everyone on the team what you should be trying to optimize for, especially if the company has weak culture, product vision or business goals. A strong company culture will help embed a common understanding in everyone about what tradeoffs you should or shouldn’t be making. Ideally, two engineers should intuitively make similar decisions when faced with the same problem.

So what can you optimize for? Here are some common ones:

  • Scalability
  • Resilience/Stability
  • Security
  • Execution speed
  • Development speed
  • Hot tech trends 😉

New product startups are inherently risky due to the high chance that you’re building something which doesn’t solve a problem for anyone. Our goal at IcePanel was to prove the product solves a problem quickly while building as little software as possible. Ensuring the codebase remains small and simple helps achieve this by allowing us to move at a rapid pace.

We deliberately decided not to use "kitchen-sink included" frameworks such as Laravel (opens new window), Django (opens new window), Loopback (opens new window) or Nest.js (opens new window) to keep things simple.

kitchen-sink!

# 🔥 Framework hell

Plenty of frameworks out there promise to make your life blissful. Get up and running in minutes and magically abstract all boring boilerplate functionality. Although, there are flipsides to this that we’ll discuss below.

1. Engineers joining the team have a higher learning curve.

A software engineer's time is expensive, and it usually takes 3-6 months of learning the codebase before they can contribute at full capacity. Learning new frameworks and understanding their nuances can be a time sink and increase this initial learning period.

2. You will find the framework's limitations one day.

It may take years, but inevitably you’ll hit the framework's limitations, and it’ll be a pain in the ass. The framework is now embedded into your organization as debt, and you’ll always need to make considerations for it moving forwards.

3. Abstracting functionality is great until it isn’t.

Not writing boilerplate code does allow you to get up and running much quicker. But as soon as something doesn’t work as expected, you’ll find yourself trawling through the framework source code to understand how it works.

change-my-mind!

Don’t get me wrong, there are times when using a framework is the correct decision. It can be amazing for building prototypes or getting up and running quickly. However, consider the points above, have a plan in mind and don’t leave it too late if you hit limitations.

# 🛠️ What we built

Our goal was to create a simple and minimalist NodeJS REST API backend using the least and smallest dependencies we could find. Elon says, "the best part is no part" agree or disagree?

One of our best decisions was to use spec-driven development (opens new window), where API paths and schemas are defined upfront in an OpenAPI spec. This allowed us to remove concerns such as input validation, path handling and client generation. It also gave us a consistent source of object types across our frontend and backend services.

We used just three libraries which provided all the functionality needed.

  1. express (opens new window) A minimalist web framework for NodeJS.

  2. express-openapi-validator (opens new window) Express middleware that routes and validates requests according to an OpenAPI spec (opens new window).

  3. openapi-typescript-codegen (opens new window) Generates a Typescript client with interfaces from an OpenAPI spec.

Check out our open-source GitHub repository (opens new window) for a working example/template of this!

# 🏁 To wrap up

Hopefully, this has been insightful and maybe triggered some conversations about what you should optimize for when making big or seemingly small upfront tech choices.

Let us know in the comments if you use a "kitchen sink included" framework and how the experience has been.

Stay chill 🧊