I Hate NestJS
ethang.dev
ethang.dev
As a maintainer of class-validator, I'd like to clarify that this is not accurate. Legitimate security issues, when reported, are promptly addressed. The multi-year security alert listed in NIST NVD is akin to the bogus report that the curl maintainer discussed a few months ago.
In a nutshell, the report suggests that specific settings can potentially lead to validation bypass, which is indeed the case because these settings determine whether unknown objects should fail or pass the validation. This is analogous to my creating a CVE for Windows simply because anyone can access my computer when I haven't set a password.
However, the other part about the scare support is sadly true though.
Pro tip: If you find a paragraph celebrating "SOLID principles" at the beginning of the documentation of something, run.
TLDR: The main culprit is encapsulation at a granular level. Explicitly associating functions with data types systematically.
A side note: I personally think this applies to "OOP as done in the industry". There are exceptions. Erlang for example.
Namely I would say: - Inheritance: bad because adding layers makes it harder to understand what code is running - favor composition instead - Encapsulation: bad because it encourages mutable internal state and couple functions with data - favor immutable state
Plenty of practices of doing good OOP are all about doing less OOP (eg. Avoid deep inheritance hierarchies).
That leaves us with composition, which isn't unique to OOP and eliminates one of the core "benefits" claimed by OOP proponents. (Object oriented thinking basically. Animal <- Cat, Dog, etc.)
Your argument (as many others) falls into the category of OOP minimalism. Basically, it says: "Do less OOP". That's part of the philosophy for years now. It shows that OOP is essentially self defeating as there will be nothing of it left after every new "best practice" has advised us to get rid of each single OOP concept.
It's time to wake up now.
Ultimately though using universal NAND gates is a convenience of manufacturing and not comprehension. Just imagine if we replace all of our Boolean operators with an equivalent NAND and then only use that. Probably not the most readable code.
OOP has one trick: dynamic dispatch. Which corresponds to logical existential operator.
You could also have product (structures ... depending on if you consider getters and setters proper OO then maybe it has this), sum (sum types and discriminated unions), implication (anon functions), and universal (generics).
Yeah technically you can simulate everything with dynamic dispatch but then you kind of end up with a mess.
edit: yup ignorance shows. Going through couple of wiki dynamic dispatch means polymorphic methods.
It's been a while now, but can't recall how many times I've been asked about SOLID and in reality the codebase was as solid as wet paper. It felt more like a buzzword thrown around so people can argue about hypothetical, perfect OOP code. So glad FP is more widespread
When the company I work for hired a person who was super excited about NestJS, I looked at it with an open mind (or so I'd like to think). I was probably a bit sceptical because this person, who seemed really nice, couldn't think of a single thing he disliked about it (that's a question I tend to ask about any tech that someone is all-over-the-moon about, and my thinking is that if you can't list a single thing you dislike about something, you probably don't know it well enough), but let's take a look at it!
As soon as I saw they had basically copy/pasted the Angular 2+ module system into Node.js, and went full-hog into IoC, I decided not to even try it out.
The same Angular 2+ module system that basically was copy/pasted from AngularJS and that I had actually ripped out of our huge AngularJS app at our previous job because it caused more friction than it solved, and lo and behold, the tests became simpler when we didn't have to use ngMock. All those services were singletons anyway and were more suited to being actual JS/TS modules.
But AngularJS had a valid reason for it because it was back in forever-ago and there were no JS modules, much less dynamic imports for code-splitting.
Not that you need code splitting for a backend app.
I have never felt I needed DI in a Node.js app.
NestJS - not even once.
I'd rather describe the HTTP to method call mapping of my controller with decorators than a bunch of "add endpoint" calls. I like that the HTTP to code binding is located right beside the code itself.
I like that I can separate my app up into modules and couple them as loosely or tightly as I need.
You can architecture astronaut with NestJS, but you don't have to! I wrote a queue module (to interface with a legacy queue system), using decorators to specify the consumer. Docs on doing this are essentially nonexistent. The NestJS based library I cribbed from spread the "find the object & method to call" logic across multiple classes. I distilled it down to a screenful.
As with all tools, you get some choice in how you use it. Want to go wild with dependency injecting a zillion classes with one method each? If you really want to, I guess you can. Want to be sensible, for your definition of sensible? You can do that too.
I've heard good things about Drizzle.
I have absolutely no idea why anyone would use NestJS, short of being used to writing dumb enterprise Java for the past 20 years or not wanting to move forward from it.
What a relief, I’m not alone feeling this way
As always, pick the right tool for the right job - let's stop the cargo-culting.
> TypeORM is not even typed. When you run a .findOne() method is just passes back a generic.
This is patently false. I'm not sure what the author is even trying to say here, but the output of `Foo.findOne(...)` is `Promise<Foo | null>`. If it's not, there's a good chance you're coloring outside the lines. Maybe this is true in the context of NestJS, but it is not true for typeorm on its own.
constructor(
// this is the only way to get a repository for the photo table/model
private photoRepository: Repository<Photo>,
) { }
getPhoto() {
// this would be type Photo
const photo = this.photoRepository.findOne();
}Reading this article reminded me that DI has a lot of advocates, but I've never heard a compelling reason to use it in most codebases.
* Minimizing non-local state
* Keeping things testable
* Keeping code reusable without having to find and copy a lot of other code from a codebase/refactor completely
Note: I am not talking about DI frameworks here, because I don't like them.
Say in rust, you have some struct backing a trait. But your implementation of the trait can also have a variety of options which are parameterized by some other trait. You end up with a generic struct: that is all dependency injection is. Every generic struct in rust is an example of dependency injection.
Instead, dependency injection frameworks help people instantiate large dependency graphs with some lifecycle management. Those I am a bit of the take it or leave it opinion.
Sorry for being cynical. I can't help it.
Unfortunately, there are many of those.
What I often find is that management in organizations rewards these people because they get quick results. The cost to the customer and the team is well hidden until it’s too late.
The alternative to DI is keeping dependencies in a global state (global variables, singletons, whatever) and that is a maintainability nightmare for any codebase above a certain size. I don't think that it matters if the language is OOP or not, the issue is the same.
Regardless of what the article says, mocking in tests is easier. As you’re only depending on injected classes, you just mock a class and inject it in a test.
Like any architectural decision “it depends”. But I think it scales well, enforces modularity, automates construction logic (which can be huge) and is quite nice to work with generally.
The same reason as in OO languages. Clean separation of startup and runtime. Testability. Ability to avoid duplication and make changes in one place.
> What techniques do you use to make the level of indirection feel like a help, rather than a hindrance, in your codebase?
Separation of concerns/responsibilities always involves indirection. Using something like a free monad would be more indirection. I can't really give any useful advice beyond "factor your app well" and "split concerns/responsibilities at points that make sense"
I suspect it is because Typescript doesn't properly support prototype function assignment, which makes scoped injection painfully ugly.
Just a minor editorial comment... you might want to check the definition of 'nubile'
The class itself a group of maybe 10-15 drop ins from the local commune, a mix of mostly straight women in their 20s and gay men in their 50s. It wasn’t really a formal class, more just the sort of thing that comes together when you’re surrounded by whimsical folk. It was fun being the center of so much attention and so many playful quips.
It was my first time, but the instructor Arthur said I was doing well and asked if I had any experience. Naked and near completely upside down, told him no, I was quite nubile. He asked if I meant a noobish. I doubled down and told him, no, I’m nubile, but excited to learn. He smiled at me, and gave me quite the flirtatious look. I had no idea until weeks later when I learned the distinction. Sadly, a few years later Arthur passed away quite suddenly. I’m always reminded of that experience every time I hear the word, and doubly so when it’s mistakenly used.
"Last Updated: 1 year ago"
I would much rather see real dates, to get a feel for how long this change of opinion took.
(if you inspect, you see the actual date is 8/28/22, but the original doesn't show the original date, only the last updated date of the same day)
This is EXACTLY how I felt using NestJS. Except, IMO, it took the worst part of Java (Spring DI, esp. the over-verbose, make-changes-in-4-random-files-to-get-an-object style they did it circa early 2010s, instead of the much cleaner styles they have today) and shoved it into a language that is built on a completely different paradigms and constraints.
Not had a single pleasant experience working with NestJS, especially once it hits a certain scale, and you are just bogged down running between modules, services, controllers and DI-hell
Decorators are higher order classes.
Dependency injection (i.e. inversion of control) is not standard OOP.
DI is not the only way to do composition in OOP. OOP gives you the choice of both inheritance and composition. Look at any Python, Rails, or Smalltalk codebase and you will see that things are both composed and inherited.
This post is just a rant. NestJS doesn't work for you? Don't fucking use it, no reason to hate.
No reason to swear either.
The article has an emotional title. The title explicitly says that it's a rant. It's literally about the author's emotion towards NestJS.
I don't get the point of your comment. You don't like the article? Don't read it!
This is the internet, it's okay to swear.
Some of us prefer to make a decision on whether or not we like an article after reading it
And some like to decide that they hate a certain framework after they have been using it. So what?
It's an ORM which means performance you may as well forget it. It's such a memory hog. On top of GraphQL, which can easily cause performance issues on its very own without help thank you very much. It's like the N + 1 problem had a baby with a memory leak. Why devs keep doing this to themselves I'll never understand. You don't need this level of pain for even a massive API codebase.
- TypeORM is pretty bad. Particularly when you start using it for more than simple crud operations. There was an epic battle in the GitHub issues and eventually some folks finally got through to the maintainer. Since then, development has picked up and some long-standing issues have been corrected. It's still pretty bad. However, I haven't found anything in the nodejs ecosystem that's substantially better, which is sad.
- class-validator and class-transformer mostly work, but are barely maintained and have many issues going back years. The maintainers left a bad taste in my mouth. I asked years ago about why these libraries which are so core to NestJS are not forked. The issue was locked with a frustrating response. It sounds like they may have finally been pursuaded to fork them.
- NestJS support is terrible. I understand they have a paid support service but if you don't subscribe to that, they lock, discard, ignore any issues raised, even if they are valid and significant. They push all support questions to Discord, and as far as I can tell, none of the actual maintainers look at it. Very frustrating.
- NestJS breaks semver by releasing breaking changes in minor or patch updates. They don't seem to understand that if a core third party dependency has a breaking change, they need to bump their major version.
- It's very difficult to understand what has actually changed between versions. And with the history of breaking semver, upgrading is just plain scary. The "release notes" are just the commit list, and to make matters worse, they don't use a monorepo so you have to search through a dozen repositories.
Unlike the author, I do actually enjoy the modular design, the dependency injection system, and a few other design choices. The only difficulty I've encountered when testing is around the third party pieces, such as TypeORM.
If you come from Java / Spring you would feel right at home, anyone else would feel disgust.
I cannot pass judgment on whether this is good or bad, but I can easily see how this familiarity can be beneficial for the average programmer.
It is pointless for the author to argue about the best way to use TypeScript because the value proposition of NestJS is to use the same patterns as another framework you are already using.
If you're building a React app, for example, then NestJS doesn't make as much sense, it's just going to feel like it's getting in the way, hence this article.
This article really misses the mark and the author is just proving that they still don't understand what NestJS is for.
TypeORM is terrible, however.
Prisma is probably way to go.
Alan Kay wanted biology and we got it, just the taxonomy system instead of cell biology.
OOP with classes is a way to classify and organize code.
The problem with the functional paradigm is there’s less clear patterns for code organization.
Check out the average typescript project (or just any successful long lived js project) and the code organization is all over the place.
Not saying you can’t organize a function and module system, it seems like a bigger challenge.
I'll tell you what. If you must work with NodeJS, IMHO you are best off just keeping things light and simple.
NestJS is not good. I've used it professionally a few years ago for several services, and it was overly complicated which would be fine if the performance was okay - but it was not good either. And don't get me started on TypeORM or Prisma (or ORMs in general). So much wasted time and effort hammering down performance issues. The NestJS docs were also not that great and required having to dive into GH issues or source code too much compared to other frameworks.
I eventually rewrote the services to use express + kysely + zod + msgpack. There was clear separation of concerns with layers which made both unit and integration testing easy. IMO attempting OOP in JS comes with additional complexity/overhead and performance implications due to overly complex inheritance.
When in doubt, KISS.
What does TypeORM do then?
https://fastify.dev/docs/latest/Guides/Getting-Started#valid...
Redwood may be an interesting full-stack option if you're OK with your backend being serverless
Turns out it's not installed by default?
https://github.com/adonisjs/core/discussions/2642#discussion...
Not sure, but at this point I don't care, I just trashed it because I don't have time in my life to fool around.
At the end of the day, those patterns were created to keep things maintainable, not just use them for the sake of it.
Nubile? Auto-correct mistake? Or just a very strange worldview?
Because of (1) it's a struggle to convince anyone to buy (2). And would be silly to expect someone to provide it for free.
At the time, I thought I was dumb for not understanding the point of those things, but now, years on, I think those things really are just unnecessary.
I do like C# btw and would be happy to work with it again.
The class keyword brings together a 'best practice' set of pre-existing JS features to provide a uniform OOP experience. Yes, classes still work a little bit differently than they do in other languages, but the benefit of being able to expect the same type of class from modern JS libraries doesn't get appreciated enough.
The only drawback I see to its addition is that it abstracts prototypal inheritance and some other key idiosyncrasies of Javascript from newcomers to the language. But the move away from doing things 'the old ways' has worked out much better for Typescript adoption anyway, as it's hard to type things properly when the interface of an object created by a custom 'class' function is just 'whatever the prototype currently has'