4 ms·
I started my career doing ~20 years of C# from pretty much the very beginning (JavaScript even before then!). Around 2020, I switched into the startup space an
by CharlieDigital 9mo ago
I started my career doing ~20 years of C# from pretty much the very beginning (JavaScript even before then!).
Around 2020, I switched into the startup space and quickly picked up TypeScript since that's "what the kids use". It wasn't without struggles, but once I wrapped my head around TypeScript as "shapes for JavaScript", it clicked.
At the time, the startup was undertaking a new product built on Nest.js[0] which looked awfully similar to ASP.NET web APIs which had the benefit of being much more mature, complete, and with one of the best ORMs (EF Core). I suggested it to the team and was a bit shocked by the pushback.
It ultimately inspired me to do a bunch of writing [1][2] on just how similar these two languages are and how they've converged on syntax (no doubt owing to Anders at the helm). Of course, they ultimately still have quite a bit of a gap, but for teams that are working in TS and finding that they are outgrowing it, C# is a very natural choice.
I left that startup after a short stint, but boomeranged almost 3 years later. Company went from seed to series C in that time. End of last year, we started the migration from TypeScript on the backend to C# on the backend and haven't looked back. The toolchain is far simpler and stable, the "default" ORM is far more flexible and powerful, the more rigorous type chain seems more conducive for agent-driven coding. Team adapted relatively quickly within ~4 weeks.
[0] https://nestjs.com/ https://nestjs.com/
[1] https://typescript-is-like-csharp.chrlschn.dev/ https://typescript-is-like-csharp.chrlschn.dev/
[2] https://itnext.io/getting-functional-with-c-6c74bf279616 https://itnext.io/getting-functional-with-c-6c74bf279616
- rafaelmn 8mo agoI can't say for sure because I don't know the full situation, but I've heard a similar story a few times and IMNSHO looking at it as "they made the wrong choice initially" is off because it assumes that a C# team could have delivered the initial version in the timeline required to get to the the next level of project. If they had a team that knows nest and can iterate fast with it that's the perfect choice. I've worked at multiple agencies over the years and was mostly in C# teams. Whenever a C# team got on greenfield/startup projects it ended up being a shit-show of self inflicted slowdowns/over-complications. I've seen 3 projects where in large part due to slow development of the MVP the project missed investment window or ran out of funds. From my experience Node/Rails teams were much more capable of delivering shit that works. It would eventually have a bunch of problems that would be non issues by default on a different stack like ASP.NET, but the difficulty of getting to that point and realizing that just being in that situation is a win is what most engineers miss.
- ngruhn 8mo agoSame experience here. C# might have superior tooling, performance, whatever but the OOP baggage is too heavy. In theory you can write something else than a giant over-complicated, over-abstracted pile of OOP nonsense in C#, but every team I've seen has.
- hvb2 8mo agoJust to keep it relevant and off topic. Typescript is moving to go because you just cannot get performance out of javascript because of its design. As he says in the video about perf "you cannot leave 10x on the table".
- cxr 8mo ago> Typescript is moving to go because you just cannot get performance out of javascript because of its design. That's not an accurate summary. An accurate summary is that: - They can't get the performance they want out of V8 - They're are moving to Go because its design is similar enough to JS that they can do a line-by-line port These aren't similar claims (or mere quibbling) compared to what you wrote. They're very nearly exact opposites.
- CharlieDigital 8mo agoYou can write very functional C#. Our codebase is a mix with some aspects being functional (`ErrorOr`[0] being a big part of it as well as `OneOf`[1]) and OOP. Our core, common services all return `ErrorOr` to allow fluent call chaining at the top of the stack (controllers). Modern C# features like `switch` expressions[2] (not `switch-case`) and pattern matching mean that it is possible to write very terse, expressive code. Extension members and methods[3] go a long way as well by making the builder pattern easier to implement. Overall, it's up to the team to make use of the tools provided by the C# team. You can write C# in a very OOP heavy way (as is possible with TS in the case of Nest.js); you can also write in in a very functional way given many of the functional features adopted from F# over the years. It's up to the team. [0] https://github.com/amantinband/error-or https://github.com/amantinband/error-or [1] https://github.com/mcintyre321/OneOf https://github.com/mcintyre321/OneOf [2] https://timdeschryver.dev/blog/pattern-matching-examples-in-csharp https://timdeschryver.dev/blog/pattern-matching-examples-in-... [3] https://typescript-is-like-csharp.chrlschn.dev/pages/intermediate/extension-methods.html https://typescript-is-like-csharp.chrlschn.dev/pages/interme...
- wbobeirne 8mo agoI assume the startup wasn't also leveraging typescript heavily on the frontend, that tends to shift the weight in its favor. Having one set of tools to use across everything, being able to share logic and types without needing to go through lossy translation layers, and giving (especially small) teams better flexibility to move people around is a huge benefit.
- CharlieDigital 8mo agoWe are using TS on the FE with React. But the reality is that at some point, your FE and BE teams will diverge anyways and we use an OpenAPI spec as the contract (Nest.js, not Next.js). So there was no benefit to using TS on both ends; only pain on the BE. If a team is going to ship an OpenAPI spec and run it through a transformer, then it changes the selection criteria for a BE language: - Easy for a TS team to adopt; similar core semantics like `async/await`, exception handling, etc. - Flexible and pluggable OpenAPI spec generation for edge cases and advanced scenarios - Excellent ORM to improve productivity around CRUD - Good tooling - Extensive docs, platform maturity, but modern language features C# meets all of those in ways that no other language and platform does.
- wbobeirne 8mo agoI can't speak to your use case, but for past projects I've not only wanted to share types but actual logic. For instance, if you want instantaneous validation on the frontend, while applying the same logic on the backend with submitted data. In many instances that would be simple and maybe even serializable, but in my case I was working with architectural data that had extremely detailed logic for what was and was not valid.
- Sammi 8mo agoUsing a TS validation library like Zod gives you both the shared validation code for both the frontend and backend, the TS types, as well as the json schema for openapi. It's a triple whammy too good to ignore. Especially as a small team it is huge leverage enabling you to go further faster.
- hirvi74 8mo ago> the "default" ORM is far more flexible and powerful I have never used any ORM that is as capable. Entity Framework Core with Linq is what keeps me on .NET.
- CharlieDigital 8mo agoBecause EF's default behavior implements Unit of Work, a LOT of the complex transactional spaghetti ended up disappearing when we switched. This aspect of EF is highly underrated for complex entity graph mutations. EF makes the 90% use case easy and the 10% case possible with very little pain. The interceptors, global conventions, and other extension points are an enabler of complex behaviors that are still transparent to most of the team.
- hirvi74 8mo ago> Because EF's default behavior implements Unit of Work, a LOT of the complex transactional spaghetti ended up disappearing when we switched. I have never felt more understood by a fellow HN user. I think I know the exact spaghetti you are talking about, and I agree with you 100%. I wish EF could create (SQL) views, but it's not really any issue considering I can just use raw SQL to accomplish the same thing. > complex entity graph mutations. I'm too dumb to know what those words mean together. But I going to assume it's something to do with complex entities. If so, I completely agree as well. I sometimes harness the powers of the Dark Magics where my domain entities and database entities are the same objects (I'm not one of those DDD people either). Thanks to EF, I've been able to create some complex objects that really have cut down on a lot of useless objects I used to litter applications with.