NestJS + class-validator, why errors array is empty? - nestjs

I have this class:
class SomeDto {
#ArrayMaxSize(100)
#Type(() => NestedDto)
#ValidateNested({ each: true })
nested: NestedDto[];
}
and i use validation pipe as:
#Body(new ValidationPipe({ transform: true })) { nested }: NestedDto,
It does the validation right, but i got an empty array of errors every time.
{
"statusCode": 400,
"message": "Bad Request",
"error": []
}

First of all - please share NestedDto to understand its validators.
Secondly I think the problem is that you use NestedDto instead of SomeDto in the body.
#Body(new ValidationPipe({ transform: true })) { nested }: SomeDto // <- Not NestedDto.
In this case I'm getting correct errors about the array size.

Related

Nestjs ValidationPipe({transform: true}) does not transform string to number for request body

As mentioned in the docs
The ValidationPipe can automatically transform payloads to be objects typed according to their DTO classes. To enable auto-transformation, set transform to true.
But I always get Bad Request, so the transform doesn't work.
validation dto
export class CreateDistrictDto {
#IsString()
#IsNotEmpty()
name: string;
// associations
#IsNotEmpty()
#IsNumber()
cityId: number;
}
route in controller
#Post()
async createCity(#Body() cityDto: CreateCityDto) {
return await this.cityService.createCity(cityDto);
}
main.ts (I use default #nestjs/common ValidationPipe)
app.useGlobalPipes(new ValidationPipe({
transform: true,
}));
This answer says that transform doesn't work for primitives, which seems to be true.
https://stackoverflow.com/a/67181540/8419307
https://github.com/nestjs/nest/blob/master/packages/common/pipes/validation.pipe.ts#L137
Then why does the docs say otherwise and what is the transform: true option for?
So it appears that body-parser is not transforming the number into a number implicitly. To get around this, either send in a JSON request (application/json) with an explicit number or enable implicit type conversion in the class-transformer options via transformOptions: { enableImplicitConversion: true }, in the ValidationPipe options.
{
provide: APP_PIPE,
useValue: new ValidationPipe({
transform: true,
transformOptions: { enableImplicitConversion: true },
}),
},

NestJs one Post request with relation

In my NestJs app I have two entities: person.entity.ts and property.entity.ts, the two are connected with OneToMany relation. I have created DTOs for both person and property.
The owning side of the relation is defined in Person like this:
#JoinColumn()
#OneToMany(
(type) => PersonProperties,
(personProperties) => personProperties.person,
{ cascade: ['insert', 'update'] },
)
personProperties: PersonProperties[];
My Controller for posting new Person looks like this:
#Post()
async create(#Body() createPersonDto: CreatePersonDto) {
return this.personService.create(createPersonDto);
}
For validation I am using Global ValidationPipes as below:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
transformOptions: {
enableImplicitConversion: true,
},
}),
);
How should I modify my code so I can post (and validate) a Person with their properties with a single Request? Isn't this going to include one DTO inside another?
What you must use are Pipes Read more about it
In your case, a Validation Pipe is required to validate the incoming request against your DTO.
Pipes are used inside a controller, though you can read about creating custom pipes inbuilt ValidationPipe would satisfy your needs for now.
METHOD 1
#Post()
async create(#Body(ValidationPipe) createPersonDto: CreatePersonDto) {
return this.personService.create(createPersonDto);
}
METHOD 2
#Post()
#UsePipes(ValidationPipe)
async create(#Body() createPersonDto: CreatePersonDto) {
return this.personService.create(createPersonDto);
}
Both methods essentially do the same thing. It takes your Body request object and validates it against your defined DTO.
Hope this answers your question.
The solution is to create a composite DTO object as follows:
export class CreatePersonWithPropertiesDto {
#ValidateNested()
#Type(() => CreatePersonDto)
neuron: CreatePersonDto;
#IsOptional()
#ValidateNested({ each: true })
#Type(() => CreatePersonPropertiesDto)
personProperties?: CreatePersonPropertiesDto[];
}
and then adjust the controller to expect the composite DTO:
#Post()
async create(#Body() createPersonWithPropertiesDto: CreatePersonWithPropertiesDto) {
return this.personService.create(createPersonWithPropertiesDto);
}

How to test Validation pipe is throwing the expect error for improperly shaped request on NestJS

I'm using NestJS 7.0.2 and have globally enabled validation pipes via app.useGlobalPipes(new ValidationPipe());.
I'd like to be able to have a unit test that verifies that errors are being thrown if the improperly shaped object is provided, however the test as written still passes. I've seen that one solution is to do this testing in e2e via this post, but I'm wondering if there is anything I'm missing that would allow me to do this in unit testing.
I have a very simple controller with a very simple DTO.
Controller
async myApi(#Body() myInput: myDto): Promise<myDto | any> {
return {};
}
DTO
export class myDto {
#IsNotEmpty()
a: string;
#IsNotEmpty()
b: string | Array<string>
}
Spec file
describe('generate', () => {
it('should require the proper type', async () => {
const result = await controller.generate(<myDto>{});
// TODO: I expect a validation error to occur here so I can test against it.
expect(result).toEqual({})
})
})
It also fails if I do not coerce the type of myDto and just do a ts-ignore on a generic object.
Just test your DTO with ValidationPipe:
it('validate DTO', async() => {
let target: ValidationPipe = new ValidationPipe({ transform: true, whitelist: true });
const metadata: ArgumentMetadata = {
type: 'body',
metatype: myDto,
data: ''
};
await target.transform(<myDto>{}, metadata)
.catch(err => {
expect(err.getResponse().message).toEqual(["your validation error"])
})
});
You can find here complete test examples for ValidationPipe in Nestjs code repository
To test custom ValidationPipe:
let target = new ValidationDob();
const metadata: ArgumentMetadata = {
type: 'query',
metatype: GetAgeDto,
data: '',
};
it('should throw error when dob is invalid', async () => {
try {
await target.transform(<GetAgeDto>{ dob: 'null' }, metadata);
expect(true).toBe(false);
} catch (err) {
expect(err.getResponse().message).toEqual('Invalid dob timestamp');
}
});

react js request result

Hello I have a problem getting a value and setting in my state in react
I can see data in console of my api response and everything goes well.
export default class index extends Component {
constructor(props){
super(props)
this.state={ products: [], filteredProducts:[]}
}
componentDidMount(){
api.get('/products').then( result => this.setState({
products: result.data.listProducts,
filteredProducts: result.data.listProducts
}))
console.log(this.state.products)
}
but when I console my state value, it appears an empty array
index.js:16 [] console.log(this.state
index.js:11 (5) [{…}, {…}, {…}, {…}, {…}] console.log( data request
Well I don't know if it's a problem with my back end
I made a map to filter what I will return to my front end since I have
an array of 3 objects
I don't know if I made the best option or if I can do better, if I can improve the code I would be happy if someone could alert me:
async getAllProduct(req,res){
try {
const results = await Products.findAll({
// raw: true, <= remove
attributes:['id','name', 'float', 'price'],
include: [{
model: SubCategory,
as: 'subcategory',
attributes: ['id','name'],
},
{
model:Exteriors,
as: 'exteriors',
attributes: ['id','name']
},
{
model:Types,
as: 'types',
attributes: ['id','name']
},
],
})
const listProducts = []
results.map(record =>
record.get({ plain: true }));
results.map( (products) => {
const model = {
id: products.id,
name: products.name,
float: products.float,
price: products.price,
id_sub: products.subcategory.id,
subcategory: products.subcategory.name,
id_types: products.types.id,
type: products.types.name,
id_ext: products.exteriors.id,
exterior: products.exteriors.name,
}
listProducts.push(model);
})
if(listProducts){return res.status(200).json({listProducts})}
else{return res.status(400).json({result: 'failed to get Products'})}
} catch (error) {
console.error(error);
}
}
setState is async, you can't see updated state right after setting the state,
You can have callback in setState to check the updated state,
this.setState({
products: result.data.listProducts,
filteredProducts: result.data.listProducts
}, () => console.log(this.state.products)) //callback method
If you console.log right after a state update you will log the old state. Try logging the state in componentDidUpdate to see if the state is actually empty:
componentDidUpdate() {
console.log(this.state)
}

Mongodb/mongoose omit a field in response [duplicate]

I have a NodeJS application with Mongoose ODM(Mongoose 3.3.1). I want to retrieve all fields except 1 from my collection.For Example: I have a collection Product Which have 6 fields,I want to select all except a field "Image" . I used "exclude" method, but got error..
This was my code.
var Query = models.Product.find();
Query.exclude('title Image');
if (req.params.id) {
Query.where('_id', req.params.id);
}
Query.exec(function (err, product) {
if (!err) {
return res.send({ 'statusCode': 200, 'statusText': 'OK', 'data': product });
} else {
return res.send(500);
}
});
But this returns error
Express
500 TypeError: Object #<Query> has no method 'exclude'.........
Also I tried, var Query = models.Product.find().exclude('title','Image'); and var Query = models.Product.find({}).exclude('title','Image'); But getting the same error. How to exclude one/(two) particular fields from a collection in Mongoose.
Use query.select for field selection in the current (3.x) Mongoose builds.
Prefix a field name you want to exclude with a -; so in your case:
Query.select('-Image');
Quick aside: in JavaScript, variables starting with a capital letter should be reserved for constructor functions. So consider renaming Query as query in your code.
I don't know where you read about that .exclude function, because I can't find it in any documentation.
But you can exclude fields by using the second parameter of the find method.
Here is an example from the official documentation:
db.inventory.find( { type: 'food' }, { type:0 } )
This operation returns all documents where the value of the type field is food, but does not include the type field in the output.
Model.findOne({ _id: Your Id}, { password: 0, name: 0 }, function(err, user){
// put your code
});
this code worked in my project. Thanks!! have a nice day.
You could do this
const products = await Product.find().select(['-image'])
I am use this with async await
async (req, res) => {
try {
await User.findById(req.user,'name email',(err, user) => {
if(err || !user){
return res.status(404)
} else {
return res.status(200).json({
user,
});
}
});
} catch (error) {
console.log(error);
}
In the updated version of Mongoose you can use it in this way as below to get selected fields.
user.findById({_id: req.body.id}, 'username phno address').then(response => {
res.status(200).json({
result: true,
details: response
});
}).catch(err => {
res.status(500).json({ result: false });
});
I'm working on a feature. I store a userId array name "collectedUser" than who is collected the project. And I just want to return a field "isCollected" instead of "collectedUsers". So select is not what I want. But I got this solution.
This is after I get projects from database, I add "isCollected".
for (const item of projects) {
item.set("isCollected", item.collectedUsers.includes(userId), {
strict: false,
})
}
And this is in Decorator #Schema
#Schema({
timestamps: true,
toObject: {
virtuals: true,
versionKey: false,
transform: (doc, ret, options): Partial<Project> => {
return {
...ret,
projectManagers: undefined,
projectMembers: undefined,
collectedUsers: undefined
}
}
}
})
Finally in my controller
projects = projects.map(i => i.toObject())
It's a strange tricks that set undefined, but it really work.
Btw I'm using nestjs.
You can do it like this
const products = await Product.find().select({
"image": 0
});
For anyone looking for a way to always omit a field - more like a global option rather than doing so in the query e.g. a password field, using a getter that returns undefined also works
{
password: {
type: String,
required: true,
get: () => undefined,
},
}
NB: Getters must be enabled with option { toObject: { getters:true } }
you can exclude the field from the schema definition
by adding the attribute
excludedField : {
...
select: false,
...
}
whenever you want to add it to your result,
add this to your find()
find().select('+excludedFiled')

Resources