I'm posting here because I have been stuck on a problem for few hours now.
I am creating an API using Nest JS 8 and MongoDB, and I test it using Postman. When I want to execute a POST request (http://localhost:3000?nom=Antoine) to insert an object in my database, I have an error (500 : Internal server error) message that says "Client validation failed: nom: Path 'nom' is required (nom is the name of my object's property).
I've wandered every topic about this kind of issue, tried to upgrade my version of Nest, to use a middleware, to make sure the right version of every depedency was installed.
I don't want to remove the "required: true" property because i think it is necessary. I tried to set it to "false", which enabled me to insert the object in the database but without my property 'nom' (name in french).
If you guys have any help, here's my schema :
import { Prop, Schema, SchemaFactory } from '#nestjs/mongoose';
import { Document } from 'mongoose';
export type ClientDocument = Client & Document;
#Schema()
export class Client {
#Prop({ required: true })
nom: string;
}
export const ClientSchema = SchemaFactory.createForClass(Client);
And here is my controller :
import { Body, Controller, Delete, Get, Param, Post, Put} from '#nestjs/common';
import { ClientService } from './client.service';
import { ClientDto } from './dto/client.dto';
import { CreateClientDto } from './dto/create-client.dto';
import { UpdateClientDto } from './dto/update-client.dto';
#Controller('/client')
export class ClientController {
constructor(private readonly clientService: ClientService) {}
#Get()
async index(){
return await this.clientService.findAll();
}
#Get(':id')
async find(#Param('id') id: string) {
return await this.clientService.findOne(id);
}
#Post()
async create(#Body() createClientDto: CreateClientDto) {
console.log(createClientDto);
return await this.clientService.create(createClientDto);
}
#Put(':id')
async update(#Param('id') id: string, #Body() updateClientDto: ClientDto) {
return await this.clientService.update(id, updateClientDto);
}
#Delete(':id')
async delete(#Param('id') id: string) {
return await this.clientService.delete(id);
}
}
Thanks for looking
I found the solution (i still don't know why it works this way tho).
In my client.service.ts, i updated the create function from this :
async create(createClientDto: CreateClientDto): Promise<Client> {
return await new this.model({createClientDto}).save();
}
To this
async create(createClientDto: CreateClientDto): Promise<Client> {
return await new this.model({
...createClientDto,
createdAt: new Date(),
}).save();
}
Thanks for taking the time to answer, I hope this will help
Related
I try to use class-transformer but i can't do it.
I also use type-graphql and #typegoose/typegoose
Here's my code:
My Decorator
import { Transform } from 'class-transformer';
export function Trim() {
console.log('DECORATOR');
return Transform(({ value }: { value: string }) => {
console.log('value: ', value);
return value.trim();
});
}
My InputType
import { IsEmail } from 'class-validator';
import { InputType, Field } from 'type-graphql';
import { User } from '../Entities/User';
import { Trim } from '../../Decorators/Sanitize';
#InputType({ description: 'Input for user creation' })
export class AddUserInput implements Partial<User> {
#Field()
#Trim()
#IsEmail({ domain_specific_validation: true, allow_utf8_local_part: false })
email!: string;
}
My Resolver
import { Arg, Mutation, Resolver } from 'type-graphql';
import { User } from '../Entities/User';
import { AddUserInput } from '../Types/UsersInputs';
#Resolver(() => User)
export class UserResolvers {
#Mutation(() => String, { description: 'Register an admin' })
async createAccount(#Arg('data') data: AddUserInput): Promise<string> {
console.log({ ...data });
return data.email;
}
}
My Entity
import { prop, getModelForClass } from '#typegoose/typegoose';
import { ObjectType, Field } from 'type-graphql';
#ObjectType({ description: 'User model' })
export class User {
#Field({ description: 'The user email' })
#prop({ required: true, unique: true, match: [/\S+#\S+\.\S+/, 'is invalid'] })
email!: string;
}
export const UserModel = getModelForClass(User, {
schemaOptions: { timestamps: true }
});
My Request
POST http://localhost:5000/app HTTP/1.1
Content-Type: application/json
X-REQUEST-TYPE: GraphQL
mutation
{
createAccount (data : {
email: " email#gmail.com ",
})
}
The problem is that the console.log('value: ', value) inside Transform function is never call and my email is not trim.
Also console.log('DECORATOR') is not call when I do the request but just one time when server starting.
Thanks !
Typegoose transpiles classes into mongoose schemas & models, it does not apply any validation / transformation aside from mongoose provided ones, so your class-transformer decorators would only be called when using the class directly and using its functions. (like plainToClass and classToPlain)
In your case, it would be better to either use PropOptions get & set or pre-hooks.
As a note, typegoose provides a guide for using class-transformer with typegoose classes Integration Example: class-transformer, just to show that it can be used like normal classes.
Also note, it is currently recommended against using class-transformer because of mentioned issues inside the documentation.
Let say I have a simple collection events created by TypeGraphql and Typegoose which stores objects like:
{ _id: ObjectId(...), name: 'SomeEvent', category: ObjectId('...') }
and corresponding type:
#ObjectType()
export class Event {
#Field(() => ID)
_id!: Types.ObjectId
#prop({ ref: 'Category' })
#Field(() => Category)
category!: Ref<Category>
#prop()
#Field()
name!: string
}
I have also collection categories which contains right now only _id and name.
Now I want to insert some Event to database. Is it possible to automatically check if categoryId provided in input exist in collection categories and if it does not, throw an error? Right now, event can be added with anything in category field and next when I try to get it by query it throws an error that category cannot be resolved because there is no category with this ID. I know, that I can check it manually during adding event but if I have more fields like that it will be problematic.
With the help of Martin Devillers answer I was able to write a validator to validate referenced documents using class-validator with typegoose.
This is my refdoc.validator.ts:
import { ValidationArguments, ValidatorConstraint, ValidatorConstraintInterface } from "class-validator";
import { Injectable } from "#nestjs/common";
import { getModelForClass } from "#typegoose/typegoose";
#ValidatorConstraint({ name: "RefDoc", async: true })
#Injectable()
export class RefDocValidator implements ValidatorConstraintInterface {
async validate(refId: string, args: ValidationArguments) {
const modelClass = args.constraints[0];
return getModelForClass(modelClass).exists({ _id: refId })
}
defaultMessage(): string {
return "Referenced Document not found!";
}
}
Then I can apply it on the DTO or model with the #Validate-Decorator. The Argument I'm passing in is the typegoose model.
#Validate(RefDocValidator, [Costcenter])
costcenterId: string;
Seems to be working for me, I'm open for any improvements..
Edit: Even better with custom decorator, as Martin Devillers suggested:
refdoc.validator.ts
import { registerDecorator, ValidationArguments, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface } from "class-validator";
import { Injectable } from "#nestjs/common";
import { getModelForClass } from "#typegoose/typegoose";
#ValidatorConstraint({ name: "RefDoc", async: true })
#Injectable()
export class RefDocValidator implements ValidatorConstraintInterface {
async validate(refId: string, args: ValidationArguments) {
const modelClass = args.constraints[0];
return getModelForClass(modelClass).exists({ _id: refId })
}
defaultMessage(): string {
return "Referenced Document not found!";
}
}
export function RefDocExists(modelClass: any, validationOptions?: ValidationOptions) {
return function (object: Object, propertyName: string) {
registerDecorator({
name: 'RefDocExists',
target: object.constructor,
propertyName: propertyName,
constraints: [modelClass],
options: validationOptions,
validator: RefDocValidator,
});
};
}
Then you can use it on the DTO like:
#ApiProperty()
#IsNotEmpty()
//#Validate(RefDocValidator, [Costcenter]) old
#RefDocExists(Costcenter) //new
costcenterId: string;
Out of the box, neither MongoDB nor mongoose nor typegoose offer any automated referential integrity checks.
At the database level, this feature doesn't exist (which is also one of the fundamental differences between a database like MongoDB and SQL Server/Oracle).
However, at the application level, you can achieve this behavior in various ways:
Middleware. Mongoose middleware allows you add generalized behavior to your models. This is useful if you're inserting documents in your EventModel in various places in your codebase and don't want to repeat your validation logic. For example:
EventModel.path('category').validate(async (value, respond) => {
const categoryExists = await CategoryModel.exists({ _id: value })
respond(categoryExists)
})
Mongoose plugins. A plugin like mongoose-id-validator allows you to add the above behavior to any type of document reference in all your schemas.
Manually. Probably the least favorite option, but I'm mentioning it for completeness sake: with mongoose's Model.exists() you can achieve this with a one-liner like: const categoryExists = await CategoryModel.exists({ _id: event.category })
To reiterate: all the above options are stopgaps. It's always possible for someone to go directly in your database and break referential integrity anyway.
I'm working on a ToDo list application in NodeJS, Koa, and GraphQL.
I wrote an update card mutation but when I run the query to update I get the following error:
Cannot perform update query because update values are not defined. Call "qb.set(...)" method to specify updated values.
The mutation:
import { getRepository } from 'typeorm';
import { Card } from '../../entities/card';
export const updateCardMutation = {
async updateCard(_, { id, patch }): Promise<Card> {
const repository = getRepository(Card);
const card = await repository.findOne({ id });
const result = await repository.update(id, patch);
return {
...card,
...patch,
};
},
};
I would like to know what I'm doing wrong and if something more is needed it please notify me so I will edit the question accordingly
card entity:
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
#Entity('cards')
export class Card {
#PrimaryGeneratedColumn('uuid')
id: string;
#CreateDateColumn()
created_at: Date;
#UpdateDateColumn()
updated_at: Date;
#Column('text')
title: string;
#Column('text', {
nullable: true,
})
description: string;
#Column('boolean', {
default: 'false',
})
done: boolean;
}
You need to spread the update Object.
export const updateCardMutation = {
async updateCard(_, { id, patch }): Promise<Card> {
const repository = getRepository(Card);
const card = await repository.findOne({ id });
const result = await repository.update(id, {...patch}); // here
return {
...card,
...patch,
};
},
};
The issue was when I was calling the updateMutation, it was creating the patch object of anonymous type. So it just needed to be clean before going to the DB engine
I resolved my issues by adding the following code:
{ ...patch }
Inside the next script:
export const updateCardMutation = {
async updateCard(_, { id, patch }): Promise<Card> {
const repository = getRepository(Card);
const card = await repository.findOne({ id });
const result = await repository.update(id, { ...patch }); // Added here
return {
...card,
...patch,
};
},
};
In this way, I was able to update my card.
https://github.com/typeorm/typeorm/blob/master/docs/update-query-builder.md
As an error Call qb.set() , typeorm query builder are different with other orm
await getRepository().createQueryBuilder().update(Card).set(patch)
.where("id = :id", { id })
.execute();
some how patch object may stringify [object], so you can spread it like set({...patch})
I have had this error before with my update query is nestjs & graqhql
Cannot perform update query because update values are not defined
I have fixed it by using the save() function from the repository on the same id, so I have changed from this
async update(
id: number,
updateEmployeeInput: UpdateEmployeeInput,
): Promise<Employee> {
await this.employeeRepository.update(id, updateEmployeeInput);
return this.employeeRepository.findOneOrFail(id);
}
to this
async update(
id: number,
updateEmployeeInput: UpdateEmployeeInput,
): Promise<Employee> {
await this.employeeRepository.save(updateEmployeeInput)
return this.employeeRepository.findOneOrFail(id);
}
Well, newbie with NodeJS and Mongoose, will try to get a document from a collection and use a class to manage results as i want to do.
I followed many tutos, but... i don't understand why, with the following code, i always get a null object with a findById() method on a model.
After hours spent, i decide to get help...
So, first i define a Model (simplified version) :
import { Schema, Model, model } from 'mongoose';
import { DocumentInterface } from './../interfaces/document-product-interface';
import { ProductClass } from './product-class';
var productSchema: Schema = new Schema(
{
_id: {
type: String,
required: 'Required _id'
},
id: {
type: String,
required: 'EAN required'
},
product_name: {
type: String
}
}
);
// Scheme methods
productSchema.method('title', ProductClass.prototype.title);
export const Products: Model<DocumentInterface> = model<DocumentInterface>('ProductClass', productSchema);
Next, create a class (for business purpose) :
import { ProductInterface } from './../interfaces/product-interface';
export class ProductClass implements ProductInterface{
public _id: String;
public id: String;
public product_name_fr?: string;
public product_name?: string;
public constructor() {}
public title(): string {
return 'something';
}
}
Next... The Document interface :
import { Document } from 'mongoose';
import { ProductInterface } from './product-interface';
import { ProductClass } from 'models/product-class';
export interface DocumentInterface extends ProductInterface, Document, ProductClass {}
Finally, just a controller, to get a product :
import { Products } from '../models/product-model';
import { SoappliProductInterface } from './../interfaces/soappli-product-interface';
import { Request, Response, NextFunction} from 'express';
export class ProductController {
public get(request: Request, response: Response, next: NextFunction) {
console.log('Search for a product : ' + request.params.ean);
Products.findById(request.params.ean, (error: any, product: DocumentInterface) => {
if (error) {
response.status(500).send( {message: error})
} else {
console.log('Product: ' + JSON.stringify(product));
if (product) {
console.log('Product title: ' + product.title);
response.status(200).send(product);
} else {
response.status(404).send({message: 'no matches for : ' + request.params.ean})
}
}
});
}
}
When i run with Postman and use a correct _id, got null for product...
If i remove all classes and interfaces, get the entire document...
What's wrong with this code ?
Thx
So, I am using typescript on a node/express/mongoose application and I am trying to have my code typecheck without errors.
I define this mongoose model:
import * as mongoose from 'mongoose';
const City = new mongoose.Schema({
name: String
});
interface ICity extends mongoose.Document {
name: string
}
export default mongoose.model<ICity>('City', City);
and this controller:
import * as Promise from 'bluebird';
import CityModel from '../models/city';
export type City = {
name: string,
id: string
};
export function getCityById(id : string) : Promise<City>{
return CityModel.findById(id).lean().exec()
.then((city) => {
if (!city) {
return Promise.reject('No Cities found with given ID');
} else {
return {
name: city.name,
id: String(city._id)
};
}
});
}
The problem is that for some reason, typescript resolves my 'getCityById' function as returning a Promise<{}> rather than a Promise<City> as it should.
Failed attempts:
I tried to wrap the return object in a Promise.resolve
I tried to use new Promise and rely on mongoose's callback API as opposed to their promise API
typescript resolves my 'getCityById' function as returning a Promise<{}> rather than a Promise as it should.
This is because of multiple return paths.
if (!city) {
return Promise.reject('No Cities found with given ID');
} else {
return {
name: city.name,
id: String(city._id)
};
}
Specifically Promise.reject is untyped.
Quick fix
Assert:
if (!city) {
return Promise.reject('No Cities found with given ID') as Promise<any>;
} else {
return {
name: city.name,
id: String(city._id)
};
}