Do you need classes in JS/TS? 2025 version

Hi there folks! This is a revisited version of 2022 article: Do you need classes in JS/TS?. No AI was used neither for the article nor for the cover image :). If you are starting out as a JavaScript/TypeScript developer, you might run into these conflicting statements, like you should write "functional style" (FP) on the frontend, but "serious backend work requires object oriented development (OOP)". You might even encounter backend frameworks that are fully object-oriented. You might feel something is off, or there are too many steps required to do simple things, but you might not have the experience yet to pinpoint the problem. Let me help you with that! The point of using a class Let's roll back time and go back to an old language: C. The algorithm you are working on requires an implementation of a stack and you have to write one from scratch. You drop in elements into the stack and using a pop() function you retrieve the last element that was dropped into it. To implement it you would need the following things: some kind of storage that holds the items of the stack some way to deal with memory when you have a lot of item in your stack since it is not trivial to manage all that, you want the user to only use specific methods that you provide Therefore you will have the following: a private internal state that holds values between calls (the stack stays in memory and you can access at your leisure) a couple of hidden (private) methods to make sure that building blocks of the internal state is managed properly and the users won't base their code on the "Implementation of the Day" a couple of public methods that lets you interact with this unit and you could optionally have an interface which declares what are the mandatory bits of a stack so when you roll out the even more efficient StackV2 data structure it will be just a drop-in replacement Now imagine when this was a new thing! It was very elegant, very organized. People back then were dealing with very low level things like buffers, pointers, etc. and it made sense to have more of classes. The rise of Object Oriented Programming As with any trends, the industry got a bit overboard with it. When C# and Java arrived it was no longer possible to just declare a procedure or function without having a class; in my opinion it was a way to discourage people from Procedural Programming which was the old paradigm. But for dead simple data transformation code where you process data from type A to type B you do not have an internal state made of low level building blocks. Therefore, while OOP solved some very important problems it also introduced bureucracy that got imprinted in the brains of a generation that thinks mostyl in classes as the way for everything. Good example: database connection Managing access to a database usually needs maintaining some kind of a connection pool (reusing a number of connections to a database server). It also needs to track if we have already logged in or not, if the remote server is down and so on. But we don't want to know about all that, we just need to know how to configure the DB access and how to run queries. So those functions are the ones that are going to be our interface: a set of functions and properties that do not change frequently (or at all preferably) while want to keep it from peering eyes how we implemented the connection pool, etc. (making it private). Moreover, we will have to persist a state: how's the database access? Did we log in already, is the server up, etc. // An overly simplified database module interface Database { configure: (options: ConfigOptions) => void; connect: (user: string, password: string) => void; query: (query: string) => T; } class PostgresDatabase implements Database { // ... } // Same public methods but maybe very different concrete implementation! class MySqlDatabase implements Database { // ... } Bad example: REST handlers Let's say you have an FAQ page on your company website. Each user comes in and for wants to see an article. Here we maybe have a projection of (userId, articleId, language) => article. For this a simple function with its isolated scope would be a great choice as you don't want to persist state between calls. I have a little story. We were new to next.js about 6 years ago and 2 team separately made the same mistake: persisting a state between calls unwittingly. For my team, we managed to serve articles in a random language, depending on the last user's setting - as we were persisting in a local state the language, it was foolish of course. The other team had it worse: unlike us they didn't catch it in QA and they managed to deploy to prod a bug where users could see other users' private data because of the same general mistake. So here we don't want to keep a state between calls and our interface is also very simple: 1 function with a specific signature (see the projection above). We mig

Mar 19, 2025 - 06:51
 0
Do you need classes in JS/TS? 2025 version

Hi there folks!

This is a revisited version of 2022 article: Do you need classes in JS/TS?. No AI was used neither for the article nor for the cover image :).

If you are starting out as a JavaScript/TypeScript developer, you might run into these conflicting statements, like you should write "functional style" (FP) on the frontend, but "serious backend work requires object oriented development (OOP)".
You might even encounter backend frameworks that are fully object-oriented. You might feel something is off, or there are too many steps required to do simple things, but you might not have the experience yet to pinpoint the problem.

Let me help you with that!

The point of using a class

Let's roll back time and go back to an old language: C. The algorithm you are working on requires an implementation of a stack and you have to write one from scratch. You drop in elements into the stack and using a pop() function you retrieve the last element that was dropped into it.

To implement it you would need the following things:

  • some kind of storage that holds the items of the stack
  • some way to deal with memory when you have a lot of item in your stack
  • since it is not trivial to manage all that, you want the user to only use specific methods that you provide

Therefore you will have the following:

  • a private internal state that holds values between calls (the stack stays in memory and you can access at your leisure)
  • a couple of hidden (private) methods to make sure that building blocks of the internal state is managed properly and the users won't base their code on the "Implementation of the Day"
  • a couple of public methods that lets you interact with this unit
  • and you could optionally have an interface which declares what are the mandatory bits of a stack so when you roll out the even more efficient StackV2 data structure it will be just a drop-in replacement

Now imagine when this was a new thing! It was very elegant, very organized. People back then were dealing with very low level things like buffers, pointers, etc. and it made sense to have more of classes.

The rise of Object Oriented Programming

As with any trends, the industry got a bit overboard with it. When C# and Java arrived it was no longer possible to just declare a procedure or function without having a class; in my opinion it was a way to discourage people from Procedural Programming which was the old paradigm. But for dead simple data transformation code where you process data from type A to type B you do not have an internal state made of low level building blocks. Therefore, while OOP solved some very important problems it also introduced bureucracy that got imprinted in the brains of a generation that thinks mostyl in classes as the way for everything.

Good example: database connection

Managing access to a database usually needs maintaining some kind of a connection pool (reusing a number of connections to a database server). It also needs to track if we have already logged in or not, if the remote server is down and so on. But we don't want to know about all that, we just need to know how to configure the DB access and how to run queries. So those functions are the ones that are going to be our interface: a set of functions and properties that do not change frequently (or at all preferably) while want to keep it from peering eyes how we implemented the connection pool, etc. (making it private). Moreover, we will have to persist a state: how's the database access? Did we log in already, is the server up, etc.

// An overly simplified database module
interface Database {
  configure: (options: ConfigOptions) => void;
  connect: (user: string, password: string) => void;
  query<T>: (query: string) => T;
}

class PostgresDatabase implements Database {
  // ...
}

// Same public methods but maybe very different concrete implementation!
class MySqlDatabase implements Database {
  // ...
}

Bad example: REST handlers

Let's say you have an FAQ page on your company website. Each user comes in and for wants to see an article. Here we maybe have a projection of (userId, articleId, language) => article. For this a simple function with its isolated scope would be a great choice as you don't want to persist state between calls. I have a little story. We were new to next.js about 6 years ago and 2 team separately made the same mistake: persisting a state between calls unwittingly. For my team, we managed to serve articles in a random language, depending on the last user's setting - as we were persisting in a local state the language, it was foolish of course. The other team had it worse: unlike us they didn't catch it in QA and they managed to deploy to prod a bug where users could see other users' private data because of the same general mistake.

So here we don't want to keep a state between calls and our interface is also very simple: 1 function with a specific signature (see the projection above). We might need sub-functions that are implementation details but we can hide them neatly by not exporting them from the file.

OOP summary

As I hopefully demonstrated, while OOP is an excellent pattern for certain problems, it is not a magic bullet and can introduce added useless complexity and even be dangerous when used on the wrong thing. A couple of more examples:

  • If you build a game, all the enemies are little persisted states with things like health, abilities etc. Perfect sense to go with classes! ✅
  • If you deal with data structures, components with internal states, complicated algorithms that run for a long time and need to keep tab using a lot of little variables: classes to use! ✅
  • Data transformation: you had an API request, you need to simplify that value and pass that onto an analytic platform: you have no internal state, classes are a bad fit! ❌
  • Responding to API requests: you might have millions of concurrent requests, each of them should be isolated from each other: having an internal state is actually dangerous here, OOP is ❌

The clash of OOP with the language design of JavaScript

The tension between Java/C# style object-oriented programming and JavaScript lies in the fact that

Javascript (and consequentially TypeScript) is neither strictly functional nor an object oriented language.

(Note: TypeScript is not making JS into an object-oriented language! I heard this baffling myth a couple of times from people.)

It has elements of both but its implementation differs in very important aspects. For example if you used JS for a while you must have encountered curious problems around this. You passed a method of a class to a function as a callback and BOOM!