6 ms·
I just built a frontend and backend with TS following this same logic. I'm super happy with TS for the frontend, but I regret picking it for the backend and am
by peferron 6y ago
I just built a frontend and backend with TS following this same logic. I'm super happy with TS for the frontend, but I regret picking it for the backend and am considering rewriting the backend in Clojure while it's still early.
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.
- rdgthree 6y agoIt'll depend on what you're building, but there's some value in rendering React on our backend. For example, we render documents (HTML => PDF) and emails with the same React components we use for our frontend to keep styling consistent. There are also some utils we use (in similar contexts) on both sides largely for things like string formatting or annoying math. Not a ton of stuff, but it's nice to have one bulletproof util to format phone numbers in every context. Another one is payment fee calculation[0], so we can guarantee the user sees the same number they'll be charged. 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-on-to-customers https://support.stripe.com/questions/passing-the-stripe-fee-...
- peferron 6y agoI wasn't very clear with my earlier description; the frontend is a SPA served by Node.js, while the backend is a completely separate GraphQL API service which I'm considering to rewrite in Clojure. 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!
- rdgthree 6y agoMakes sense! We have the same setup. FWIW, it's nice to do the React rendering on the backend because it's a bit easier to pass values to those templates directly (as opposed to using URL params or something). We render the React to a string there, then just pass that HTML string to a Browserless[0] instance. Certainly not a showstopper though, passing a URI is easy enough! [0]https://docs.browserless.io/docs/pdf.html https://docs.browserless.io/docs/pdf.html
- danenania 6y agoHave you tried Ramda for the data manipulation functions you miss from Clojure? It used to not work very well with Typescript (lots of inference issues), but now it's gotten quite good. You can use something like https://github.com/vriad/zod 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!
- cultofmetatron 6y agotake a look at elixir/phoenix if you're considering a rewrite. The Absinthe library is excellent and well documented. I'm using it for my startup and the app is speedy as hell on minimal hardware.
- pkilgore 6y ago> 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. 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!