π What is the Null Object Pattern?
The Null Object Pattern is a behavioral design pattern where:
- Instead of returning 
null(which can cause errors if used without checking), - You return a special object that does nothing but still behaves like a real object.
 
β
 This avoids if (obj != null) checks everywhere in your code
β
 It helps follow Open/Closed Principle (extend behavior without changing logic)
π¨ Real-World Examples (Simple to Visualize)
π€ 1. Unknown User (Guest)
- 
Instead of: Returning 
nullfor unauthenticated users - 
Do this: Return a 
GuestUserobject that has safe defaults (no login, read-only) 
π§ 2. Empty Cart
- 
Instead of: 
if (cart) { cart.checkout() } - 
Do this: Use 
EmptyCartobject with.checkout()method that logs "No items" 
ποΈ 3. Logger
- A 
ConsoleLoggerlogs to the console - A 
NullLoggerjust does nothing (used in production or testing) 
π§ Why Use It?
β
 Avoid runtime errors from null
β
 Reduce code clutter from if (obj !== null)
β
 Improve readability & testability
β
 Follow Polymorphism instead of conditionals
π§± TypeScript Example β Logger Pattern
Let's build a real use case: a logger.
1. Define Logger Interface
interface Logger {
  log(message: string): void;
}
2. Real Logger (Console)
class ConsoleLogger implements Logger {
  log(message: string): void {
    console.log(`[LOG]: ${message}`);
  }
}
3. Null Logger (does nothing)
class NullLogger implements Logger {
  log(message: string): void {
    // do nothing
  }
}
4. Service Using Logger
class UserService {
  constructor(private logger: Logger) {}
  createUser(name: string) {
    // ... create logic
    this.logger.log(`User created: ${name}`);
  }
}
5. Use It Without Null Check
const consoleLogger = new ConsoleLogger();
const nullLogger = new NullLogger();
const service1 = new UserService(consoleLogger);
service1.createUser("Alice");  // logs: User created: Alice
const service2 = new UserService(nullLogger);
service2.createUser("Bob");    // logs nothing, no error
β
 No need to check if (logger != null) β just inject the correct behavior.
π‘ Better Than Null Check
Instead of:
if (logger) {
  logger.log("something");
}
With Null Object:
logger.log("something"); // always safe!
π Real-World Use Cases
| Context | Null Object | 
|---|---|
| Logging | 
NullLogger (test env, prod) | 
| Payment | 
NullPaymentProcessor (free plan) | 
| User | 
GuestUser (not signed in) | 
| Cart | 
EmptyCart (user hasn't added anything) | 
| Strategy | 
NoOpStrategy for default behavior | 
π― Pro Tips for Mid-to-Senior Devs
β
 Replace null/undefined returns with Null Object classes
β
 Let Null Object implement the same interface
β
 Makes code cleaner, testable, and follows polymorphic behavior
π§ͺ Testability Bonus
In unit tests, instead of mocking a logger:
const service = new UserService(new NullLogger());
No mocks needed β just use the Null Object!
π Final Summary
"The Null Object Pattern replaces null with an object that safely does nothing β reducing checks and avoiding errors."
Perfect! Letβs now apply the Null Object Pattern in a NestJS service layer β a common place where it adds a lot of clarity and safety. Iβll walk you through a practical example slowly and clearly.
β Use Case: Optional Logger in a NestJS Service
Letβs say you have a UserService that logs events (like user creation), but sometimes you donβt want logging (e.g., during tests or in certain environments). You donβt want to check if (logger) everywhere.
π§± Step-by-Step: Null Object Pattern in NestJS
πΉ 1. Create a Logger Interface
// logger/logger.interface.ts
export interface LoggerService {
  log(message: string): void;
}
πΉ 2. Real Logger Implementation
// logger/console-logger.service.ts
import { Injectable } from '@nestjs/common';
import { LoggerService } from './logger.interface';
@Injectable()
export class ConsoleLoggerService implements LoggerService {
  log(message: string): void {
    console.log(`[LOG] ${message}`);
  }
}
πΉ 3. Null Logger (does nothing)
// logger/null-logger.service.ts
import { Injectable } from '@nestjs/common';
import { LoggerService } from './logger.interface';
@Injectable()
export class NullLoggerService implements LoggerService {
  log(message: string): void {
    // do nothing
  }
}
πΉ 4. Inject Logger into Your Service
// user/user.service.ts
import { Injectable } from '@nestjs/common';
import { LoggerService } from '../logger/logger.interface';
@Injectable()
export class UserService {
  constructor(private readonly logger: LoggerService) {}
  createUser(username: string) {
    // ... your real user creation logic
    this.logger.log(`User created: ${username}`);
  }
}
πΉ 5. Provide Either Logger in Your Module
// app.module.ts
import { Module } from '@nestjs/common';
import { UserService } from './user/user.service';
import { ConsoleLoggerService } from './logger/console-logger.service';
import { NullLoggerService } from './logger/null-logger.service';
import { LoggerService } from './logger/logger.interface';
@Module({
  providers: [
    UserService,
    {
      provide: LoggerService,
      useClass:
        process.env.NODE_ENV === 'test' ? NullLoggerService : ConsoleLoggerService,
    },
  ],
})
export class AppModule {}
β Now:
- In test env, it uses 
NullLoggerService(no logs, no noise) - In prod/dev, it uses 
ConsoleLoggerService(full logs) 
π― Why This Works Well
| Benefit | How It Helps | 
|---|---|
β
 Avoid null checks | 
No if (logger) needed anywhere | 
| β Clean DI | Swap behaviors easily at runtime | 
| β Safe by default | 
NullLogger never throws | 
| β Open/Closed Principle | Add new loggers without changing service logic | 
| β Testing-friendly | Inject NullLoggerService in tests, no mocking needed | 
π§ͺ Test Usage (Easy)
const module = await Test.createTestingModule({
  providers: [
    UserService,
    { provide: LoggerService, useClass: NullLoggerService },
  ],
}).compile();
const service = module.get(UserService);
service.createUser('Alice'); // runs silently
π§ You Can Extend This Pattern To:
- 
AnalyticsService β 
NullAnalyticsService - 
EmailService β 
NullEmailService - 
CacheService β 
NullCacheServicefor dev mode - 
NotificationService β 
NullNotificationService 
π§΅ Final Summary
In NestJS, use the Null Object Pattern by creating default "no-op" services that follow the same interface as real ones β this simplifies logic, removes conditionals, and improves testing and runtime flexibility.
              
    
Top comments (0)