Bring Spring-Style AOP to NestJS with nestjs-saop

Implement powerful Aspect-Oriented Programming in your NestJS applications with complete TypeScript support and familiar Spring-like syntax.

If you’ve worked with Spring Framework, you’ve probably enjoyed its powerful AOP (Aspect-Oriented Programmi…


This content originally appeared on DEV Community and was authored by miinhho

Implement powerful Aspect-Oriented Programming in your NestJS applications with complete TypeScript support and familiar Spring-like syntax.

If you've worked with Spring Framework, you've probably enjoyed its powerful AOP (Aspect-Oriented Programming) capabilities. AOP helps you cleanly separate cross-cutting concerns like logging, caching, security, and transaction management from your business logic, making your code more maintainable and readable.

What if you could bring that same elegant AOP experience to your NestJS applications? Enter nestjs-saop – a library that brings Spring-style AOP to the NestJS ecosystem with full TypeScript support.

What is nestjs-saop?

nestjs-saop is a comprehensive AOP library for NestJS that provides:

Complete AOP Advice Types: All 5 Spring-style advice types (Around, Before, After, AfterReturning, AfterThrowing)

Full TypeScript Support: Type safety with generics and interfaces

Seamless NestJS Integration: Works perfectly with the NestJS module system

Decorator Pattern: Clean, familiar decorator-based API

Flexible Configuration: Highly configurable with custom options support

Quick Setup

Install the package:

npm install nestjs-saop

Register the AOP module in your app:

import { AOPModule } from 'nestjs-saop';

@Module({
  imports: [
    AOPModule.forRoot(),
  ],
})
export class AppModule {}

Creating Your First AOP Decorator

Let's create a logging decorator that demonstrates all AOP advice types:

import { AOPDecorator, Aspect } from 'nestjs-saop';

@Aspect()
export class LoggingDecorator extends AOPDecorator {
  around({ method, options }) {
    return (...args: any[]) => {
      console.log('🔄 Around: Before method execution');
      const result = method.apply(this, args);
      console.log('🔄 Around: After method execution');
      return result;
    };
  }

  before({ method, options }) {
    return (...args: any[]) => {
      console.log('▶️ Before: Method called with', args);
    };
  }

  after({ method, options }) {
    return (...args: any[]) => {
      console.log('⏹️ After: Method completed');
    };
  }

  afterReturning({ method, options, result }) {
    return (...args: any[]) => {
      console.log('✅ AfterReturning: Method returned', result);
    };
  }

  afterThrowing({ method, options, error }) {
    return (...args: any[]) => {
      console.error('❌ AfterThrowing: Method threw', error.message);
    };
  }
}

Register your decorator as a provider:

@Module({
  providers: [LoggingDecorator],
})
export class AppModule {}

Real-World Examples

1. Smart Caching Decorator

@Aspect()
export class CachingDecorator extends AOPDecorator {
  private cache = new Map();

  around({ method, options }) {
    return (...args: any[]) => {
      const key = `${method.name}:${JSON.stringify(args)}`;

      // Cache hit
      if (this.cache.has(key)) {
        console.log('💡 Cache hit!');
        return this.cache.get(key);
      }

      // Cache miss - execute method
      console.log('💭 Cache miss, executing method...');
      const result = method.apply(this, args);

      // Store in cache with TTL
      this.cache.set(key, result);
      if (options.ttl) {
        setTimeout(() => this.cache.delete(key), options.ttl);
      }

      return result;
    };
  }
}

// Usage
@Injectable()
export class UserService {
  @CachingDecorator.around({ ttl: 300000 }) // 5 minutes
  async getUserById(id: string): Promise<User> {
    return await this.userRepository.findById(id);
  }
}

2. Performance Monitoring

@Aspect()
export class PerformanceDecorator extends AOPDecorator {
  around({ method, options }) {
    return (...args: any[]) => {
      const start = Date.now();

      try {
        const result = method.apply(this, args);
        const duration = Date.now() - start;

        if (duration > options.threshold) {
          console.warn(`⚠️ Slow method detected: ${method.name} (${duration}ms)`);
        }

        return result;
      } catch (error) {
        const duration = Date.now() - start;
        console.error(`💥 Method failed: ${method.name} (${duration}ms)`);
        throw error;
      }
    };
  }
}

// Usage
@Injectable()
export class DataService {
  @PerformanceDecorator.around({ threshold: 1000 })
  async processLargeDataset(data: any[]): Promise<ProcessedData> {
    // Your expensive operation here
    return await this.complexProcessing(data);
  }
}

3. Error Handling with Retry Logic

@Aspect()
export class RetryDecorator extends AOPDecorator {
  afterThrowing({ method, options, error }) {
    return (...args: any[]) => {
      console.error(`🔄 Method ${method.name} failed:`, error.message);

      if (options.retryCount < options.maxRetries) {
        console.log(`🔄 Retrying... (${options.retryCount + 1}/${options.maxRetries})`);

        // Implement exponential backoff
        const delay = Math.pow(2, options.retryCount) * 1000;
        setTimeout(() => {
          // Retry logic here
          method.apply(this, args);
        }, delay);
      }
    };
  }
}

// Usage
@Injectable()
export class ApiService {
  @RetryDecorator.afterThrowing({ maxRetries: 3, retryCount: 0 })
  async callExternalAPI(endpoint: string): Promise<any> {
    const response = await fetch(endpoint);
    if (!response.ok) {
      throw new Error(`API call failed: ${response.status}`);
    }
    return response.json();
  }
}

TypeScript Power 💪

One of the strongest features of nestjs-saop is its complete TypeScript support. Define custom option types for better developer experience:

interface LoggingOptions {
  level: 'debug' | 'info' | 'warn' | 'error';
  includeTimestamp: boolean;
  logArgs: boolean;
}

@Aspect()
export class TypedLoggingDecorator extends AOPDecorator {
  before({ method, options }: UnitAOPContext<LoggingOptions>) {
    return (...args: any[]) => {
      const timestamp = options.includeTimestamp 
        ? `[${new Date().toISOString()}] ` 
        : '';

      const argsLog = options.logArgs 
        ? ` with args: ${JSON.stringify(args)}` 
        : '';

      console.log(
        `${timestamp}${options.level.toUpperCase()}: ${method.name}${argsLog}`
      );
    };
  }
}

// Usage with full type safety
@Injectable()
export class UserService {
  @TypedLoggingDecorator.before({
    level: 'info',           // ✅ Type-safe
    includeTimestamp: true,  // ✅ Type-safe  
    logArgs: false          // ✅ Type-safe
  })
  async createUser(userData: CreateUserDto): Promise<User> {
    return await this.userRepository.create(userData);
  }
}

Combining Multiple Decorators

You can stack multiple decorators on a single method for powerful combinations:

@Injectable()
export class PaymentService {
  @LoggingDecorator.before({ level: 'info', logArgs: true })
  @PerformanceDecorator.around({ threshold: 2000 })
  @CachingDecorator.around({ ttl: 60000 })
  @RetryDecorator.afterThrowing({ maxRetries: 3 })
  async processPayment(paymentData: PaymentDto): Promise<PaymentResult> {
    // This method gets enhanced with:
    // 1. Logging before execution
    // 2. Performance monitoring 
    // 3. Caching capabilities
    // 4. Automatic retry on failures

    return await this.paymentGateway.process(paymentData);
  }
}

AOP Execution Order

The advice types execute in this specific order:

  1. 🔄 Around (before method)
  2. ▶️ Before
  3. Method Execution
  4. ⏹️ After
  5. ✅ AfterReturning OR ❌ AfterThrowing
  6. 🔄 Around (after method)

Conclusion

nestjs-saop brings the power and elegance of Spring-style AOP to the NestJS ecosystem. Whether you need logging, caching, performance monitoring, or error handling, this library helps you implement cross-cutting concerns cleanly and efficiently.

The combination of familiar Spring-like syntax, complete TypeScript support, and seamless NestJS integration makes it a perfect choice for building maintainable enterprise applications.

Give it a try in your next NestJS project – your code will thank you! 🚀

📚 Resources:

What's your experience with AOP in backend development? Have you tried implementing cross-cutting concerns in NestJS before? Share your thoughts in the comments below! 💬


This content originally appeared on DEV Community and was authored by miinhho


Print Share Comment Cite Upload Translate Updates
APA

miinhho | Sciencx (2025-09-08T15:51:46+00:00) Bring Spring-Style AOP to NestJS with nestjs-saop. Retrieved from https://www.scien.cx/2025/09/08/bring-spring-style-aop-to-nestjs-with-nestjs-saop/

MLA
" » Bring Spring-Style AOP to NestJS with nestjs-saop." miinhho | Sciencx - Monday September 8, 2025, https://www.scien.cx/2025/09/08/bring-spring-style-aop-to-nestjs-with-nestjs-saop/
HARVARD
miinhho | Sciencx Monday September 8, 2025 » Bring Spring-Style AOP to NestJS with nestjs-saop., viewed ,<https://www.scien.cx/2025/09/08/bring-spring-style-aop-to-nestjs-with-nestjs-saop/>
VANCOUVER
miinhho | Sciencx - » Bring Spring-Style AOP to NestJS with nestjs-saop. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/09/08/bring-spring-style-aop-to-nestjs-with-nestjs-saop/
CHICAGO
" » Bring Spring-Style AOP to NestJS with nestjs-saop." miinhho | Sciencx - Accessed . https://www.scien.cx/2025/09/08/bring-spring-style-aop-to-nestjs-with-nestjs-saop/
IEEE
" » Bring Spring-Style AOP to NestJS with nestjs-saop." miinhho | Sciencx [Online]. Available: https://www.scien.cx/2025/09/08/bring-spring-style-aop-to-nestjs-with-nestjs-saop/. [Accessed: ]
rf:citation
» Bring Spring-Style AOP to NestJS with nestjs-saop | miinhho | Sciencx | https://www.scien.cx/2025/09/08/bring-spring-style-aop-to-nestjs-with-nestjs-saop/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.