JavaScript gives you a lot of freedom when it comes to structuring and writing your code. In Node.js, you can write entire applications using just functions, or you can go with the object-oriented routes with classes. Both work, but they’re not always effective.
In this article, We will break down when and why it’s better to use classes instead of just functions in your Node.js project. If you want cleaner, more scalable, and testable code, read on.
Functions Are Great — But They Don’t Scale Alone
Let’s start with a common use case with creating a user and sending a welcome message.
Example using a function:
function createUser(name, email) {
return {
name,
email,
sendWelcomeEmail() {
console.log(`Welcome, ${this.name}`);
}
};
}
const user = createUser("Alice", "alice@example.com");
user.sendWelcomeEmail();
This works fine. But if you want to extend or reuse the logic, things start to getting messy and difficult.
Classes come with Structure and Reusability
The same example using a class with cleaner and more flexible.
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
sendWelcomeEmail() {
console.log(`Welcome, ${this.name}`);
}
}
const user = new User("Alice", "alice@example.com");
user.sendWelcomeEmail();
Why is this better?
- You don’t need to recreate methods every time.
- You can extend the classes (e.g.
AdminUser extends User
). - The code is easier to manage and scale.
Inheritance and Extensibility
Class make it simple to extend existing logic. Let’s see the example, how a Car
can extend a Vehicle
.
class Vehicle {
constructor(make) {
this.make = make;
}
start() {
console.log(`${this.make} engine started.`);
}
}
class Car extends Vehicle {
honk() {
console.log(`${this.make} says beep!`);
}
}
const car = new Car("Toyota");
car.start();
car.honk();
Converting from classes with functions, would require more boilerplate and it’s not readable.
Dependency Injection Becomes Easier with Class
In real-world Node.js apps, especially when working with databases or external services, you often required to inject dependencies. For that classes make this straightforward and easy.
class UserService {
constructor(database) {
this.db = database;
}
async getUser(id) {
return await this.db.findUserById(id);
}
}
Now testing is a breeze,
const mockDb = {
findUserById: async () => ({ id: 1, name: "Test" })
};
const service = new UserService(mockDb);
service.getUser(1).then(console.log);
This keeps your logic clean, decoupled, and testable.
Scalable Architecture Required Structure
As your app grows, It does the complexity. So, Classes help with:
- Keep related code bundled (state + behavior).
- Organize services, controllers, and models.
- Avoid “function soup” across multiple files.
For example, in an Express app, you might have authController
class AuthController {
constructor(authService) {
this.authService = authService;
}
async login(req, res) {
const token = await this.authService.login(req.body);
res.json({ token });
}
}
This is clean, testable, and extendable code— exactly what you want in a production app.
When You Shouldn’t Use Classes
Let’s be honest: not everything needs a class.
Use plain functions when:
- The logic is stateless (e.g.,
formatDate()
,slugify()
). - You don’t need inheritance or encapsulation.
- Simplicity is more important than structure.
Example:
function formatCurrency(amount) {
return `$${amount.toFixed(2)}`;
}
No need to engineer simple logic.
Summary: When to Use Classes in Node.js
Use Classes When… | Use Functions When… |
---|---|
You manage state | Logic is stateless |
You need reusability or inheritance | You need quick, simple logic |
You’re building services/controllers | You’re writing utilities |
You want easier testing via dependency injection | No need for OOP features |
In Node.js projects, classes bring structure, reusability, and scalability to your codebase. They shine when you’re working with services, models, or anything that holds state and behavior. Functions still have their place, but as your app grows, so should your architecture.
Know when to use each — that’s what separates good code from maintainable code.