Building up from JavaScript to TypeScript to C# 10 and .NET 6
As I’ve worked with JavaScript and TypeScript on Node backends over the last year, there was always a point in the project when I wonder “This would be way better in C# and .NET 6”. When proposed to various teams, the responses I’ve gotten have been interesting.
There’s Been a…Misunderstanding
The biggest challenge is that many recently minted engineers — and frankly even engineers who may have looked at C# and .NET even just 5 or 6 years ago — have a complete misunderstanding of where C# and .NET are today. (Quite frankly, Microsoft historically hasn’t done a great job with respect to the identity of .NET with “.NET Framework”, “.NET Standard”, “.NET Core”, and now just “.NET”.)
Of course, there are several “myths” about C# and .NET that are simply no longer true as the .NET Framework has given way to .NET Core and now simply .NET 6. However, many of those platform misunderstandings persist and engineers and teams without exposure to C# simply do not realize how trivial the lift is between TypeScript and C#.
When discussing the possibility of considering C# instead of TypeScript on one project with another developer, he stated that he always thought C# was more like C/C++ and was surprised by how closely it resembled TypeScript upon looking at C# more closely.
Others have expressed a concern that the lift from JavaScript to C# is too high and not feasible with the existing developers on the team yet are totally on board with TypeScript after suffering the challenges of working with JavaScript on the server at scale. There is a sense that learning and adopting TypeScript is feasible, but learning C# is somehow infeasible. The truth is that, in fact, C# is probably the easiest of Go, Rust, and Java for JavaScript and TypeScript developers to adopt.
I personally think that for most JavaScript developers, the lift from JavaScript to TypeScript is much more significant than the lift from TypeScript to C# to the extent that a team choosing to start a greenfield backend project in TypeScript should evaluate C# as well.
When All You Have is a Hammer…
As the old saying goes, when all you have is a hammer, every problem looks like a nail.
In the last decade, as demand for software developers has grown, it has become more economical and efficient to train developers in a single programing language and anoint them as “full-stack” engineers. This language has been JavaScript. But the reality is that working with modern JavaScript, especially on the server, has many shortcomings and challenges (if I’ve piqued your interest, I’ve written more extensively on this topic). Yet many younger developers are not equipped to really consider other options because of just how pervasive Node has become as the first and often the only runtime environment developers are familiar with.
Even Ryan Dahl, the creator of Node, had this to write about Node in 2012 (and by extension, JavaScript):
I want programming computers to be like coloring with crayons and playing with duplo blocks. If my job was keeping Twitter up, of course I’d using a robust technology like the JVM.
Node’s problem is that some of its users want to use it for everything? So what? I have no interest in educating people to be well-rounded pragmatic server engineers, that’s Tim O’Reilly’s job (or maybe it’s your job?).
In fact, Dahl would later expound on the many regrets he had with respect to Node:
And go on to create Deno to address many of those gaps. Notably, Deno is built on TypeScript and not JavaScript. It also focuses on security and improved dependency management; we all know the pain of working with node_modules
and fretting over (or more likely ignoring) the latest batch of vulnerability warnings on build. My favorite take on this is from Erlang The Movie II: The Sequel:
I like Node.js because as my hero Ryan Dahl says it’s like coloring with crayons and playing with Duplo blocks, but as it turns out it’s less like playing with Duplo blocks and more like playing with Slinkies. Slinkies that get tangled together and impossible to separate.
🤣
Duplo, Lego, and Technic
I love Dahl’s analogy of Node and JavaScript to Duplo because it works on so many levels. For the unfamiliar, Duplo is a chunky building block toy made by Lego which is designed to be easy to handle for young builders who lack the dexterity and fine motor skills to work with Lego properly; they are easier to snap together and easier to take apart. Duplo are often a young builder’s first introduction to Lego:
Working with Node and NPM is often like snapping blocks into place and with JavaScript and Node, those blocks have been designed for ease of handling rather than the ability to build complex structures. This is not to say that one cannot build complete and complex applications with JavaScript and Node, but that doing so involves compromises because the tool has fundamental limitations and challenges.
Of course, there comes a time when every young builder is ready to move on to regular Legos.
The pieces are smaller, more varied, more nuanced, and require more dexterity to work with; the sets themselves become more complex with higher piece counts. There are more mechanisms for the pieces to be connected. But it is easier to build more sophisticated constructs with Lego than with Duplo. Likewise, there comes a time in every team when TypeScript becomes a necessity to support the complexity of the construct being built or the size of the team doing the building.
It is possible to build and engineer incredibly complex structures with Lego, yet Lego produces another tier of building blocks known as Lego Technic and Lego Architecture that further extends the creative possibilities and the complexity of the structures that can be built.
There are even more varied pieces, more specialized pieces, motorization units, and so on which allows for the construction of elaborate and complex structures. While it is possible to build this same freight scene with Duplo or Lego, there is an undeniable richness that exists in the Technic version; there is a higher ceiling for creative outlet. And it is for this reason that teams seeking to build high performance systems should evaluate .NET and C#.
While Technic provides a higher ceiling to what can be constructed, one can argue that this comes at the cost of complexity. Yet it clearly remains undeniably Lego-like and it is easy to see the progression from Duplo to Lego to Technic whereas Plus-Plus building blocks clearly adopt a different paradigm (interesting fact: Lego, Plus-Plus, TypeScript, and C# were all created by Danes 🇩🇰!).
Likewise, there is a clear progression from JavaScript to TypeScript to C#. For developers ready to make the lift to TypeScript, the gap to C# and .NET is really not that far as the languages — JavaScript, TypeScript, and C# — share a common lineage and observant developers will note that they have been converging since .NET 2.0.
The More Things Change…
I have worked with JavaScript for close to 24 years now and C# for 19 and what has been interesting to me is how they have converged over time. I first really noticed it with C# 3.0 and the introduction of var
. That release also introduced object and collection initializers in a similar style to JavaScript.
Since C# 3.0’s release in 2007, the language has overall been trending towards more type inference and less explicit typing. While obviously not the same as JavaScript’s fully dynamic type system, there is a syntactic congruence.
(I’ve published a small sample repository that highlights some of these congruencies:)

C# even supports dynamic types (equivalent of var x = {}
) using — what else — the dynamic
type (aka ExpandoObject
) so it’s possible to use some dynamic techniques. I recently used it with the Jint library to build a simple JavaScript powered rules engine in .NET. It can even be used to implement double-dispatch style Visitor pattern.
Few are aware that C# 3.0 introduced arrow functions and LINQ expressions in 2007 — 8 years before JavaScript would introduce arrow functions and lambda expressions in 2015 with ES6.
Like JavaScript, functions are first class objects in C# via the Func
and Action
types (see line 22 on the right in the image). So you can pass, return, and invoke function references just as you would in JavaScript or TypeScript (see line 42 on the right).
You can also notice that C#’s async/await
(released with C# 5.0 in 2012), is largely identical to JavaScript’s (released with ES2017) with the exception of Task
versus Promise
.
C#’s try-catch-finally
exception handling is almost identical to JavaScript, but it is a bit more sophisticated since you can catch and handle specific types of exceptions using multiple catch()
blocks whereas JavaScript can only use a single catch()
and then check the type of the error.
C#’s deconstructing (see line 38 on the right above) and discards are analogues to JavaScript’s very own.
C# even has local functions (in largely congruent styles; see line 48–56 on the right below):

This listing below is indeed, C# (right) and the TypeScript equivalent on the left:

It should be noted that C# lambda closures behave differently from JavaScript closures.
This:
Does not behave like this:
Which behaves like this:
(Google’s TypeScript style guide actually discourages the use of Array.prototype.forEach
and encourages the use of for-of
to iterate)
C# closures don’t have JavaScript’s issues with this
which makes it much easier to work with C# in my opinion because you never have to worry about usage of this
and complex management of binding this
correctly.
The three languages are so similar that when teams consider TypeScript on the backend (especially Nest.js), I recommend at least taking a look at .NET 6 Web APIs because the lift for most JavaScript developers to something like Nest.js with advanced concepts is pretty much 80% of the way to C# and .NET without all of the performance, runtime, and language benefits. Teams switching to .NET can save themselves a lot of headaches down the line with package churn, constant patching for security, and painful node_modules
management.
Ultimately, what I see happening with JavaScript, TypeScript, and C# is that the three languages are converging. JavaScript is in the late stages of implementing decorators like C# attributes (though there is some nuanced difference with C# attributes which are consumed via reflection). C# recently received pattern matching which I think we’ll see in JavaScript at some point in the future. TypeScript of course adds strong compile time checks, generics, and advanced structural code patterns (interfaces, abstract classes, statics, private members) on top of JavaScript’s class
construct like C#. C# will likely be getting discriminated union types similar to TypeScript’s type unions in the near future, especially since C#’s sister language F# already has it. C#’s Language Integrated Query (LINQ) libraries make it possible to work with collections with even greater richness than JavaScript arrays. JavaScript’s yield
serves the same purpose as C#’s yield
.
And of course, there has been a strong push to add opt in typing to JavaScript.
My friend Arash Rohani said:
And to be fair the reason that .NET and C# is good now is because they have borrowed good concepts from other languages on top of the good things that they had already
Indeed, in recent years, the C# and .NET team have been — all things considered — very quick to evolve the language compared to Java and even JavaScript. Heck, Node just got native fetch
.
.NET’s CLI tooling is now also very similar to the Node ecosystem and largely congruent:
dotnet new webapi
dotnet add package serilog # Equiv of npm install winston
dotnet build # Equiv of npm run build <-- build script
dotnet run # Equiv of npm run start <-- run script
dotnet watch # Equiv of running with watch
dotnet test # Equiv of npm run test <-- test script
There has been a lot of old-man-yelling-at-clouds from the “fuddy-duddies” within the .NET community with respect to the continuous evolution of C# and .NET. I myself am guilty of it. But each iteration of C# has only deepened my fondness of the language and platform, especially when I find myself fighting against Node, NPM, and the limitations of JS/TS.
Making the Lift
As Dahl implied, it is important for engineers and technical leaders to pick the right tool for the task at hand. JavaScript and TypeScript on Node are fantastic tools to build with for rapidly building applications. Like Duplo, the blocks are easy to handle, especially for inexperienced builders and certainly there is an advantage for newly minted developers to be be able to program full stack with one language.
For performance at scale, platform security and stability, operational manageability, as well as overall scalability, C# and .NET’s close lineage with JavaScript and TypeScript provide a clear path for building more complex systems. C#’s congruency with TypeScript means that the lift from TypeScript to C# isn’t nearly as onerous as some would think; it’s easy enough to start with C# — especially using the minimal APIs — to provide your team a higher ceiling. You don’t have to build using all of the advanced features of C# and .NET, but you retain the flexibility to incrementally add more performance (e.g. Task Parallel Library) and complexity as needed.
In a future article, I want to highlight some of the key features of C# and .NET which make it worthwhile for teams to consider the platform; it’s not just raw performance and security, but also developer productivity.
It has been a long 20 year journey from the .NET Framework to to C# 10 and .NET 6 and the platform’s transformation that started with .NET Core has now been fully realized. For teams that have been feeling the growing pains with JavaScript and TypeScript on Node, there’s never been a better time to evaluate C# and .NET!
Hit Follow if you’d like to be notified about my upcoming article highlighting some of the key advantages of C# and .NET for backends.