Overview

Automatically wrap all responses in a standard API response format with success flag and metadata.

Features

  • Consistent response format
  • Timestamp and path included
  • @SkipTransform decorator
  • Paginated variant included

Installation

npx hanma add transform-interceptor --framework nest

Code

import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from "@nestjs/common";
import { Observable, map } from "rxjs";
import { Reflector } from "@nestjs/core";
import { SetMetadata } from "@nestjs/common";

export interface ApiResponse<T> {
  success: boolean;
  data: T;
  timestamp: string;
  path: string;
}

// Decorator to skip transformation for specific routes
export const SKIP_TRANSFORM_KEY = "skip_transform";

export const SkipTransform = () => SetMetadata(SKIP_TRANSFORM_KEY, true);

// Decorator to add custom metadata to response
export const RESPONSE_MESSAGE_KEY = "response_message";
export const ResponseMessage = (message: string) =>
  SetMetadata(RESPONSE_MESSAGE_KEY, message);

@Injectable()
export class TransformInterceptor<T>
  implements NestInterceptor<T, ApiResponse<T>>
{
  constructor(private reflector: Reflector) {}

  intercept(
    context: ExecutionContext,
    next: CallHandler
  ): Observable<ApiResponse<T>> {
    const skipTransform = this.reflector.getAllAndOverride<boolean>(
      SKIP_TRANSFORM_KEY,
      [context.getHandler(), context.getClass()]
    );

    if (skipTransform) {
      return next.handle();
    }

    const request = context.switchToHttp().getRequest();
    const message = this.reflector.get<string>(
      RESPONSE_MESSAGE_KEY,
      context.getHandler()
    );

    return next.handle().pipe(
      map((data) => ({
        success: true,
        data,
        ...(message && { message }),
        timestamp: new Date().toISOString(),
        path: request.url,
      }))
    );
  }
}

// Paginated response transformer
export interface PaginatedResponse<T> {
  success: boolean;
  data: T[];
  meta: {
    total: number;
    page: number;
    limit: number;
    totalPages: number;
    hasNext: boolean;
    hasPrev: boolean;
  };
  timestamp: string;
}

@Injectable()
export class PaginatedTransformInterceptor<T>
  implements NestInterceptor<{ data: T[]; total: number; page: number; limit: number }, PaginatedResponse<T>>
{
  intercept(
    context: ExecutionContext,
    next: CallHandler
  ): Observable<PaginatedResponse<T>> {
    return next.handle().pipe(
      map(({ data, total, page, limit }) => {
        const totalPages = Math.ceil(total / limit);
        
        return {
          success: true,
          data,
          meta: {
            total,
            page,
            limit,
            totalPages,
            hasNext: page < totalPages,
            hasPrev: page > 1,
          },
          timestamp: new Date().toISOString(),
        };
      })
    );
  }
}

Usage

// Global usage
app.useGlobalInterceptors(new TransformInterceptor());

Dependencies

No external dependencies required.

Frequently Asked Questions

How do I install Transform Interceptor?
Run the following command: npx hanma add transform-interceptor --framework nest
How do I use this snippet?
Copy the code and paste it into your project. The snippet will be saved to src/interceptors/transform.interceptor.ts.
Can I customize this snippet?
Yes! All snippets are copy-paste code that you own. Feel free to modify them to fit your needs.
Does this work with other frameworks?
This snippet is designed for the specific framework. Check the other framework pages for equivalent implementations.