You can use something like https://github.com/vriad/zod to get both runtime validation and static types (via inference) at the same time. The downside is defining your types in a DSL instead of plain TS, but I think it's worth it for the runtime validation.
It probably depends on your project, but I find type checking to be at least as crucial on the backend as the frontend (probably moreso), and being able to share types/have editor auto-complete across the whole stack is a godsend. While node isn't my favorite backend environment, it's not too bad (async/await are fantastic for io), and code sharing/type sharing makes it the most productive choice overall imho, all things considered.
Clojure's a lot of fun though, and it has taught me a lot, so I do see where you're coming from!
I think the more powerful benefits are definitely hiring and having a team that can work on both sides (and in that regard I think your points are perfectly reasonable), but there are definitely some more direct benefits in our case.
[0]https://support.stripe.com/questions/passing-the-stripe-fee-...
So the frontend can actually do SSR to render PDFs or emails. However, it would definitely be much harder to to ensure that any phone formatting or fee calculation done directly in the user's browser matches what the API returns. So yeah, definite benefits in sharing a language there. Thanks for the examples!
Certainly not a showstopper though, passing a URI is easy enough!
We found @nexus/schema to be excellent for this particular problem, but only after you set it up correctly to feed in type information via its typegenAutoconfig options.
I agree it is less frequently helpful at catching type errors, but the errors it does catch tend to be more important to get correct than the frontend errors!
My reasons are:
- I don't need to share type definitions between the frontend and backend; the frontend can generate TS type defs using GraphQL introspection regardless of which language the backend is implemented in.
- The backend is doing a lot of data manipulation, which TS is not very good at. For example, there's no built-in group-by function, and group keys must be primitives since TS doesn't have value equality for objects or arrays.
- Static typing doesn't help much with setting up GraphQL resolvers correctly, and overall feels much less useful than on the frontend where it's absolutely stellar to type check all GraphQL queries, React calls, 3rd party component props, and so on.
- I miss clojure.spec for validating calls to external services.
Plus a few more situational reasons:
- I work in a team that's already familiar with Clojure. I don't think it's a huge barrier to hiring either; I didn't know Clojure before I joined, they just recommended me a book.
- I personally don't find that using the same language everywhere helps much in reducing context switching costs. Using different languages actually feels more engaging, especially when one is as pleasant as Clojure.
Put all these together and I'm seeing real benefits in using different languages.