TypeGraphQL: ArgumentValidationError on mutation - graphql

I'm playing around with TypeGraphQL and have a simple api setup to query projects and their associated clients.
For my createClient mutation, I'm receiving an ArgumentValidation Error, 'an unknown value was passed to the validate function' and can't pinpoint whats going wrong.
Client Schema
import { ObjectType, Field, ID } from "type-graphql";
#ObjectType()
class ClientSchema {
#Field(type => ID)
id: string
#Field()
name: string
#Field()
email: string
#Field()
phone: string
}
export default ClientSchema
Client Resolver
import { Arg, Field, InputType, Mutation, Query, Resolver } from "type-graphql";
import exampleClients from "../utils/clients";
import ClientSchema from "../schemas/ClientsSchema";
#InputType()
class CreateClassInput {
#Field()
name: string
#Field()
email: string
#Field()
phone: string
}
#Resolver()
class ClientResolver {
#Query((returns) => [ClientSchema])
async clients() {
return exampleClients;
}
#Query((returns) => ClientSchema)
async client(#Arg("id") id: string) {
const client = exampleClients.find((client) => client.id === id);
if (!client) throw new Error("No such client");
return client;
}
#Mutation((returns) => ClientSchema)
async createClient(#Arg("data") createClassData: CreateClassInput) {
const { name, email, phone } = createClassData;
const newClient: ClientSchema = {
id: String(exampleClients.length + 1),
name,
email,
phone
};
exampleClients.push(newClient);
return newClient;
}
}
export default ClientResolver;
GraphQL Query
mutation createClient {
createClient(data: { name: "Test Name", email: "Test Email", phone: "Test phone" }) {
name
}
}
If i pass the arguments to the mutation directly, without de-structuring an object, I don't get the error however with it I do. Is there something I'm doing wrong?

Related

GraphQLError: Syntax Error: Expected Name, found ":"

I am trying to Prisma along side with Apollo Server
and I keep getting this error
GraphQLError: Syntax Error: Expected Name, found ":"
this is the index.ts file
import { PrismaClient } from '#prisma/client';
import { ApolloServer } from 'apollo-server';
import { typeDefs } from './schema/schema';
import { Query } from './resolvers/Query';
import { Mutation } from './resolvers/Mutation';
const prisma = new PrismaClient();
const server = new ApolloServer({
typeDefs,
resolvers: {
Query,
Mutation,
},
context: {
prisma,
},
});
server.listen().then(({ url }: any) => {
console.log(`Server is running on ${url}`);
});
this is the schema.ts file
const { gql } = require('apollo-server');
export const typeDefs = gql`
type Query {
getProducts: [Product!]!
}
type Mutation {
addProduct(input: addProductInput): Boolean!
}
type Product {
name: String!
price: Float!
description: : String!
}
input addProductInput {
name: String!
price: Float!
description: : String!
}
`;
this is the Query.ts file in the resolvers folder
export const Query = {
getProducts: async (parent: any, args: any, { prisma }: any) => {
return await prisma.products.findMany();
},
};
this is the Query.ts file in the resolvers folder
export const Mutation = {
addProduct: async (parent: any, { input }: any, { prisma }: any) => {
const productData = {
name: input.name,
price: input.price,
description: input.description,
};
await prisma.products.create({
data: productData,
});
return true;
},
};
and lastly this is the Product model in schema.prisma file
model Product {
##map(name: "products")
id Int #id #default(autoincrement())
name String
price Float
description String
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
}
I have done some researches and all I got is that might be a missing bracket or a curly bracket, but I reviewed my code multiple times and did not find any mistakes.
In the schema definition, pay close attention to the Product type and addProductInput:
type Product {
name: String!
price: Float!
description: : String!
}
input addProductInput {
name: String!
price: Float!
description: : String!
}
`;
Are you sure the description fields should have two colons? I think they shouldn't have the middle one, and just be like description: String!

TypeGraphQL - Not able to match all the interfaces of a union

Summary
The goal is to declare the return type of a mutation using a union in order to express multiple states: Success and user errors
Being able to select concrete types according to the use cases:
mutation($data: CreateUserInput!) {
createUser(data: $data){
... on CreateUserSuccess {
user {
id
}
}
... on EmailTakenError {
emailWasTaken
}
... on UserError {
code
message
}
}
}
Implementation using TypeGraphQ:
#ObjectType()
class CreateUserSuccess {
#Field(() => User)
user: User
}
#ObjectType()
class EmailTakenError {
#Field()
emailWasTaken: boolean
}
const mapMutationValueKeyToObjectType = {
user: CreateUserSuccess,
code: UserError,
emailWasTaken: EmailTakenError
}
const CreateUserPayload = createUnionType({
name: 'CreateUserPayload',
types: () => [CreateUserSuccess, EmailTakenError, UserError] as const,
resolveType: mutationValue => {
const mapperKeys = Object.keys(mapMutationValueKeyToObjectType)
const mutationValueKey = mapperKeys.find((key) => key in mutationValue)
return mapMutationValueKeyToObjectType[mutationValueKey]
}
})
#InputType()
class CreateUserInput implements Partial<User> {
#Field()
name: string
#Field()
email: string
}
#Resolver(User)
export class UserResolver {
#Mutation(() => CreateUserPayload)
createUser (#Arg('data', {
description: 'Represents the input data needed to create a new user'
}) createUserInput: CreateUserInput) {
const { name, email } = createUserInput
return createUser({ name, email })
}
}
Data layer
export const createUser = async ({
name, email
}: { name: string; email: string; }) => {
const existingUser = await dbClient.user.findUnique({
where: {
email
}
})
if (existingUser) {
return {
code: ErrorCode.DUPLICATE_ENTRY,
message: "There's an existing user with the provided email.",
emailWasTaken: true
}
}
return dbClient.user.create({
data: {
name,
email
}
})
}
Issue
The response doesn't resolve all of the selected fields according to their unions, even by returning fields that are related to different types
if (existingUser) {
return {
code: ErrorCode.DUPLICATE_ENTRY,
message: "There's an existing user with the provided email.",
emailWasTaken: true
}
}
My doubt is this case is, why emailWasTaken is not being returned within the response if the EmailTakenError type is being selected?
This was an interpretation mistake on my part
The reasoning is that resolvers with a union type as the return definition should indeed just return one of those, in the case above, UserError and EmailTakenError wouldn't be returned on the same response
More info on this GitHub discussion

Schema must contain uniquely named types named "Project"

I am creating a Apollo Graphql backend using type-orm. I create an entity called Project:
import { Field, ObjectType } from "type-graphql";
import { BaseEntity, Column, Entity, ObjectID, ObjectIdColumn } from "typeorm";
#ObjectType()
#Entity()
export class Project extends BaseEntity {
#Field(() => String)
#ObjectIdColumn()
id: ObjectID;
#Field()
#Column({ unique: true })
name!: string;
#Field()
#Column()
startDate!: Date;
#Field()
#Column({nullable: true})
endDate!: Date
#Field()
#Column({unique:true})
githubUrl: string;
}
and the resolver project:
import { Arg, Mutation, Query, Resolver } from 'type-graphql'
import {Project} from '../entities/project'
import {ProjectInput, ProjectResponse} from '../types/ProjectTypes'
#Resolver()
export class ProjectResolver {
#Query(() => [Project])
async getProjects(): Promise<Project[] | null> {
let projects = await Project.getRepository().find();
return projects;
}
#Mutation(() => ProjectResponse)
async createProject(
#Arg("input") input: ProjectInput
): Promise<ProjectResponse>{
let project : Project;
if(input.name == ""){
throw Error("Invalid input")
}
try{
project = await Project.create({
name: input.name,
startDate: input.startDate,
}).save();
}catch (error) {
if (error.code === 11000) {
return {
errors: [
{
field: "project",
message: "The project name is already in use",
},
],
};
} else return error;
}
return {project: project};
}
#Mutation(() => ProjectResponse)
async setProjectEndDate(
#Arg("projectId") projectId: string,
#Arg("endDate") endDate: Date
): Promise<ProjectResponse>{
let project = await Project.getRepository().findOne(projectId)
if(project){
if(project?.startDate > endDate){
return {
errors:[{
field:"EndDate",
message:"The end date must be a date after the start date of a project."
}]
}
}
project.endDate = endDate;
project.save();
}
return {
errors:[{
field:"Project",
message:"Project could not be found."
}]
}
}
}
this is the code of the 2 auxiliary classes for the input and response of the resolver:
#InputType()
export class ProjectInput{
#Field()
name: string
#Field()
startDate: Date
#Field(()=> Date,{nullable:true})
endDate?: Date | null
#Field(()=> String, {nullable:true})
githubUrl?: string
}
#ObjectType()
export class ProjectResponse{
#Field(() => [FieldError], { nullable: true })
errors?: FieldError[]
#Field(() => Project, { nullable: true })
project?: Project | null
}
this is the code I use to create the ApolloServer object:
const apolloServer = new ApolloServer({
introspection: true,
playground: true,
schema: await buildSchema({
resolvers: [ProjectResolver],
validate: false, // Disable default GraphQL errors
}),
context: ({ req, res }) => ({ req, res}), // Enables use of context (with request) in resolvers
})
And the error I get is the following:
Error: Schema must contain uniquely named types but contains multiple types named "Project".
at new GraphQLSchema (C:\Users\User\Desktop\UPV\Proyectos\Cv web\myweb-backend\node_modules\graphql\type\schema.js:194:15)
at Function.generateFromMetadataSync (C:\Users\User\Desktop\UPV\Proyectos\Cv web\myweb-backend\node_modules\type-graphql\dist\schema\schema-generator.js:31:32)
at Function.generateFromMetadata (C:\Users\User\Desktop\UPV\Proyectos\Cv web\myweb-backend\node_modules\type-graphql\dist\schema\schema-generator.js:16:29)
at Object.buildSchema (C:\Users\User\Desktop\UPV\Proyectos\Cv web\myweb-backend\node_modules\type-graphql\dist\utils\buildSchema.js:10:61)
at C:\Users\User\Desktop\UPV\Proyectos\Cv web\myweb-backend\dist\index.js:42:38
at Generator.next ()
at fulfilled (C:\Users\User\Desktop\UPV\Proyectos\Cv web\myweb-backend\dist\index.js:5:58)
at processTicksAndRejections (internal/process/task_queues.js:97:5)
I don't know what the problem is so I would appreciate your help
I have managed to solve the problem by changing the import of the class 'Project' made in the 'ProjectResolver' class.
Instead of:
import {Project} from '../entities/project'
Now looks like this:
import {Project} from '../../src/entities/project'

TypeGraphql - #inputtype on typeorm

Hello I need to check if there is an email in the database already:
with this:
return User.findOne({ where: { email } }).then((user) => {
if (user) return false;
return true;
});
I have the following inputtypes:
#InputType()
export class RegisterInput {
#Field()
#IsEmail({}, { message: 'Invalid email' })
email: string;
#Field()
#Length(1, 255)
name: string;
#Field()
password: string;
}
I would like to know if there is any way for me to validate the email in the inputtype? or just in my resolve:
#Mutation(() => User)
async register(
#Arg('data')
{ email, name, password }: RegisterInput,
): Promise<User> {
const hashedPassword = await bcrypt.hash(password, 12);
const user = await User.create({
email,
name,
password: hashedPassword,
}).save();
return user;
}
Actually you can register your own decorator for class-validator
For example it can look something like this:
isEmailAlreadyExists.ts
import {
registerDecorator,
ValidationOptions,
ValidatorConstraint,
ValidatorConstraintInterface,
} from 'class-validator';
import { UserRepo } from '../../repositories/UserRepo';
import { InjectRepository } from 'typeorm-typedi-extensions';
#ValidatorConstraint({ async: true })
export class isEmailAlreadyExist
implements ValidatorConstraintInterface {
#InjectRepository()
private readonly userRepo: UserRepo;
async validate(email: string) {
const user = await this.userRepo.findOne({ where: { email } });
if (user) return false;
return true;
}
}
export function IsEmailAlreadyExist(validationOptions?: ValidationOptions) {
return function (object: Object, propertyName: string) {
registerDecorator({
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
constraints: [],
validator: isEmailAlreadyExist,
});
};
}
If you're injecting dependencies than you should in inject it in class-validator too. Simply add to your main file this:
import { Container } from 'typedi';
import * as classValidator from 'class-validator';
classValidator.useContainer(Container);
...
const schema = await buildSchema({
resolvers: [...],
container: Container,
});
Then you can use decorator in your InputType
import { InputType, Field } from 'type-graphql';
import { IsEmailAlreadyExist } from '../../../utils/validators/isEmailAlreadyExist';
#InputType()
export class YourInput {
#Field()
#IsEmailAlreadyExist()
email: string;
}
I actually just figured this out myself for my own project.
You can simply add a validation on the email from RegisterInput argument and throw an error if the email already exists.
import { Repository } from 'typeorm'
import { InjectRepository } from 'typeorm-typedi-extensions'
...
// Use dependency injection in the resolver's constructor
constructor(
#InjectRepository(User) private readonly userRepository: Repository<User>
) {}
...
// Your mutation
#Mutation(() => User)
async register(
#Arg('data')
{ email, name, password }: RegisterInput,
): Promise<User> {
const hashedPassword = await bcrypt.hash(password, 12);
const userWithEmail = this.userRepository.find({ email: email })
// If a user with the email was found
if (userWithEmail) {
throw new Error('A user with that email already exists!')
}
const user = await User.create({
email,
name,
password: hashedPassword,
}).save();
return user;
}
To use the InjectRepository make sure you add a "container" to your buildSchema function:
import { Container } from 'typedi'
...
const schema = await buildSchema({
resolvers: [...],
container: Container
})
Let me know if this works out for you? Thanks!

TypeORM GraphQL nested query returns null

I am trying to use a GraphQL nested query to get information about project a and customer. I can get the information about project but I am unable to get the customer info.
How do I properly get the nested information?
Project Model
import { Entity, BaseEntity, PrimaryGeneratedColumn, Column, ManyToOne} from "typeorm";
import { ObjectType, Field, ID } from "type-graphql";
import {Customer} from "./Customer";
#Entity('project')
#ObjectType()
export class Project extends BaseEntity {
#Field(() => ID)
#PrimaryGeneratedColumn()
id: number;
#Field(() => String)
#Column()
name: string;
#Field(() => String)
#Column()
short_name: string;
#Field(() => String)
#Column()
description: string;
#Field(() => Customer, { nullable: true })
#ManyToOne(type => Customer, customer => customer.projects)
customer: Customer;
}
User Model
import { Entity, BaseEntity, PrimaryGeneratedColumn, Column, OneToMany, JoinTable } from "typeorm";
import { ObjectType, Field, ID } from "type-graphql";
import {Project} from "./Project";
import {ProjectUser} from "./Project_handled_by_user";
#Entity('customer')
#ObjectType()
export class Customer extends BaseEntity {
#Field(() => ID)
#PrimaryGeneratedColumn()
id: number;
#Field(() => String)
#Column()
name: string;
#Field(() => String)
#Column()
short_name: string;
#Field(() => String)
#Column()
active: string;
#Field(() => [Project], { nullable: true })
#OneToMany(type => Project, project => project.customer)
projects: Project[];
}
Project Resolver
import { Resolver, Query, Mutation, Arg } from "type-graphql";
import { Project } from "../models/Project";
#Resolver()
export class ProjectResolver {
#Query(() => [Project])
projects() {
return Project.find();
}
#Query(() => Project)
project(#Arg("id") id: string) {
return Project.findOne({ where: { id } });
}
}
For example, my query is:
{
project(id: "1"){
id
name
customer{
id
name
}
}
}
The query returns:
{
"data": {
"project": {
"id": "1",
"name": "KFS 5.1",
"customer": null
}
}
}
Thanks for help

Resources