Skip to main content

Usage with NestJS

@automapper/nestjs is the official integration for NestJS.

Installation

npm i @automapper/core @automapper/nestjs
yarn add @automapper/core @automapper/nestjs

Strategy

Recommendation is to use @automapper/classes in a NestJS application.

Usage

  • Call AutomapperModule.forRoot() in AppModule
// single strategy
@Module({
imports: [
AutomapperModule.forRoot({
strategyInitializer: classes(),
}),
],
})
export class AppModule {}

// multiple strategies
@Module({
imports: [
AutomapperModule.forRoot(
[
{
name: 'classes',
strategyInitializer: classes(),
},
{
name: 'pojos',
strategyInitializer: pojos(),
},
],
{
globalErrorHandler,
globalNamingConventions,
}
),
],
})
export class AppModule {}
  • Use @InjectMapper() to inject the Mapper in NestJS's Injectable
  • @InjectMapper() accepts an optional argument name. This is the name of the CreateMapperOptions passed to AutomapperModule.forRoot()
  • AutomapperModule is a Global module, so it is only needed to be imported once to have the Mapper available across the application

Async Configuration

TBD: AutomapperModule.forRootAsync()

AutomapperProfile

AutomapperProfile is an Injectable in NestJS. Make sure to extends AutomapperProfile

import { AutomapperProfile } from '@automapper/nestjs';
import type { Mapper } from '@automapper/core';
import { Injectable } from '@nestjs/common';

@Injectable()
export class UserProfile extends AutomapperProfile {
constructor(@InjectMapper() mapper: Mapper) {
super(mapper);
}

override get profile() {
return (mapper) => {
createMap(mapper, User, UserDto);
};
}
}

Then provide UserProfile in a Module

@Module({
providers: [UserProfile],
})
export class UserModule {}
  • AutomapperProfile enforces the sub-classes to implement a get profile() method that returns a MappingProfile.
  • AutomapperProfile can have other Services injected to its constructor if needed.

mappingConfigurations

Same concept as Share Configuration using MappingProfile, AutomapperProfile has an optional protected get mappingConfigurations() that subclasses can override to provide an array of MappingConfiguration. These configurations are passed to all createMap() in get profile()

@Injectable()
export class UserProfile extends AutomapperProfile {
constructor(@InjectMapper() mapper: Mapper) {
super(mapper);
}

get profile(): MappingProfile {
return (mapper) => {
createMap(mapper, UserEntity, UserDto);
createMap(mapper, UserEntity, UserInformationDto);
createMap(mapper, UserEntity, AuthUserDto);
};
}

protected get mappingConfigurations(): MappingConfiguration[] {
// the 3 createMap() above will get this `extend()`
return [extend(BaseEntity, BaseDto)];
}
}

MapInterceptor

@automapper/nestjs provides MapInterceptor. In cases where you do not care about annotating the correct return type for a Controller#method and want your Service to be a little cleaner, you can utilize the MapInterceptor to execute the mapping.

import { MapInterceptor } from '@automapper/nestjs';

export class UserController {
@Get('me')
@UseInterceptors(MapInterceptor(User, UserDto))
me() {
// userService.getMe() returns a User here and does not have mapping logic in it.
return this.userService.getMe();
}
}

MapInterceptor has the following signature:

MapInterceptor(sourceModelType, destinationModelType, {
isArray?: boolean;
mapperName?: string;
} & MapOptions)

MapPipe

@automapper/nestjs provides MapPipe. When you want to transform the incoming request body before it gets to the route handler, you can utilize MapPipe to achieve this behavior

@Post('/from-body')
postFromBody(@Body(MapPipe(User, UserDto)) user: UserDto) {
// from the request perspective, user coming in as an User object but will be mapped to UserDto with MapPipe
return user;
}

MapPipe only works with @Body or @Query.

@Get('/from-query')
getFromQuery(@Query(MapPipe(User, UserDto)) user: UserDto) {
// from the request perspective, user coming in as an User object but will be mapped to UserDto with MapPipe
return user;
}

Note that when we send a request with Body or Query, the data is serialized. Data-type like Date will come in the request handler as string. Hence, please be cautious of the mapping configuration when you use MapPipe

MapPipe has the same signature as MapInterceptor

MapPipe(sourceModelType, destinationModelType, {
isArray?: boolean;
mapperName?: string;
} & MapOptions)