nestjs / nest

A progressive Node.js framework for building efficient, scalable, and enterprise-grade server-side applications with TypeScript/JavaScript 🚀
https://nestjs.com
MIT License
67.07k stars 7.56k forks source link

@Body() don't transfer body object to dto object #552

Closed XGHeaven closed 6 years ago

XGHeaven commented 6 years ago

I'm submitting a...


[ ] Regression 
[x] Bug report
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead post your question on Stack Overflow.

Current behavior

@Controller()
export class ApiController {
  @Post()
  async index(@Body() body: BodyDTO) {
     // `body` is not a BodyDTO, just a normal object
     console.log(body instanceof BodyDTO) // false
  }
}

Expected behavior

@Controller()
export class ApiController {
  @Post()
  async index(@Body() body: BodyDTO) {
     console.log(body instanceof BodyDTO) // true
  }
}

What is the motivation / use case for changing the behavior?

Sometimes, I need to do something with dto object.

class BodyDTO {
  readonly firstName: string
  readonly lastName: string
  get name(): string { return this.firstName + ' ' + this.lastName }
}

It's not convenience without dto object. And it's not a intuitive way.

Environment


Nest version: 4.6.6


For Tooling issues:
- Node version: 9.11.1  
- Platform:  Mac 
workfel commented 6 years ago

Hi . @XGHeaven, you missing something, you should do this @Body(), if you look the doc, because i need to instantiate it.

@Controller()
export class ApiController {
  @Post()
  async index(@Body() body: BodyDTO) {
     return body;
  }
}
XGHeaven commented 6 years ago

I tried @Body(). It still not works. And I updated my question with @Body().

workfel commented 6 years ago

@XGHeaven , how you call your ENDPOINT ?

@Controller('api')
export class ApiController {
  @Post()
  async index(@Body() body: BodyDTO) {
     return `Hello ${body.firstName} ${body.lastName}`;
  }
}
//...

class BodyDTO {
  readonly firstName: string
  readonly lastName: string
  get name(): string { return this.firstName + ' ' + this.lastName }
}

It's work fine for me with

$ curl -d '{"firstName":"John", "lastName":"Doe"}' -H "Content-Type: application/json" -X POST http://localhost:3000/api
XGHeaven commented 6 years ago

Can you try this one?

// ...
  return `Hello ${body.firstName} ${body.lastName} ${body instanceof BodyDTO}`
// ...

I get Hello John Doe false. But I want to get Hello John Doe true.

unlight commented 6 years ago

Transforming plain object to specific class is not applying automatically. You need to use pipes (see docs) You can write your own, or you can use built-in pipe ValidationPipe.

XGHeaven commented 6 years ago

@unlight Thanks

It works for me.

// ...
async index(@Body(new ValidationPipe({transform: true})) body: BodyDTO) {
  console.log(body instanceof BodyDTO) // true
}
// ...

I suggest updating docs.

kamilmysliwiec commented 6 years ago

The new documentation provides more details about built-in ValidationPipe. https://docs.nestjs.com/v5

I'll merge docs soon.

unlight commented 6 years ago

docs.nestjs.com/v5

Redirects to docs.nestjs.com

kamilmysliwiec commented 6 years ago

Open in the incognito mode (cache)

hlibco commented 6 years ago

@unlight Thanks

It works for me.

// ...
async index(@Body(new ValidationPipe({transform: true})) body: BodyDTO) {
  console.log(body instanceof BodyDTO) // true
}
// ...

I suggest updating docs.

@XGHeaven It's also useful to see the other options the @ValidationPipe() supports. The one I found very useful for security purposes is the whitelist. Here is a use case:

User.create(body).save()

The problem with this is that it's possible to inject into the body of the request properties that are not listed in the DTO, for example, isAdmin: true or whatever flag/role you use to identify admin users.

Ideally, we should strip out from the body non-allowed properties. whitelist: true does just that.

tskweres commented 5 years ago

I'm having an issue with Postman where if I send a POST via the command line, for example:

curl -d '{"phone":"value1"}' -H "Content-Type: application/json" -X POST http://localhost:3000/auth/requestCode

It works!

But if I send it through Postman, the @Body() is an empty object. Has anyone seen this?

workfel commented 5 years ago

@tskweres can you show us the POSTMAN query please.

bennycode commented 5 years ago

It's also useful to see the other options the @ValidationPipe() supports. The one I found very useful for security purposes is the whitelist.

I second that. It's also possible to forbid processing requests that have unexpected properties using the forbidNonWhitelisted option.

@XGHeaven The docs (https://docs.nestjs.com/techniques/validation#stripping-properties) also mention now that payloads are not automatically transformed to DTOs but it also took me a whole to find it so it was good that you brought it up!

Instead of using new ValidationPipe({transform: true})) on every @Body you can also enable transformation on a global application level:

app.useGlobalPipes(
  new ValidationPipe({
    transform: true,
    whitelist: true
  }),
);
anwarhamr commented 5 years ago

I'm having an issue with Postman where if I send a POST via the command line, for example:

curl -d '{"phone":"value1"}' -H "Content-Type: application/json" -X POST http://localhost:3000/auth/requestCode

It works!

But if I send it through Postman, the @Body() is an empty object. Has anyone seen this?

I was able to get postman to accept body. In body tab select raw option. Then to the right of that I selected application/json in the dropdown. Entered my object into the text area and it converted it correctly using @Body myVar: MyType

{
    "toAddress": "whoyouare@gmail.com",
    "message": "hello",
    "useSecureEmail": true,
    "contactPhoneNumber": "509931212"
}
tskweres commented 5 years ago

I didn't. Ended up using Paw instead of Postman and it works fine

sangdth commented 4 years ago

I'm having an issue with Postman where if I send a POST via the command line, for example: curl -d '{"phone":"value1"}' -H "Content-Type: application/json" -X POST http://localhost:3000/auth/requestCode It works! But if I send it through Postman, the @Body() is an empty object. Has anyone seen this?

I was able to get postman to accept body. In body tab select raw option. Then to the right of that I selected application/json in the dropdown. Entered my object into the text area and it converted it correctly using @Body myVar: MyType

{
    "toAddress": "whoyouare@gmail.com",
    "message": "hello",
    "useSecureEmail": true,
    "contactPhoneNumber": "509931212"
}

Hei, thank you! THANK YOU!

lock[bot] commented 4 years ago

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.