Nest: Transform Request Body with Header other than application/json

Created on 22 Jul 2019  路  9Comments  路  Source: nestjs/nest

I have some DTO class that defines the body of a patch method I have in my controller:

class MyObjDTO {
...
}

@UseInterceptors(ClassSerializerInterceptor)
@Patch(':id')
async updateObj(@Param() params: {id: string}, @Body() body: MyObjDTO): Promise<MyObj> {
        // do some stuff
}

When I bootstrap my module that uses this controller, I use the ValidationPipe like so:

  app.useGlobalPipes(new ValidationPipe({
    transform: true
  }));

If I send in a PATCH request with the header set Content-Type=application/json, nestjs behaves as expected, validates the body, and performs the controller method.

However, I am using the JSON API Spec and if I send a in a PATCH method using Content-Type=application/vnd.api+json with the same body, the body parameter comes in as an empty object, thus failing the validation.

How can I perform the same transformation and validation that occurs with the header Content-Type=application/json with the Content-Type header set to application/vnd.api+json?

Note this is the request header.

needs triage question 馃檶

Most helpful comment

this is my workaround for now:

@Injectable()
class JSONAPIMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction ) {
    bodyParser.json({
      type: 'application/vnd.api+json'
    })(req, res, next);
  }
}

@Module({
  imports: [...],
  controllers: [...],
  providers: [...],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(JSONAPIMiddleware).forRoutes('/my-route');
  }
}

with creating the module like so:

  const app: NestApplication = await NestFactory.create(AppModule, {
    bodyParser: false
  });

All 9 comments

In order to transform & validate body, you have to pass type param to the body-parser library https://github.com/expressjs/body-parser/issues/131. However, using pipes on headers is impossible. In this case, you'd have to write your own decorator https://docs.nestjs.com/custom-decorators OR run pipe within your route handler (as a service)

@kamilmysliwiec even if I remove the pipe from the bootstrapping function, the body parameter is still an empty object, so moving the pipe into the controller method (handler) wouldn't solve it, no? Since it is empty at that point.

also, trying to write my own decorator

const MyDec = createParamDecorator((data: string, req: Request) => {
})

but without being able to specify what Content-Type header to parse the incoming request data on, the body seems to alway be empty when not using application/json. Is this what you meant for me to do? I am starting to think this might be impossible without being able to modify the body parser type config option.

this is my workaround for now:

@Injectable()
class JSONAPIMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction ) {
    bodyParser.json({
      type: 'application/vnd.api+json'
    })(req, res, next);
  }
}

@Module({
  imports: [...],
  controllers: [...],
  providers: [...],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(JSONAPIMiddleware).forRoutes('/my-route');
  }
}

with creating the module like so:

  const app: NestApplication = await NestFactory.create(AppModule, {
    bodyParser: false
  });

You don't have to create a class.

app.use(bodyParser.json({
   type: 'application/vnd.api+json'
}));

^ this will be enough

When I tried it with the app.use (the NestApplication instance), it still wasn't parsing the body with that header

I also use Nest with JSON:API and these lines fixed the issue for me:

const app = await NestFactory.create(AppModule, {
    bodyParser: true
  });

  app.use(bodyParser.json({ type: (req: any) => req.get('Content-Type') === 'application/vnd.api+json', strict: false }));

This is the only thing that worked for me when trying to parse 'text/plain; charset=UTF-8' into JSON for a selective route:

// In parse-sns-notification.middleware.ts
import * as bodyParser from 'body-parser'
import {NestMiddleware, Injectable} from '@nestjs/common'
import {Request, Response, NextFunction} from 'express'

@Injectable()
export class ParseSnsNotificationMiddleware implements NestMiddleware {
  // Parse text/plain content type for SNS notifications to work due to AWS setting
  //  incorrect  content type https://forums.aws.amazon.com/thread.jspa?threadID=69413
  use(req: Request, res: Response, next: NextFunction) {
    bodyParser.json({
      type: (request: any) => request.get('Content-Type') === 'text/plain; charset=UTF-8',
      strict: false,
    })(req, res, next)
  }
}
// In app.module.ts
...
export class ApiAppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(ParseSnsNotificationMiddleware).forRoutes('/path/to/route')
  }
}

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings