There is so much to unpack to get as why I have such an issue with it. But time and again I have been frustrated with it in terms of: it's design philosophy, implementation, scope of what it covers, bloat, recommended implementation approaches, etc.
I don't understand how a single framework can think that it should cover: message/request handling, logging, config management, dependency inversion, persistence, and IO. These things have almost no cross over (i.e. if they are well designed they should be easily composable with any other component) but time and again framework developers attempt to bundle them into a "one size fits all solution".
To best sum it up. I think any package I use should be secondary to my application. But this package makes it so that my application is secondary to the framework.
I suppose in nextjs it's the only option?
My go-to right now is itty router with zod.
Btw, itty-router v5 just dropped a couple days ago, with a fun batteries-included (still ultra-small) router, that should make your code even leaner.
How is the integration w/ zod anyway, and how can we make it better?
I'm in the progress of making a simple middleware based on zod to parse not only request body, but also params, headers, etc. Zod is really powerful and you could even use it to do stuff like parse jwt tokens and have complete type inference.
Perhaps my only issue with this approach is that you rely on a wrapper function to correctly pass the generics fron the middleware to the main handler.
Another possible approach is using types-per-route but then it's hard to enforce that the validator agrees with the handler itself.
Provider initialization (dependency injection) failed on me on a few occasions and it always wasted hours of productivity. It would break in some obscure way that wouldn't log any errors to the console, so there was nothing to go on besides attaching a debugger and stepping through layers of framework code. It was quite infuriating because it always happened when I was in the middle of something else.
If your specific use case wasn't covered by their docs (which were very barebones and "hello-world" oriented at the time), it was painful to figure out and use.
But like Angular, there is a very wide range of use cases where it is totally overkill and like Angular, companies are throwing it at each and every project.
I don’t find it bad but it’s in a strange spot being more bloated than other JS frameworks while still being way less "batteries included" than more classical corporate frameworks.
Like Angular, I don’t hate it though it’s just that I still haven’t figured out a project where it’s better suited than something else.
So some people will feel like it's over engineered.
I mean it's overengineered. Why do I have to register all these things, and why does it keep crashing if I register it like this without any understandable error message. It has a little bit of an OCD relationship with dependency injection. Where the normal import system can handle most of those cases.
But few nice things, resolvers, auto-generate swaggers. And TypeORM is lovely.
But yeah it's a bit too demanding. I'm okay with an opiniated framework if it gives a lot of features out of the box (like laravel or NextJs), but NestJS tells me how to do things without giving me enough in return. (auth, sockets etc are still quite a lot of work)
To me in most cases it is used it seems to just overengineer and obfuscate things unnecessarily when much simpler code would be easier to understand, etc.
It shouldn't be a thing that is done by default, only when it really makes sense.
Eventually with all those injections you are going to cause a situation where making any changes becomes really complex, if there are any use cases you didn't foresee at all.
In 90%+ cases you don't need interfaces and or DI, you should just be able to follow the logic with your IDE, it makes no sense to obfuscate that.
If the impl truly must vary for whatever case, then sure, you can use it.
But I would also say that don't do interfaces and impl before you actually need to switch them out dynamically depending on the context (and not just for testing).
If you have something that does Storage, and Storage drivers could be different like FileSystem, GoogleDrive, whatever, then sure use an interface, but not otherwise.
It's like DRY. Unless you actually use it 2-3 times, don't make everything unnecessarily into a reusable fn.
If you don't foresee having multiple storage methods in the near future then just use a class for storing which you can move to, to see how it does file storage or similar.