The Single Language Productivity Myth

The Single Language Productivity Myth

It's great, because you can use the same language on the frontend and on the backend!

I don't think its great, actually.

This is a pretty common assumption in the javascript world.
You gain productivity, because you use the same language on the frontend and the backend.
But why exactly? How do you gain productivity? And how much?

Some common arguments and baked in assumptions are

  • You only have to learn a single language
  • You can share code between frontend and backend
  • You get type safety between backend and frontend
  • Easier deployment

It's hard to disagree with any of these in theory.
All else being equal, everyone should choose to write the frontend and the backend in the same language.

But all else is not equal...

The Arguments

You only have to learn a single language

In any real project, you already use so many languages and tool.

  • Html
  • Css
  • Javascript
  • Typescript
  • Tailwind
  • SQL
  • Prisma
  • Vite
  • Yaml
  • Docker
  • Vercel
  • Serverless vs Server
  • 8 .config.js files

You switch between so many languages, tools and systems constantly. One more is not gonna kill your productivity.

Knowing "frontend" javascript gives you almost no advantage once want to create a server that takes in requests and responds back with json.

If you want to learn that you have to learn

  • SQL
  • How to model your tables / entities
  • Prisma (ORM)
  • How to authenticate on the server
  • How to run long running, expensive stuff in the background
  • How do you even route incoming requests?
  • How do you respond back with json?
  • How do you respond with a 404? When do you respond with a 404?
  • How do you do middleware?
  • How do you accept a file upload and store it somewhere?
  • How do you generate an openAPI definition? What even is that?
  • Wtf is this cors thing?
  • Where do you host it?
  • How do you set up your pipelines to deploy automatically?

Imagine having to figure all these out for the first time in a node app vs a c# app.

No matter how experienced you are in frontend javascript, you gain basically nothing by choosing node over c# over go over django.

Knowing react does not significantly improve your speed in learning or implementing any of these.


You can share code between frontend and backend

Yea and you probably shouldn't. I've seen this on the javascript and the .net side.

It's amazing in theory, it just doesn't really work out in reality. What happens most of the time is that one class tries to do too much.

This is especially appearant in the case of entity classes that also describe the shape of the database tables.

We have the Post class which created the Posts table via our ORM.
Then we have the get/posts/1 endpoint which returns a Post naturally.
Of course we also display posts in the frontend and look at that we have a perfect class for that Post.

Now wait, this Slug property on the post can never be empty on the database level. Lets add an attribute.
And when you create a post via the post/Post endpoint we want to make sure the Author is always filled, so we add some validation with an attribute.
Oh we also need to tell the user so lets add a client side attribute too somehow, with internationalization, of course.

This goes out of control every single time.
Most people have caught onto different dto or model classes by now.

Especially validation trips people up, because they think about "the" validation.
But there are actually 3 different types of validation here. Client side, api level, database level.

These 3 levels of the system just don't map 1:1 and if you try to make it work (which sadly you can) you will find yourself drowning in complexity.

You get type safety between backend and frontend

This is just not really a big deal with modern tools like openAPI / swagger.

I have type safety between my AspNetCore backend and my NextJs frontend too.
I just have to generate a client from my automatically generated openAPI contract with one command.
npx swagger-typescript-api -p ./../backend/Schema.yaml -o ./src/api -n api.ts --axios

and now I call my typesafe endpoints like this
let launches = await api.getTodaysLaunchesEndpoint();

Easier deployment

Honestly, yea.
It definitely is easier to deploy a singular nextjs app than a nextjs app + a backend server in c#, go or even javascript.

However figuring it out shouldnt cost you any more than a day. That's if you never done it before.

How to choose then?

So, if none of these arguments hold, how do you pick your backend then?

Look at the actual programming language.
Look at the eco system.
See how it feels to write c# vs javascript vs python.

This is how you write a query that gets the newest posts from the rss feeds a user is subscribed to.

Prisma / Javascript

const posts = await prisma.post.findMany({
  where: {
    feed: {
      appUsers: {
        some: {
          id: currentUser.id,
        },
      },
    },
  },
  orderBy: {
    publishedAt: 'desc',
  },
  take: req.limit ?? 200,
  select: {
    title: true,
    description: true,
    publishedAt: true,
    url: true,
    feed: {
      select: {
        name: true,
      },
    },
  },
});

EF Core / C#

var posts = await context.Posts
    .Where(x => x.Feed.AppUsers.Any(u => u.Id == currentUser.Id))
    .OrderByDescending(x => x.PublishedAt)
    .Take(req.Limit ?? 200)
    .Select(x => new PostDto(x.Title, x.Description, x.PublishedAt, x.Url, x.Feed.Name))
    .ToListAsync();

Pure SQL

SELECT
    p.Title,
    p.Description,
    p.PublishedAt,
    p.Url,
    f.Name
FROM
    Posts AS p
INNER JOIN
    Feeds AS f ON p.FeedId = f.Id
WHERE
    EXISTS (
        SELECT 1
        FROM AppUsers AS u
        INNER JOIN FeedAppUsers AS fau ON u.Id = fau.AppUserId
        WHERE fau.FeedId = f.Id
        AND u.Id = @CurrentUser_Id
    )
ORDER BY
    p.PublishedAt DESC
LIMIT
    @Limit;

How does knowing how to create a react context to share data between multiple components help you here?

This was not a cherry picked example. It was the first query I picked from my RssFeedAggregator sample repository.

Earlier, when I was copying the code to create these blogs in mdx files from the shadcn/taxonomy repository, I saw this code and couldn't believe it.

import { compareDesc } from "date-fns"
 
const posts = allPosts
  .filter((post) => post.published)
  .sort((a, b) => {
    return compareDesc(new Date(a.date), new Date(b.date))
  })

This probably looks normal to javascript mains.
In no universe should sorting an array by 1 field on an object be a 3 line 2 parameter operation.
That also requires an additional library for working with dates.

I know you can write it in one line. But one of your other little tools, called prettier, won't let you.

var posts = allPosts
  .Where(post => post.Published)
  .OrderByDescending(post => post.Date);

This is how you get started with accepting http requests and responding back with json in js, go and c#.

app.get('/hello', (req, res) => {
    res.status(200).json({ message: 'Hello, World!' });
});
 
http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, World!")
})
 
app.MapGet("/hello", () => {
    return Ok("Hello, World!");
});

The languages and frameworks are all consolidating on this type of syntax.
Go and c# can come up with syntax sugar, where javascript just can't.

Other languages are not as scary as you think.

Their programming languages are superior.
Their standard libraries are superior.
Their eco systems are superior for doing backend work.

The productivity you think you lose by writing c# instead of javascript is more than made up by the eco system, the tools and the language.

You will also just be a better programmer with a more clear understanding of the difference between client and server.

This might be obvious for more experienced people, but I remember starting my first job doing webforms in aspnet.
The first couple of months I just didn't have the concept of client and server, because everything is just... there.
I just changed this bit of logic and then it did what I needed it to do.

Where does it run? I don't even have the understanding to think of the question yet.
Beginners doing NextJS or react server components will 100% run into the same issue.

Final Thoughts

The javascript eco system is amazing for building frontends.
JSX is awesome. Tailwind is awesome. Instant visual feedback when you save is awesome.
All the meta frameworks like NextJS and Svelte with their magic reactivity, the server side rendering, static caching and routing give you an insane amount of power for very little investment.
Libraries like TanStack Query or shadcn or next-auth work great.
The landing pages I can just steal from github that come with perfect responsiveness and little animations are great.

Incredible engineers have made javascript on the server bearable.
But that's it. That's all it is and all it ever will be.

Javascript on the server does not compare to any of the languages purpose made for it.

I genuinely feel sorry for people who got tricked into writing javascript on the server. I've seen what it does to people.

Just try it. Pick a backend language and see how it feels.

You will be more productive, not less.