I am trying to create simple appliaction with Nest.js, GraphQL and MongoDB. I wnated to use TypeORM and TypeGraphql to generate my schema and make a connection with localhost databasebut but i can not run my server with nest start becouse I am getting this error:
UnhandledPromiseRejectionWarning: Error: Cannot determine GraphQL output type for getArticles
I have no idea why i am getting this error. My class ArticleEntity does't has any not primary types, so there should not be any problem. I tried to remove () => ID from #Field() decorator of filed _id of ArticleEntity class but it didn't helped
ArticleResolver
#Resolver(() => ArticleEntity)
export class ArticlesResolver {
constructor(
private readonly articlesService: ArticlesService) {}
#Query(() => String)
async hello(): Promise<string> {
return 'Hello world';
}
#Query(() => [ArticleEntity])
async getArticles(): Promise<ArticleEntity[]> {
return await this.articlesService.findAll();
}
}
ArticleService
#Injectable()
export class ArticlesService {
constructor(
#InjectRepository(ArticleEntity)
private readonly articleRepository: MongoRepository<ArticleEntity>,
) {}
async findAll(): Promise<ArticleEntity[]> {
return await this.articleRepository.find();
}
}
ArticleEntity
#Entity()
export class ArticleEntity {
#Field(() => ID)
#ObjectIdColumn()
_id: string;
#Field()
#Column()
title: string;
#Field()
#Column()
description: string;
}
ArticleDTO
#InputType()
export class CreateArticleDTO {
#Field()
readonly title: string;
#Field()
readonly description: string;
}
If you need anything else comment
ArticleEntity should be decorated with the #ObjectType decorator as shown in the docs https://typegraphql.com/docs/types-and-fields.html.
#Entity()
#ObjectType()
export class ArticleEntity {
...
}
For anyone who gets this error and uses enums, you may be missing a call to registerEnumType.
In my case, I was using the #ObjectType decorator, but I was importing from type-graphql. I imported from #nestjs/graphql instead, and the problem was resolved.
import { ObjectType } from '#nestjs/graphql';
See here for a related discussion on GitHub.
I was using MongoDB and I had my Query return the schema instead of the model class.
Changing #Query((returns) => UserSchema) to #Query((returns) => User) fixed the issue for me.
user.schema.ts
#ObjectType()
#Schema({ versionKey: `version` })
export class User {
#Field()
_id: string
#Prop({ required: true })
#Field()
email: string
#Prop({ required: true })
password: string
}
export const UserSchema = SchemaFactory.createForClass(User)
user.resolver.ts
#Query((returns) => User)
async user(): Promise<UserDocument> {
const newUser = new this.userModel({
id: ``,
email: `test#test.com`,
password: `abcdefg`,
})
return await newUser.save()
}
Output model class should be decorated with #ObjectType() and then all properties of that class will decorated with #Field().
For any one who is using a custom output model class and NOT an entity(sequelize, typeorm, prisma etc). Because I was using database entity first, everything was working fine till I moved to a more customized output model.
One more case would be someone using class A as output and class B is used within A
export class A{
id: number;
name:string;
childProperty: B
. . . . .
}
export class B{
prop1:string;
prop2:string;
}
In that case class B should also be decorated with #ObjectType and fields (prop1 , prop2 ) should be also be decorated with #Field as well.
Related
At the moment, I have a very simple class-validator file with a ValidationPipe in Nest.js as follows:
import {
IsDateString,
IsEmail,
IsOptional,
IsString,
Length,
Max,
} from 'class-validator';
export class UpdateUserDto {
#IsString()
id: string;
#Length(2, 50)
#IsString()
firstName: string;
#IsOptional()
#Length(2, 50)
#IsString()
middleName?: string;
#Length(2, 50)
#IsString()
lastName: string;
#IsEmail()
#Max(255)
email: string;
#Length(8, 50)
password: string;
#IsDateString()
dateOfBirth: string | Date;
}
Lets say in the above "UpdateUserDto," the user passes an "email" field. I want to build a custom validation rule through class-validator such that:
Check if email address is already taken by a user from the DB
If the email address is already in use, check if the current user (using the value of 'id' property) is using it, if so, validation passes, otherwise, if it is already in use by another user, the validation fails.
While checking if the email address is already in use is a pretty simple task, how would you be able to pass the values of other properties within the DTO to a custom decorator #IsEmailUsed
It was pretty simple to solve, I solved it by creating a custom class-validation Decorator as below:
import { PrismaService } from '../../prisma/prisma.service';
import {
registerDecorator,
ValidationOptions,
ValidatorConstraint,
ValidatorConstraintInterface,
ValidationArguments,
} from 'class-validator';
import { Injectable } from '#nestjs/common';
#ValidatorConstraint({ name: 'Unique', async: true })
#Injectable()
export class UniqueConstraint implements ValidatorConstraintInterface {
constructor(private readonly prisma: PrismaService) {}
async validate(value: any, args: ValidationArguments): Promise<boolean> {
const [model, property = 'id', exceptField = null] = args.constraints;
if (!value || !model) return false;
const record = await this.prisma[model].findUnique({
where: {
[property]: value,
},
});
if (record === null) return true;
if (!exceptField) return false;
const exceptFieldValue = (args.object as any)[exceptField];
if (!exceptFieldValue) return false;
return record[exceptField] === exceptFieldValue;
}
defaultMessage(args: ValidationArguments) {
return `${args.property} entered is not valid`;
}
}
export function Unique(
model: string,
uniqueField: string,
exceptField: string = null,
validationOptions?: ValidationOptions,
) {
return function (object: any, propertyName: string) {
registerDecorator({
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
constraints: [model, uniqueField, exceptField],
validator: UniqueConstraint,
});
};
}
However, to allow DI to that particular Decorator, you need to also add this to your main.ts bootstrap function:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
...
// Line below needs to be added.
useContainer(app.select(AppModule), { fallbackOnErrors: true });
...
}
Also, make sure to import the "Constraint" in the app module:
#Module({
imports: ...,
controllers: [AppController],
providers: [
AppService,
PrismaService,
...,
// Line below added
UniqueConstraint,
],
})
export class AppModule {}
Finally, add it to your DTO as such:
export class UpdateUserDto {
#IsString()
id: string;
#IsEmail()
#Unique('user', 'email', 'id') // Adding this will check in the user table for a user with email entered, if it is already taken, it will check if it is taken by the same current user, and if so, no issues with validation, otherwise, validation fails.
email: string;
}
Luckily for us, the class-validator provides a very handy useContainer function, which allows setting the container to be used by the class-validor library.
So add this code in your main.ts file (app variable is your Nest application instance):
useContainer(app.select(AppModule), { fallbackOnErrors: true });
It allows the class-validator to use the NestJS dependency injection container.
#ValidatorConstraint({ name: 'emailId', async: true })
#Injectable()
export class CustomEmailvalidation implements ValidatorConstraintInterface {
constructor(private readonly prisma: PrismaService) {}
async validate(value: string, args: ValidationArguments): Promise<boolean> {
return this.prisma.user
.findMany({ where: { email: value } })
.then((user) => {
if (user) return false;
return true;
});
}
defaultMessage(args: ValidationArguments) {
return `Email already exist`;
}
}
Don't forget to declare your injectable classes as providers in the appropriate module.
Now you can use your custom validation constraint. Simply decorate the class property with #Validate(CustomEmailValidation) decorator:
export class CreateUserDto {
#Validate(customEmailValidation)
email: string;
name: string;
mobile: number;
}
If the email already exists in the database, you should get an error with the default message "Email already exists". Although using #Validate() is fine enough, you can write your own decorator, which will be much more convenient. Having written Validator Constraint is quick and easy. We need to just write decorator factory with registerDecorator() function.
export function Unique(validationOptions?: ValidationOptions) {
return function (object: any, propertyName: string) {
registerDecorator({
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
validator: CustomEmailvalidation,
});
};
}
As you can see, you can either write new validator logic or use written before validator constraint (in our case - Unique class).
Now we can go back to our User class and use the #Unique validator instead of the #Validate(CustomEmailValidation) decorator.
export class CreateUserDto {
#Unique()
email: string;
name: string;
mobile: number;
}
I think your first use case (Check if email address is already taken by a user from the DB), can be solved by using custom-validator
For the second one there is no option to get the current user before the validation. Suppose you are getting the current user using the #CurrentUser decorator. Then once the normal dto validation is done, you need to check inside the controller or service if the current user is accessing your resource.
My function save of an object (in model.ts file) which is created by typegoose should return Promise<Todo> but it returns Promise<Document<any, {}>>
and i have this error :
Type 'Document<any, {}>' is missing the following properties from type 'Todo': createdAt, updatedAt, content, isDone
How should i correct this ?
model ts
import { getModelForClass } from "#typegoose/typegoose";
import { ObjectId } from "mongodb";
import { Todo } from "../../entities";
import { NewTodoInput } from "./input";
// This generates the mongoose model for us
export const TodoMongooseModel = getModelForClass(Todo);
export default class TodoModel {
async getById(_id: ObjectId): Promise<Todo | null> {
// Use mongoose as usual
return TodoMongooseModel.findById(_id).lean().exec();
}
async create(data: NewTodoInput): Promise<Todo> {
const todo = new TodoMongooseModel(data);
return todo.save();
}
}
input ts
import { Field, InputType, ID } from "type-graphql";
import { MaxLength, MinLength } from "class-validator";
#InputType()
export class NewTodoInput {
#Field()
#MaxLength(300)
#MinLength(1)
content: string | undefined;
}
entities todo ts
import { ObjectType, Field } from "type-graphql";
import { prop } from "#typegoose/typegoose";
import { ObjectId } from "mongodb";
#ObjectType()
export class Todo {
#Field()
readonly _id!: ObjectId;
#prop()
#Field(() => Date)
createdAt!: Date;
#prop()
#Field(() => Date)
updatedAt!: Date;
#prop()
#Field()
content!: string;
#prop({ default: false })
#Field()
isDone!: boolean;
}
Thank you.
with the unofficial mongoose types, it is an known problem that await document.save() does not return the correct typings
-> this is not an problem with typegoose, it is with #types/mongoose
the workaround is to use:
await doc.save();
return doc;
(or maybe in your case directly use the mongoose provided function .create)
PS: this is not a problem in the official types of mongoose (can be currently used with typegoose 8.0.0-beta.x)
When implementing User entity and Roles entity in TypeORM, I used #ManyToMany with eager on true.
I implemented a UserRepository that extends Repository.
When using this.find() it works, without a problem (but also loads the password and other fields an API doesn't need to serve). When using this.find({select: 'email firstname roles'}), it suddenly gives me this error:
RequestError: Invalid column name 'userId'.
I also tried adding option relations, but that gives me error
QueryFailedError: Error: Invalid column name 'userId'.
Can anyone help me with this?
Node version: 12.16.2
TypeORM version: 0.2.24
Typescript version: 3.7.4
Database: SQL Server
Role entity:
#Entity()
export class Role {
#ManyToMany(type => User, user => user.roles)
#PrimaryColumn()
role!: string
}
User Entity
#Entity()
export class User {
#PrimaryGeneratedColumn()
id!: number;
#Column()
public email!: string;
#Column()
public password!: string;
#Column()
public firstname!: string;
#ManyToMany(type => Role, role => role.role, {eager: true, nullable: true})
#JoinTable()
public roles!: Role[];
}
User Repository:
#EntityRepository(User)
export class UserRepository extends Repository<User> {
whitelist: IWhitelist<User> = {
admin: ['email', 'firstname','roles', 'id',]
};
getOptions = (list: string) => {
return {select: this.whitelist[list], relations: ['roles']};
};
adminGetUsers = async (): Promise<Array<User> | undefined> => {
return await this.find(this.getOptions('admin'));
};
}
Have you tried
this.find({select: ['email', 'firstname', 'roles']}
from the documentation :
https://github.com/typeorm/typeorm/blob/master/docs/find-options.md#basic-options
I have created below mention controller,model and repository in my code. Please have look.
I have developed below mention code but still not able to perform the join operation.
I am going to join two table that is person and info table.
- Info table having one foreign key which is belong to person table.
- Person table: id, name, status
- Info table : id, person_id , name , status
I have also create repository,model and controller file for info and person.
Person Repository ( person.repository.ts)
) {
super(Person, dataSource);
this.infos = this._createHasOneRepositoryFactoryFor(
'info',
getInfoRepository,
);
}
Person Module ( person.module.ts)
#hasOne(() => Info)
infos?: Info;
constructor(data?: Partial<Person>) {
super(data);
}
Info Module (info.module.ts)
#belongsTo(() => Person)
personId: number;
constructor(data?: Partial<Info>) {
super(data);
}
It show me error like this
Unhandled error in GET /people/fetchfromtwotable?filter[offset]=0&filter[limit]=10&filter[skip]=0: 500 TypeError: Cannot read property 'target' of undefined
Is there any idea about join?
drp, Thanks for sharing your models. My post got deleted because I am just starting out and needed to ask for more info which seems strange. ANYWAY, Try to change this line:
this.infos = this._createHasOneRepositoryFactoryFor(
'info',
getInfoRepository
);
to
this.infos = this._createHasOneRepositoryFactoryFor(
'infos',
getInfoRepository,
);
The framework cannot find the 'info' relation on the model because you called the property 'infos'
Here is my example that currently works for me (running latest lb4 and postgres):
User.model.ts
import { model, property, hasOne, Entity } from '#loopback/repository';
import { Address } from './address.model';
#model()
export class User extends Entity {
constructor(data?: Partial<User>) {
super(data);
}
#property({ id: true })
id: number;
#property()
email: string;
#property()
isMember: boolean;
#hasOne(() => Address, {})
address?: Address;
}
Address.model.ts:
import { model, property, belongsTo, Entity } from '#loopback/repository';
import { User } from '../models/user.model';
#model()
export class Address extends Entity {
constructor(data?: Partial<Address>) {
super(data);
}
#property({ id: true })
id: number;
#property()
street1: string;
#property()
street2: string;
#property()
city: string;
#property()
state: string;
#property()
zip: string;
#belongsTo(() => User)
userId: number;
}
User.repository.ts:
import { HasOneRepositoryFactory, DefaultCrudRepository, juggler, repository } from '#loopback/repository';
import { User, Address } from '../models';
import { PostgresDataSource } from '../datasources';
import { inject, Getter } from '#loopback/core';
import { AddressRepository } from '../repositories'
export class UserRepository extends DefaultCrudRepository<
User,
typeof User.prototype.id
> {
public readonly address: HasOneRepositoryFactory<Address, typeof User.prototype.id>;
constructor(
#inject('datasources.postgres')
dataSource: PostgresDataSource,
#repository.getter('AddressRepository')
protected getAccountRepository: Getter<AddressRepository>,
) {
super(User, dataSource);
this.address = this._createHasOneRepositoryFactoryFor('address', getAccountRepository);
} // end ctor
}
User.controller.ts (abridged for length):
#get('/users/{id}/address')
async getAddress(
#param.path.number('id') userId: typeof User.prototype.id,
#param.query.object('filter', getFilterSchemaFor(Address)) filter?: Filter,
): Promise<Address> {
return await this.userRepository
.address(userId).get(filter);
}
Hope this helps.
Good luck!
I 'm trying to bind my Model with a mongoose schema using Typescript.
I have my IUser interface:
export interface IUser{
_id: string;
_email: string;
}
My User class:
export class User implements IUser{
_id: string;
_email: string;
}
My RepositoryBase:
export class RepositoryBase<T extends mongoose.Document> {
private _model: mongoose.Model<mongoose.Document>;
constructor(schemaModel: mongoose.Model<mongoose.Document>) {
this._model = schemaModel;
}
create(item: T): mongoose.Promise<mongoose.model<T>> {
return this._model.create(item);
}
}
And finally my UserRepository which extends RepositoryBase and implements an IUserRepository (actually empty):
export class UserRepository extends RepositoryBase<IUser> implements IUserRepository{
constructor(){
super(mongoose.model<IUser>("User",
new mongoose.Schema({
_id: String,
_email: String,
}))
)
}
}
Thr problem is that typescript compiler keeps saying :
Type 'IUser' does not satisfy the constraint 'Document'
And if I do:
export interface IUser extends mongoose.Document
That problem is solved but the compiler says:
Property 'increment' is missing in type 'User'
Really, i don't want my IUser to extend mongoose.Document, because neither IUser or User should know about how Repository work nor it's implementation.
I solved the issue by referencing this blog post.
The trick was to extends the Document interface from mongoose like so:
import { Model, Document } from 'mongoose';
interface User {
id: string;
email: string;
}
interface UserModel extends User, Document {}
Model<UserModel> // doesn't throw an error anymore