DEV Community

Cover image for Modern API Development with Node.js, Express, and TypeScript using Clean Architecture

Modern API Development with Node.js, Express, and TypeScript using Clean Architecture

Dipak Ahirav on August 17, 2024

APIs are the backbone of modern web applications. As the complexity of applications grows, it's crucial to adopt an architecture that promotes scal...
Collapse
 
webjose profile image
José Pablo Ramírez Vargas

NodeJS is far from "ideal" for backend. Ideal is the epitome of what backends should be. NodeJS is just OK. But I'm most likely commenting on an AI-generated piece, and we all know what AI is good for: Lies.

Collapse
 
mrrishimeena profile image
Rishi Kumar

Nodejs is far from "ideal" ? Joke of the month.

Collapse
 
nicolus profile image
Nicolus

So you think node.js is the ideal runtime for creating backend APIs ? That's an interesting take.

What makes it better suited for creating APIs than Java or C# in your opinion ?

I've always been under the impression that the rationale for using node.js was "I'm already using javascript for the frontend anyways so I might as well use it for the backend too" rather than "JavaScript is the ultimate backend language, let's all use node.js"

Thread Thread
 
cholasimmons profile image
Chola

If you like .NET, use .NET
Why follow another framework to bash it? 🤣

Thread Thread
 
webjose profile image
José Pablo Ramírez Vargas

Is NodeJS close family or something? You seem offended. Did I touch a nerve or something?

Thread Thread
 
mrrishimeena profile image
Rishi Kumar

@nicolus, it all depends on the use case. I'm not a fan of any one particular language because each has its pros and cons. For service companies, startups, microservices, or chat applications where handling millions of requests simultaneously isn't necessary, any language can work. Node.js, for example, is easy to set up in just a few days, while others may require more time and specialized developers. In the end, it all comes down to the specific use case.

Just because Stack Overflow uses a monolithic application doesn't mean everyone should. Similarly, Discord's transition from MongoDB to Cassandra, and then from Cassandra to Scylla, doesn't mean everyone should start using Scylla. I remember the hype around big data in 2014-15 when everyone wanted to implement it in their applications, even if they only had a few hundred customers. Criticizing a language based on selected criteria is an interesting perspective.

Thread Thread
 
nicolus profile image
Nicolus • Edited

Sure, I (more or less) agree with that, the thing we're discussing in this thread is that sentence from the original post (emphasis mine) :

"Node.js is a powerful JavaScript runtime that allows you to build scalable network applications. It's non-blocking and event-driven, making it ideal for building APIs that handle a large number of requests."

And my follow up question is what makes it particularly "ideal" in the particular situation where you need to build an API that handles many requests ? If your answer is "it's easy to setup" then fine, it's definitely a valid answer, although I personally wouldn't say it makes it "ideal", it just makes it "convenient".

Note that I don't consider it a deal breaker. For example my stack of choice for web projects is PHP with Laravel, and it's most definitely not ideal for anything, it's just incredibly convenient to use and handles 99% of use cases well enough.

Thread Thread
 
mrrishimeena profile image
Rishi Kumar

True.

Collapse
 
webjose profile image
José Pablo Ramírez Vargas

The joke is how far from the top spots of performance charts NodeJs is. That's the joke.

Thread Thread
 
mrrishimeena profile image
Rishi Kumar

Unless you wanna handle 100+ billions requests, any language can handle this. you just need better architecture. And 90% of companies don't need such level of requirement.

Thread Thread
 
webjose profile image
José Pablo Ramírez Vargas

Incorrect. It depends on things like spikes of usage more than on overall total requests. But you're right in the sense that most systems can handle normal amounts of requests. NodeJs is one of the better ones for "those" cases. That's why I say it is far from ideal, and in the "OK" category.

Thread Thread
 
igorrubinovich profile image
Igor Rubinovich

All resources are limited one way or another, so architecting for scalability is the real concern/goal if indeed operations are expected to spike over that level.
I have a couple of writeups on the topic in my dev.to profile if you will.

Collapse
 
cholasimmons profile image
Chola

I miss the dislike button

Collapse
 
ratulsharker profile image
Ratul sharker • Edited

NodeJS is just a runtime, it is not framework. It’s not meant to be ideal for backend development.

Like java and spring-boot, it can compared to java, not spring boot. To be ideal, batteries needed to be included. i.e i was scrolling through the post, the author needed to maintain his own DI container. Which is not ideal, as this problem is not new. Business does not care about you maintaining the DI container, all they needed flawless operations.

There might be some controversy, but in my personal opinion, this sort of open ended non-framework implementations can get up up and running pretty fast. Which is ideal for POC and MVP. In the long run, lack of documentation, change in the team member may strangle all the way up. So its all about tooling, which suits the use case in hand.

Collapse
 
tombohub profile image
tombohub

why?

Collapse
 
webjose profile image
José Pablo Ramírez Vargas • Edited

Despite having an efficient event loop that scales pretty well, it cannot possibly beat multi-threaded runtimes like .Net or C/C++ in terms of scalability. For example, drogon is pretty much the king of performance.

But that's just performance-wise. There are other factors, and largely depend on what people need. For example, some people say that because JavaScript is not a typed language, it is difficult to have good IoC containers, and some people just need their IoC container.

Collapse
 
oscardmg profile image
Oscar Montoya

Thank you very much for sharing this post, it is very interesting.

Please can you help me by implementing your code, I have the following error, how would you solve it?

`src/web-api/middleware/auth.ts:14:9 - error TS2339: Property 'user' does not exist on type 'Request>'.

14 req.user = user;`

github.com/oscardmg/nodejs-typescr...

Collapse
 
dipakahirav profile image
Dipak Ahirav • Edited

Thanks @oscardmg for bringing this up. It looks like the error you're encountering is due to a type mismatch when using jwt.verify in TypeScript. The issue typically arises because TypeScript isn't sure about the structure of the decoded token payload. Here's a solution that should resolve the problem:

Solution:

  1. Extend JwtPayload: First, define an interface for your JWT payload that extends JwtPayload. This ensures that the payload type matches what jsonwebtoken expects.

  2. Type Assertion in jwt.verify: Use a type assertion to tell TypeScript what type the decoded token should be.

Here's the updated code:

import jwt, { JwtPayload } from 'jsonwebtoken';
import { Request, Response, NextFunction } from 'express';

// Define the structure of your JWT payload, extending JwtPayload
interface UserPayload extends JwtPayload {
  id: string;
  email: string;
  // Add any other properties you expect in the payload
}

// Extend the Express Request interface to include the user property
interface AuthenticatedRequest extends Request {
  user?: UserPayload;
}

export function authenticateToken(
  req: AuthenticatedRequest,
  res: Response,
  next: NextFunction
) {
  const token = req.header('Authorization')?.split(' ')[1];
  if (!token) return res.sendStatus(401);

  jwt.verify(token, process.env.JWT_SECRET as string, (err, decoded) => {
    if (err) return res.sendStatus(403);

    // Type assertion to ensure the decoded payload matches UserPayload
    req.user = decoded as UserPayload;
    next();
  });
}
Enter fullscreen mode Exit fullscreen mode

Let me know if you have any further questions or if there's anything else I can help with!

Collapse
 
oscardmg profile image
Oscar Montoya

Thank you very much for the help

Collapse
 
benedya profile image
Bohdan Turyk

Hi Dipak, thanks for sharing!
I also like Clean Architecture principles, so it was quite interesting to read.
For DI you might consider using InversifyJS. Maybe it violates the dependency direction rule but it helps a lot with dependencies :)
I saw questions regarding the implementation of this using NestJs. Some time ago I created a skeleton with a very similar approach that you presented here and NestJs. Just in case someone is interested in it here is the link github.com/benedya/nestjs-layered-...
(apologies if sharing links is not allowed, just thought it might be helpful)

Collapse
 
evankhann profile image
Berlin

good writing

Collapse
 
charlesr1971 profile image
Charles Robertson • Edited

This is a good tutorial for getting people started.

I would probably use a framework like NestJS [only because it complements my Angular frontend].

nestjs.com/

It abstracts away some of the features that you have created manually. But your tutorial is valid because it shows how this stuff works, behind the scenes. 🙂

Well, I use both Coldfusion & NodeJs, for my backend.
The former uses a multithreaded Application Server. This allows a Coldfusion Application Server to handle multiple requests, concurrently.
CFML is generally a linear language, although it is possible to spawn asynchronous threads.
Taffy.io is a good Rest API framework for CFML.
Coldfusion is compiled, at runtime. It is an interpreted language.
NodeJS is single threaded and uses an event loop, but it is fast, very fast, so in fact the asynchronous operations that come off the event loop, create a comparatively fast response, when compared to the multithreaded CFML environment. Although NodeJs can only handle a single request, at any one time, requests are handled very quickly and use asynchronous operations that rejoin the main event loop, to speed up the response times. Surprisingly, NodeJs compares favourably to Coldfusion, when it comes to handling requests. Because NodeJs can process requests faster, this makes up for the fact that it cannot handle concurrency.

Both approaches have their pros & cons, but both systems make good Rest API candidates.

Collapse
 
dipakahirav profile image
Dipak Ahirav

Thank you so much 😊

Collapse
 
cmohanc profile image
cmohanc

Node.js?is neither clean nor lean.its a combination of dependencies.

Collapse
 
tian_wijaya profile image
tian

on post said, good for testing then provide the DI but on post doesn't create unit testing at all

Collapse
 
nguyenhhkiet profile image
NguyenHHKiet

amazing

Collapse
 
dbroadhurst profile image
David Broadhurst

I use NestJS but if I didn't this would be my goto article. Excellent guide and for those crying over node they need to appreciate how well this article describes best practices regardless of the language / framework.

Collapse
 
charlesr1971 profile image
Charles Robertson

I agree.
NestJS is great, because it abstracts away a lot of what this article explains in more detail.
But, it is good to see how Rest API works at a lower level.

Collapse
 
jangelodev profile image
João Angelo

Hi Dipak Ahirav,
Top, very nice and helpful !
Thanks for sharing.

Collapse
 
yogithesymbian profile image
Yogi Arif Widodo

awesome

Collapse
 
axorax profile image
Axorax

nice

Collapse
 
vilmos_bartalis_7242c8d44 profile image
Vilmos Bartalis

Interesting POV. Thanks for sharing.

Collapse
 
ricardogesteves profile image
Ricardo Esteves

Great article @dipakahirav , really good, clean, insightful and you cover all the important topics! Nice!

Collapse
 
bhataasim profile image
Bhat Aasim

Amazing Post buddy. Loved it. I am almost using the same strategy in my projects. Inspired by Nest.js.

Collapse
 
tombohub profile image
tombohub • Edited

unfortunatelly typescript is still bothered by dynamic javascript. Still better than nothing.

interface Koko {
    mama: (tata: "tata") => void;
}

const koko: Koko = {
    mama() {}, // no error
};

koko.mama(); // error

koko.mama("tata"); // no error
Enter fullscreen mode Exit fullscreen mode
Collapse
 
jora_dev profile image
Jora

Won't you include unit and e2e tests?

Collapse
 
jos_afernndezm_d2f7 profile image
José A. Fernández M.

I wonder, wouldn't be easier to use nestjs? I mean, instead of going raw with express?

Collapse
 
viiik profile image
Eduard

If you want strict "clean architecture" then yeah, it has all the dependency injection solved for you.

However, real clean code is code that doesn't exist, and NestJS is a massive amount of boilerplate code. In that regard I tend to favor minimal setups instead of something like NestJS.

Collapse
 
charlesr1971 profile image
Charles Robertson

I guess this is the same argument as to whether you should use Angular or a pure class based TypeScript frontend? Angular [frontend] & NestJS [backend] provide a series of features that abstract away code modules that you would otherwise have to write yourself. If you need a highly customised application, then it might be better to write everything from scratch. Generally, I prefer the productivity that NestJS provides.

Collapse
 
tombohub profile image
tombohub

people always say 'massive boilerplate'. What exactly is the boilerplate? Which part, which lines of code?

And if you know how, would you do the same architecture, same functionality, same decoupling and modularity without that boilerplate?

Collapse
 
charlesr1971 profile image
Charles Robertson

Yes. Of course, it is easier, but the point of the article, is to expose all those features that NestJS abstracts away.
And then of course, you have to take into account the time that it takes to learn NestJS.
But, yes, once you understand NestJS, Rest API development, can be extremely productive.