The trade off between developer experience (DX) and scalability & complexity is the never ending struggle in the software industry.
We all hate writing tedious boilerplate code that does nothing but glueing things together. That's the exact reason why developers celebrate LLMs so much when they take part of our jobs away.
But just because you don't have to write it yourself doesn't mean it's not there and you don't have to read and understand it.
The best code is the one you don't write at all.
A major thing driving developer fatigue, project complexity and technical debt that's hard to come by, is a codebase that is scattered with many places and files where you have to adjust many things for a small change.
This often comes from types or some controllers - that are calling services - being registered in routers for some routes. And then, of course, you also have to add exports and imports everywhere in the correct places!
Example:
You have an old-school expressjs app. You're very traditional and obey to the MVC principle, so you have the routers, your controllers and the service classes.
Sure, the goal of the separation is that you minimize the duplicated logic, because you will have similar endpoints with slightly different filtering logic and such in your RESTful API.
But the reality is that you will have to adjust three files just for a small piece of new business logic. Plus all files that hold your shared type definitions you explicitly defined in order to keep everything explicit and "nice".
When you're just starting out as a developer, such a structure might calm your mind and you're paitent enough to edit 6 files... but let's be honest, it's ridiculous and just annoying. Plus, you'll end up hating the project when it get's bigger... it gets unmanageable.
That's why developers love tRPC.
It's like:
"I don't care 'bout all this shit, just give me an endpoint and a client.
Typesafe of course!!".
That's great, because you just change or add an endpoint and get to try it in the frontend immediately!
However, this does not free you of the burden of designing good endpoints that balance complexity and over- and under-fetching data for the client.
With tRPC you gain short term speed and satisfaction but risk long term complexity and technical debt.
Then there's GraphQL. Loved and hated.
With GraphQL you're able to solve all the aforementioned issues. But till now, it would have always meant that you're buying into new problems. Often harder to solve, or let's say more advanced problems.
On the client, GraphQL is great. You get everyting you need from the API and not more - and it's all typed, so you know exactly what you're dealing with. Also, you don't have to care about the right HTTP verb, it's just Queries, Mutations and Subscriptions. It's self-explainatory.
On the server it's more work however. Traditionally you start by designing your schema and then write the resolvers for it. So here you also start off with a situation where you have multiple places you need to adjust for small changes - let's say you're adding a property in a Query's return payload.
You'd have to adjust the schema and then the code. That's why probably most GraphQL is written in a code-first approach. You define the schema in the code by annotating your code in some way. You keep the annotations for the GQL schema in close proximity to your actual resolver code. This way you avoid jumping around files.
To get all this right you have to usually set up a lot of codegen scripts and for sure keep an eye on maintaing a well-designed schema!
- You have to keep type names unique.
- Make sure you don't design resolvers poorly so you don't crush the server's performance.
- Change names of input types? You'll have to adjust all your queries using them!
- You always have to type-out the graphql selection for your queries... or learn how to use fragments and really use them.
- Your result-types depend on the graphql selection, so you need codegen to have accurate typescript types. (gql.tada solves this)
It's just so much more boilerpalte stuff!
You can't just start with an endpoint, add another, change a field, change a type, etc.... and don't care about all the other stuff going on.
Until now.
Introducing Cobalt
In short, the combination of Cobalt & Samarium let's you leverage the power of GraphQL with all it's benefits, without the downsides.
- Write your Queries, Mutations and even Subscriptions as simple typescript functions without any additional annotation.
- Keep it organized with a filesystem-based naming convention.
- Never again deal with manually changing / remembering type names.
- Don't even install the 'graphql' package in the client at all.
- No manual codegen steps for accurate types.
Cobalt let's you spin up a graphql server with one command and you can incrementally develop your backend just by adding one file after another.
One Query, Mutation or Subscription at a time, and the generated Samarium SDK instantly allows your client to use the operation fully type safe in the frontend.
You changed a type name? Your code doesn't care.
And if it does, typescript will tell you.
You changes the name or Non-Nullable flag of an input argument?
Typescript will tell you.
You removed a field server-side and you have a selection for it in the client?
Typescript will tell you.
It's like tRPC, but with the flexibilty of GraphQL.
You don't even have to name your types. But you can.
To make your schema look good. AFTER you're sure what it will look like.
You can easily add a field resolver to extend a type.
And only select the field when you need it in the client.
You can have dynamic selections or fragments,
that depend on code logic.
You define your selections as typescript code, you don't need to learn GQL.
You hate typing out the whole graphql selection?
Just use the $all
or $scalars
helper funciton.
Finally you can move fast AND keep the quality high!
Give it a try and check the GitHub repository:
And don't forget to check out Samarium.
You can use it for all the APIs you consume!
Don't make your life harder than it has to be ;)
If you like my content and OpenSource projects, consider joining my mailing list!
You won't miss any high quality content about software development, AI and startups / company culture anymore.
It also gives me feedback and keeps me motivated :)