15

One of the controller methods in my NestJS application is supposed to take plain text as its body but whenever I try to make a request, the parameter is received as an empty object. Is this even possible or am I going to have to create some sort of DTO to pass that single string?

Example:

@Post()
  myFunction(@Body() id: string) {
    // do something here
  }
luna
  • 153
  • 1
  • 1
  • 5

9 Answers9

37

I see that this question is pretty old, but it is listed in google among first, so I want to add answer here.

If you don't want to add body-parser middleware (for example, you want plain text only in single controller method), you can use raw-body (which is already exists in your node_modules), something like this:

import * as rawbody from 'raw-body';
import { Controller, Post, Body, Req } from '@nestjs/common';

@Controller('/')
export class IndexController {

  @Post()
  async index(@Body() data, @Req() req) {

    // we have to check req.readable because of raw-body issue #57
    // https://github.com/stream-utils/raw-body/issues/57
    if (req.readable) {
      // body is ignored by NestJS -> get raw body from request
      const raw = await rawbody(req);
      const text = raw.toString().trim();
      console.log('body:', text);

    } else {
      // body is parsed by NestJS
      console.log('data:', data);
    }

    // ...
  }

}

you could also create new parameter decorator

import * as rawbody from 'raw-body';
import { createParamDecorator, HttpException, HttpStatus } from '@nestjs/common';

export const PlainBody = createParamDecorator(async (data, req) => {
  if (req.readable) {
    return (await rawbody(req)).toString().trim();
  }
  throw new HttpException('Body aint text/plain', HttpStatus.INTERNAL_SERVER_ERROR);
});

and use it like

@Post()
async index(@PlainBody() text: string) {
  // ...

(I didn't check decorator code, wrote it right here in comment)

yumaa
  • 965
  • 9
  • 18
  • While this works (I tested it) the only issue is you can not be strict with the types, as rawbody requires whatever gets parsed into it to be streamable and the Request type that @Req actually is not that. So you have to turn off strict typing to get it to work, for instance if you want to check the content-length as well (which you should) But works, so thanks! – Nift Sep 30 '19 at 07:26
  • @yumaa you made my developer's evening! :) – Vaha Dec 01 '20 at 14:35
12

Adding on @yumaa's post above

Here's the working decorator with NestJS v7.0.8:

import { createParamDecorator, ExecutionContext, BadRequestException } from '@nestjs/common';
import * as rawBody from "raw-body";

export const PlainBody = createParamDecorator(async (_, context: ExecutionContext) => {
    const req = context.switchToHttp().getRequest<import("express").Request>();
    if (!req.readable) { throw new BadRequestException("Invalid body"); }

    const body = (await rawBody(req)).toString("utf8").trim();
    return body;
})
DoronG
  • 2,576
  • 16
  • 22
2

Just register a text parser dynamically in NestJS on the fly.

You can use the rawBody: true option as documented in the FAQ section (https://docs.nestjs.com/faq/raw-body#raw-body). However, please note that by default, only the jsonParser and urlencodedParser are registered. If you want to register a different parser, you will need to do so explicitly.

Example:

main.ts:

import { NestFactory } from '@nestjs/core';
import type { NestExpressApplication } from '@nestjs/platform-express';

const app = await NestFactory.create<NestExpressApplication>(module, {
  rawBody: true
});

app.useBodyParser('text')

my.controller.ts:

import { Controller, Post, RawBodyRequest, Req } from '@nestjs/common';
import { Request } from 'express';

@Post()
async post(@Req() req: RawBodyRequest<Request>) {
  console.log(req.rawBody?.toString('utf-8') ?? '')
}
krema
  • 939
  • 7
  • 20
0

The semantics of a post request are determined by the header that indicates the content type. Try making sure the request header has the type 'text/plain' and see of this helps.

https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST

Rich Duncan
  • 1,845
  • 1
  • 12
  • 13
  • I've tried both "text" and "text/plain" in Postman and neither of them are picked up by nest as strings. – luna Sep 12 '18 at 16:12
0

Nest is not compatible with plain/text and you must pass a bodyparser to your express app instead. Try something like this:

import * as bodyParser from 'body-parser';


async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.use(bodyparser({ ...options })) // for plain/text bodies
  await app.listen(3000)
}
bootstrap();

where options is created from https://www.npmjs.com/package/body-parser

TehWalnut
  • 11
  • 1
0

Old question but none of the above worked for me but the following did:

The above decorator or controller-method approaches did not work for me as the request body buffer had always already been read.

I was able to get it working using the following middleware. (Note in my case I needed to validate a Xero webhook so the example is geared towards that)

cache-raw-body-on-request.ts:

import { json } from 'body-parser';
import * as cloneBuffer from 'clone-buffer';

export const cachedRawBodyRequestKey = 'rawBodyBuffer';

/**
 * Clones the request buffer and stores it on the request object for reading later 
 */
export const cacheRawBodyOnRequest = json({
  verify: (req: any, res, buf, encoding) => {

    // only clone the buffer if we're receiving a Xero webhook request
    if (req.headers['x-xero-signature'] && Buffer.isBuffer(buf)) {
      req[cachedRawBodyRequestKey] = cloneBuffer(buf);
    }
    return true;
  },
});

main.ts:

app.use(cacheRawBodyOnRequest);

controller:

const textBody = req[cachedRawBodyRequestKey].toString('utf-8');
Max Mumford
  • 2,482
  • 5
  • 28
  • 40
0

This is my take on getting the raw(text)body in NestJS's hander:

  1. configure the app with preserveRawBodyInRequest as shown in JSDoc example
  2. use RawBody decorator in handler to retrieve the raw(text)body

raw-request.decorator.ts:

import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { NestExpressApplication } from "@nestjs/platform-express";

import { json, urlencoded } from "express";
import type { Request } from "express";
import type http from "http";

export const HTTP_REQUEST_RAW_BODY = "rawBody";

/**
 * make sure you configure the nest app with <code>preserveRawBodyInRequest</code>
 * @example
 * webhook(@RawBody() rawBody: string): Record<string, unknown> {
 *   return { received: true };
 * }
 * @see preserveRawBodyInRequest
 */
export const RawBody = createParamDecorator(
  async (data: unknown, context: ExecutionContext) => {
    const request = context
      .switchToHttp()
      .getRequest<Request>()
    ;

    if (!(HTTP_REQUEST_RAW_BODY in request)) {
      throw new Error(
        `RawBody not preserved for request in handler: ${context.getClass().name}::${context.getHandler().name}`,
      );
    }

    const rawBody = request[HTTP_REQUEST_RAW_BODY];

    return rawBody;
  },
);

/**
 * @example
 * const app = await NestFactory.create<NestExpressApplication>(
 *   AppModule,
 *   {
 *     bodyParser: false, // it is prerequisite to disable nest's default body parser
 *   },
 * );
 * preserveRawBodyInRequest(
 *   app,
 *   "signature-header",
 * );
 * @param app
 * @param ifRequestContainsHeader
 */
export function preserveRawBodyInRequest(
  app: NestExpressApplication,
  ...ifRequestContainsHeader: string[]
): void {
  const rawBodyBuffer = (
    req: http.IncomingMessage,
    res: http.ServerResponse,
    buf: Buffer,
  ): void => {
    if (
      buf?.length
      && (ifRequestContainsHeader.length === 0
        || ifRequestContainsHeader.some(filterHeader => req.headers[filterHeader])
      )
    ) {
      req[HTTP_REQUEST_RAW_BODY] = buf.toString("utf8");
    }
  };

  app.use(
    urlencoded(
      {
        verify: rawBodyBuffer,
        extended: true,
      },
    ),
  );
  app.use(
    json(
      {
        verify: rawBodyBuffer,
      },
    ),
  );
}
kaznovac
  • 1,303
  • 15
  • 23
0

If you'd prefer to avoid extra 3rd party dependencies, you could also get use of the built-in nodejs approach here:

function readPost(req: IncomingMessage) {
  return new Promise<string>((resolve, reject) => {
    let body = '';
    req.on('data', (data: string) => (body += data));
    req.on('error', (error: unknown) => reject(error));
    req.on('end', () => resolve(body));
  });
}

Usage:

import { Post, Req } from '@nestjs/common';
import { IncomingMessage } from 'http';
...
@Post()
myFunction(@Req() req: IncomingMessage) {
  const bodyStr = await readPost(req);
  console.log('request body:', bodyStr);
}
Klesun
  • 12,280
  • 5
  • 59
  • 52
0

For express driver just add an extra body parser:

// main.ts
import { NestExpressApplication } from '@nestjs/platform-express';

// ...
const app = await NestFactory.create<NestExpressApplication>(AppModule);

app.useBodyParser('text');
// ...other code

And as always:

// some.controller.ts
@Post('text')
public async register(@Body() payload: string) {
    console.log(payload);
}

Official docs HERE

zemil
  • 3,235
  • 2
  • 24
  • 33