@cisstech/nestjs-expand
TypeScript icon, indicating that this package has built-in type declarations

1.10.0 • Public • Published

@cisstech/nestjs-expand

A NestJS module to build Dynamic Resource Expansion for APIs

CI codecov codefactor GitHub Tag npm package NPM downloads licence code style: prettier

Overview

The NestJS Expandable Library is a powerful and flexible extension for NestJS applications, providing a generic pattern for resource expansion in REST APIs. It allows you to dynamically expand and include related resources in API responses, enhancing the flexibility of your API design.

Features

  • Dynamic Resource Expansion: Easily expand related resources in API responses using query parameters.
  • Dynamic Field Selection: Easily select only the fields you want to get from the API responses using query parameters.
  • Decorator-Based Configuration: Use decorators like @Expander, @Expandable, @ExpanderMethods, and @UseExpansionMethod to configure expansion logic.
  • Reusable Expansion Logic: Define common expansion logic once in classes decorated with @ExpanderMethods and reuse it across multiple DTOs using @UseExpansionMethod.
  • Enhanced Metadata Handling: Improved handling of metadata allows for multiple decorators of the same type on the same target.
  • Configuration and Customization: Configure and customize the library to suit your application's specific needs.
  • Comprehensive Error Handling: Control how expansion errors are handled with policies (ignore, include, throw) and response customization.
  • Tested and Reliable: Extensive unit and integration tests ensure the reliability of the library.

Installation

yarn add @cisstech/nestjs-expand

Usage

    1. Decorate Expandable Endpoints
    import { Controller, Get } from '@nestjs/common'
    import { CourseService } from './course.service'
    import { CourseDTO } from './course.dto'
    import { Expandable } from '@cisstech/nestjs-expand'
    
    @Controller('courses')
    export class CourseController {
      constructor(private readonly courseService: CourseService) {}
    
      @Get()
      @Expandable(CourseDTO)
      async getAllCourses(): Promise<CourseDTO[]> {
        return this.courseService.getAllCourses()
      }
    }
    1. Implement Expander Services (@Expander)
    import { Injectable } from '@nestjs/common'
    import { ExpandContext, Expander } from '@cisstech/nestjs-expand'
    import { CourseDTO } from './course.dto'
    import { InstructorDTO } from './instructor.dto'
    import { InstructorService } from './instructor.service'
    
    @Injectable()
    @Expander(CourseDTO)
    export class CourseExpander {
      constructor(private readonly instructorService: InstructorService) {}
    
      async instructor(context: ExpandContext<Request, CourseDTO>): Promise<InstructorDTO> {
        const { parent } = context
        const instructor = await this.instructorService.getInstructorById(parent.instructorId)
        if (!instructor) {
          throw new Error(`Instructor with id ${parent.instructorId} not found`)
        }
        return instructor
      }
    }
    1. (Optional) Implement Reusable Expansion Logic (@ExpanderMethods)
    // instructor.expander-methods.ts (Example)
    import { Injectable } from '@nestjs/common'
    import { ExpanderMethods } from '@cisstech/nestjs-expand'
    import { InstructorService } from './instructor.service'
    import { InstructorDTO } from './instructor.dto'
    
    @Injectable()
    @ExpanderMethods() // Mark class containing reusable methods
    export class InstructorExpanderMethods {
      constructor(private readonly instructorService: InstructorService) {}
    
      async fetchById(id: number): Promise<InstructorDTO | null> {
        return this.instructorService.getInstructorById(id)
      }
    }
    1. (Optional) Link Reusable Logic in Standard Expanders (@UseExpansionMethod)
    // course.expander.ts
    import { Injectable } from '@nestjs/common'
    import { Expander, UseExpansionMethod } from '@cisstech/nestjs-expand'
    import { CourseDTO } from './course.dto'
    import { InstructorExpanderMethods } from '../instructors/instructor.expander-methods' // Assuming this class exists
    
    @Injectable()
    @Expander(CourseDTO)
    @UseExpansionMethod<CourseDTO, InstructorExpanderMethods>({
      name: 'instructor', // Field to populate
      class: InstructorExpanderMethods, // Class with reusable logic
      method: 'fetchById', // Method to call
      params: ['instructorId'], // Map parent.instructorId to the method's first arg
    })
    export class CourseExpander {
      // No instructor method needed here if using @UseExpansionMethod
    }
    1. Register the controllers and providers (Expanders, ExpanderMethods classes)
// app.module.ts

import { Module } from '@nestjs/common'
import { NestKitExpandModule } from '@cisstech/nestjs-expand'
import { CourseController } from 'PATH_TO_FILE'
import { CourseExpander } from 'PATH_TO_FILE'
import { InstructorExpanderMethods } from 'PATH_TO_FILE' // Register the class with reusable methods

@Module({
  imports: [
    NestKitExpandModule.forRoot({
      enableLogging: true,
      errorHandling: {
        includeErrorsInResponse: true,
        defaultErrorPolicy: 'ignore',
      },
    }),
  ],
  controllers: [CourseController],
  providers: [CourseExpander, InstructorExpanderMethods], // Add all expander/methods classes
})
export class AppModule {}

Configuration Options

The library provides configuration options to customize its behavior. You can pass an optional configuration object when initializing the NestKitExpandModule in your module:

NestKitExpandModule.forRoot({
  // General configuration
  enableLogging: true, // Enable or disable logging
  enableGlobalSelection: true, // Make all endpoints selectable by default
  expandQueryParamName: 'expands', // The query parameter name for expansions
  selectQueryParamName: 'selects', // The query parameter name for field selection
  logLevel: 'warn', // Log level: 'debug', 'log', 'warn', 'error', or 'none'

  // Error handling configuration
  errorHandling: {
    includeErrorsInResponse: true, // Include error details in the response
    defaultErrorPolicy: 'ignore', // Default error policy: 'ignore', 'include', or 'throw'
    errorResponseShape: (error, path) => ({
      // Customize the error shape
      message: `Error in ${path}: ${error.message}`,
      path: path,
      code: error.code || 'EXPANSION_ERROR',
    }),
  },
})

Error Handling Policies

You can control how expansion errors are handled at both the module and endpoint levels:

  • ignore (default): Continue with other expansions, but don't include the failed expansion
  • include: Include error details in the response for debugging (requires includeErrorsInResponse: true)
  • throw: Fail the entire request if any expansion fails

Example with per-endpoint error policy:

@Get('users')
@Expandable(UserDTO, { errorPolicy: 'include' })
findAll() {
  return this.userService.findAll();
}

Documentation

For detailed documentation, examples, and advanced usage, please refer to the official documentation at https://cisstech.github.io/nestkit/docs/nestjs-expand/getting-started

A presentation article is also available medium

License

MIT © Mamadou Cisse

Dependents (0)

Package Sidebar

Install

npm i @cisstech/nestjs-expand

Weekly Downloads

54

Version

1.10.0

License

MIT

Unpacked Size

86.2 kB

Total Files

24

Last publish

Collaborators

  • mcisse