After five-and-a-half years building production platforms across Node.js, .NET, and Go, I keep coming back to .NET for backend work. It's not nostalgia or hype—it's a deliberate choice backed by real constraints: shipping fast, maintaining code over years, and keeping infrastructure costs reasonable. Let me walk you through why I still choose .NET for backends, and why that choice has only gotten stronger.
Top-tier performance out of the box — Kestrel is fast
When I started using .NET Core seriously around 2018, Kestrel wasn't just "good enough." It was fast. We're talking sub-millisecond latency on simple request-response cycles, and that's before you even touch performance tuning.
I remember a 2020 project where we rebuilt a Node.js API gateway in ASP.NET Core. Same hardware, same endpoint shape. The .NET version handled 3× the throughput at lower memory. We didn't hire a performance expert or spend weeks optimizing—the baseline was just that good. Kestrel, the HTTP server bundled with ASP.NET Core, is written in managed C# with direct socket access. It's battle-tested at scale.
Here's a bare-minimum endpoint to show what I mean:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/users/{id}", async (int id, ILogger<Program> logger) =>
{
logger.LogInformation("Fetching user {UserId}", id);
// Direct I/O, no extra marshalling, minimal allocations
return Results.Json(new { Id = id, Name = "Alice" });
});
app.Run();
This is fast by default. No middleware chains you didn't ask for, no express-style routing layers adding latency. You'll often see ASP.NET Core in TechEmpower benchmarks near the top, competing with Rust and Go implementations. That matters when you're handling thousands of requests per second and your per-request budget is counted in microseconds.
In production ride-hailing and real-time trading systems I've worked on, that baseline performance meant we could handle traffic spikes without scaling the cluster as aggressively. Lower memory per instance, fewer containers, lower AWS bills. The compounding effect over a year is not trivial.
One cohesive stack: DI, config, logging, EF Core
Node.js excels at tiny, composable modules. That's also its curse: every project becomes a hunt for the "right" logger, the "right" ORM, the "right" HTTP client. You're gluing together ten different opinionated libraries, hoping they play nicely.
.NET took the opposite path. The framework ships with dependency injection, structured logging, configuration management, and an excellent ORM. You don't need to argue about it or swap things out every six months. It's baked in, it's boring, and that's the point.
Here's what onboarding a junior developer looks like in a .NET team:
// Program.cs — one file, everything declared upfront
var builder = WebApplication.CreateBuilder(args);
// DI is built-in
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<UserService>();
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"))
);
// Logging: structured, zero config needed
builder.Logging.AddConsole();
var app = builder.Build();
// Middleware pipeline is declarative
app.UseHttpLogging();
app.MapControllers();
app.Run();
New team members know exactly where to find database configuration, where to register services, how logging flows. There's no decision fatigue. Contrast this with a Node.js project where someone used tsyringe for DI, another used inversify, another just exports singletons. After a few years, these inconsistencies accumulate as technical debt.
Entity Framework Core deserves its own mention. I've used Prisma, TypeORM, Sequelize, and raw SQL in various projects. EF Core is the only ORM where I trust it with complex queries and migrations at scale. Its LINQ API is type-safe, composable, and generates efficient SQL. I've debugged fewer query performance issues in EF Core than any alternative.
| Concern | .NET built-in | Node.js typical approach |
|---|---|---|
| DI container | Microsoft.Extensions.DependencyInjection | Pick one: tsyringe, inversify, manual |
| Logging | ILogger<T> with structured output | Pick one: winston, pino, bunyan |
| Config management | IConfiguration + appsettings.json | dotenv, custom loaders, or manual |
| ORM | Entity Framework Core | Pick one: Prisma, TypeORM, Sequelize |
The time I save not debating and not context-switching between competing libraries adds up. Across five+ years, that's probably months of engineering time I've reclaimed.
C# keeps getting better every November release
C# releases a major version every November. It's one of the few languages where the evolution is predictable. You know exactly when new features land, and they're almost always practical improvements to your day-to-day code.
C# 11 (November 2022) gave us required members, file-scoped types, and raw string literals. C# 12 (November 2023) added collection expressions and primary constructors. These aren't flashy—they're boring, grinding improvements that make code more readable and reduce boilerplate.
Compare this to Go, where generic types took a decade to arrive, or Python, where async/await still feels bolted on. C#'s evolution is intentional and pragmatic.
Here's C# 12 in action:
// Primary constructor (C# 12) — no need for backing fields
public class User(int id, string name, string email)
{
public int Id { get; } = id;
public string Name { get; } = name;
public string Email { get; } = email;
}
// Collection expressions (C# 12) — cleaner initialization
var numbers = [1, 2, 3, 4, 5];
var combined = [..numbers, 6, 7];
// File-scoped types (C# 11) — internal types without "internal" keyword
file class InternalHelper { }
Smaller improvements compound. Your codebase becomes less verbose, more expressive, and new features rarely require breaking changes. You upgrade your .NET SDK one November, and suddenly you're writing terser, clearer code without rewriting existing functionality.
Boring, stable LTS releases are a feature for businesses
This is the one that doesn't get enough credit. .NET ships LTS (Long Term Support) versions every other year: .NET 6, .NET 8, and coming .NET 10. These are supported for three years. You can update .NET 6 to .NET 6.1, .NET 6.2, etc., with near-zero breaking changes. The same is true for all patch and minor versions within an LTS release.
If you're running a CRM or e-commerce platform that's not actively being rewritten, that stability is gold. You can postpone major version upgrades for years without accumulating security risk (because patch releases stay current). Your infrastructure team can focus on features, not firefighting dependency hell.
I've run production .NET 6 applications for three years without touching the version number. Same code, same performance. Try that with Node.js where a minor version bump can introduce unexpected breaking changes, or where unmaintained dependencies in your tree suddenly vanish from npm.
For a SaaS platform with hundreds of customers, that stability means fewer prod incidents, more predictable release cycles, and lower operational burden. Your DevOps team sleeps better.
Common mistakes
Mistake 1: Over-abstracting your DI. .NET's DI is so clean that teams sometimes wrap it in "custom abstractions." Your boss then asks why there's a ServiceFactory that creates ServiceFactoryManagers. Don't do that. Use what's built-in.
Mistake 2: Ignoring EF Core performance opportunities. EF Core is fast, but it's not automatic. N+1 queries happen if you're not careful. Always profile with .Include(), AsNoTracking(), and batch operations where needed. It's your responsibility, not the ORM's failure.
Mistake 3: Choosing .NET for a one-off script or CLI tool. For that, Go or Python is usually faster to write. .NET shines when you're building and maintaining systems over years, not quick prototypes.
Mistake 4: Ignoring async/await patterns. ASP.NET Core is async by design. If you're blocking threads with .Result or .Wait(), you're defeating the entire concurrency model and will hit scaling limits faster than you expect.
Key takeaways
- Kestrel gives you world-class HTTP server performance without extra work; this translates directly to lower cloud costs and better user experience at scale.
- A cohesive, opinionated framework (DI, logging, config, ORM) eliminates decision fatigue and reduces technical debt accumulation over years.
- C# improves every November with pragmatic, non-breaking features; your codebase gets better without refactoring.
- LTS releases mean you can stay stable and secure for three years, reducing the overhead of constant dependency upgrades.
- .NET wins for platforms and services you maintain long-term; it's less ideal for throwaway scripts or tiny microservices.
That's why, after five-and-a-half years and exposure to solid alternatives, I still choose .NET for backends. It's not the flashiest choice, but it delivers on what matters: reliability, performance, and sustainable pace for teams building real systems.





Comments
Loading comments…