Packages over processes
Languages sport nice abstractions: Type systems, modules, functions, classes, package managers, database integrations, etc. We can catch mistakes easily through the type system, automated tests, local manual testing, and code review. On the other hand, with processes (like APIs), everything happens through interprocess communication. Many mistakes are caught late because we can't surface them until we interact with the other process. Moreover, for proprietary stuff, the API is a black box: We can't see what's going on, and we can't debug it directly. We can simulate what it'll do in our automated tests with fake data, but the data is expensive to maintain and doesn't give us much confidence. We still need to follow up with manual tests against the API before releasing anything significant, and that's before accounting for added setup costs and the overhead of learning new tooling, all of which cost time.
When we use packages, we own the deployment, which means that we don't have to utilize a whole bunch of extra infrastructure and communication layers for some tiny concern, and we don't have to pay for it either.
In addition to horrible DX, we have to deal with:
- All of these services mean excess cloud infrastructure that's expensive, mostly idle, and terrible for the environment.
- Artificially limited capacity (rate-limiting, space, etc.), which means more work design and scale.
- Our app's failure surface area increases because there are more points of failure (with the upside that someone else maintains it), which is especially painful when we use services for integral functionality.
That's why, given a choice between a package and a third-party service, I'll pick the package pretty much every time unless there's some specific infrastructure on offer that I don't want to maintain myself.
Opinionated problems
So, using packages and monolithic deployments is incredibly effective. But, communities like Node's tend to lean toward unopinionated stacks and can't make enough assumptions to build something like a robust authentication package since it's a full stack concern. In order to implement auth you need to:
- Know what email service provider you're using and how to format your emails.
- Know how to read and persist users safely (no injection vulnerabilities, appropriate hashing, and so on)
- Know about your UI and enforce username format and password length requirements.
- Rate limiting middleware for auth endpoints
- A way to manage blocked users
- Possibly integrate with a 2FA service.
- Integrate with some kind of session store.
- ...
These are problems that require opinions in hand to solve. It's not up to the auth package to decide which database or ESP you want to use. Since the packages aren't getting us far enough, businesses capitalize, and the Node community reaches for services like Firebase, Supabase, or Auth0. Large companies don't like distributing source anyway because they want to gate their code to monetize it; sticking it behind a service is an easy way to do that, and it also solves the problem of needing opinions: Now you get their opinions, but you don't have to think about it because they manage the implementation, which sucks for all of the reasons listed above.
I'm already paying for my own cloud infra, so I ought to be able to set and check a session cookie with my own resources. Yes, auth is a little involved, but it's definitely doable. So, if you're like me, the next logical step is to look for more opinionated solutions (think hackathon starter kit or boilerplate). There are a lot of devs that will say you just do this and you're done. Unfortunately, every boilerplate-y solution that I've seen has major security issues and falls short of a complete solution. Features like password reset are usually non-existent, even though they're essential to every app.
And, of course, it's not just auth. To have modern amenities in a full-stack app, we're going to need much more: automated test support (setup and teardown), a bundler, database migrations, a way to handle the request lifecycle on the frontend and backend along with validations, a bunch of glue to hold it all together, and more. But, none of the big players are going to push code oriented solutions for these problems because they can't make enough money on it. They'd have to release their source and pitch it to a bunch of people who are, by definition, trying to get out of paying more money and getting sucked into their services which is not a great target demo. You can make a little money at this, but they want to make bank. This solution is too involved and too efficient for the consumer for them to make a ton of money.
But I'm small potatoes, it's a right-sized problem for me to tackle, and I'd be thrilled if I made just a little bit of money at it. That's why I built Sapling. Better still, I stuck a very breathable license on it because I want you to be able to hack on it and use it for whatever you want.