@cisstech/nestjs-expand
Version:
A NestJS module to build Dynamic Resource Expansion for APIs
209 lines (164 loc) • 8.27 kB
Markdown
# @cisstech/nestjs-expand
<div align="center">
A NestJS module to build Dynamic Resource Expansion for APIs
[](https://github.com/cisstech/nestkit/actions/workflows/ci.yml)
[](https://codecov.io/gh/cisstech/nestkit)
[](https://www.codefactor.io/repository/github/cisstech/nestkit/overview/main)
[](https://github.com/cisstech/nestkit/tags)
[](https://www.npmjs.org/package/@cisstech/nestjs-expand)
[](https://npmjs.org/package/@cisstech/nestjs-expand)
[](https://github.com/cisstech/nestkit/blob/main/LICENSE)
[](https://github.com/prettier/prettier)
</div>
## 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
```bash
yarn add @cisstech/nestjs-expand
```
## Usage
- 1. Decorate Expandable Endpoints
```typescript
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()
}
}
```
- 2. Implement Expander Services (`@Expander`)
```typescript
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
}
}
```
- 3. (Optional) Implement Reusable Expansion Logic (`@ExpanderMethods`)
```typescript
// 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)
}
}
```
- 4. (Optional) Link Reusable Logic in Standard Expanders (`@UseExpansionMethod`)
```typescript
// 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
}
```
- 5. Register the controllers and providers (Expanders, ExpanderMethods classes)
```typescript
// 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:
```typescript
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:
```typescript
@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](https://medium.com/p/08c06be4c2ba)
## License
MIT © [Mamadou Cisse](https://github.com/cisstech)