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.
- 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.
yarn add @cisstech/nestjs-expand
-
- 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() } }
-
- 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 } }
- Implement Expander Services (
-
- (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) } }
- (Optional) Implement Reusable Expansion Logic (
-
- (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 }
- (Optional) Link Reusable Logic in Standard Expanders (
-
- 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 {}
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',
}),
},
})
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 (requiresincludeErrorsInResponse: 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();
}
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
MIT © Mamadou Cisse