rsommerard
rsommerard

Reputation: 540

Auto-fill DTO fields with other data than request body data

I have a class CreateFolderDto with two readonly fields:

export class CreateFolderDto {
    public readonly name: string
    public readonly user_id: number
}

I have a controller which is:

@UseGuards(AuthGuard('jwt'))
@Post()
public create(@Request() req, @Body() createFolderDto: CreateFolderDto) {
    return this.folderService.create(createFolderDto)
}

The request send to my controller is a good one, I only send the name in json format with an accessToken in the header. The accessToken permit me to get my user_id from the request with req.user.id.

The DTO field user_id is not automatically filled. I would like to fill it automatically.

Is it a way to auto-fill my createFolderDto.user_id variable ?

Upvotes: 1

Views: 1591

Answers (1)

kamilg
kamilg

Reputation: 743

@Body only wraps actual request body into instance of the CreateFolderDto class. As the body which comes to your endpoint has no such a field, you need to add it manually.

Normally, aggregated fields could be added with custom constructor of your DTO:

export class CreateFolderDto {
    public readonly name: string
    public readonly session_uuid: string

    constructor(bodyValue: any = {}) {
       this.name = bodyValue.name
       this.session_uuid = generateUuid()
    }
}

But in your case, user is attached to request itself, so I believe you have the following options:

  1. Check out your code which attaches the user to request itself. If you are using JWT Auth described in NestJS docs, you cannot do this that way.

  2. You can write custom Interceptor:

Injectable()
export class ExtendBodyWithUserId implements NestInterceptor {
  async intercept(context: ExecutionContext, next: CallHandler) {
    const request = context.switchToHttp().getRequest()
    request.body.user_id = request.user
    return next.handle()
  }
}

// usage
@UseGuards(AuthGuard('jwt'))
@UseInterceptors(ExtendBodyWithUserId)
@Post()
public create(@Request() req, @Body() createFolderDto: CreateFolderDto) {
    return this.folderService.create(createFolderDto)
}

Last but not least, some personal recommendation. Consider how much you will use this interceptor as an extension, as too many of 'extras' like this bloat the codebase.

I would recommend to change the folderService signature to: create(createFolderDto: CreateFolderDto, user: User), where folder dto has only the name, without user-related entry. You keep the consistency, separation and clear intentions. In the implementation of create you can just pass user.id further. And going this way, you don't have to write custom interceptors.

Pick your way and may the consistency in your codebase be with you!

Upvotes: 2

Related Questions