I have a React client-side project and a Node.js/GraphQL api in two separate repo's.
In my React app, I want to pass an object as variable type into my mutation. Here's how my mutation looks like:
export const CREATE_SPEAKER = gql`
input Expertise {
title: String!
domain: String!
}
mutation CreateSpeaker(
$name: String!
$age: String!
$nationality: String!
$avatar: String!
$expertise: Expertise!
) {
createSpeaker(
speakerInput: {
name: $name
age: $age
nationality: $nationality
avatar: $avatar
expertise: $expertise
}
) {
name
age
nationality
avatar
expertise {
title
domain
}
}
}
`;
In my Node.js project I have the following schema:
input SpeakerInput {
name: String!
age: String!
expertise: ExpertiseInput!
nationality: String!
avatar: String
}
input ExpertiseInput {
title: String!
domain: String!
}
And my resolver:
createSpeaker: async args => {
const { name, age, nationality, avatar, expertise } = args.speakerInput;
const newSpeaker = new Speaker({
name,
age,
nationality,
avatar,
expertise: {
title: expertise.title,
domain: expertise.domain
}
});
try {
return await newSpeaker.save();
} catch (error) {
throw ("Failed to create speaker:: ", error);
}
}
But I'm getting the following error when trying to create the speaker:
Uncaught (in promise) Invariant Violation: Schema type definitions not
allowed in queries. Found: "InputObjectTypeDefinition"
Any suggestions/ideas how to do this?
You can't define additional types when sending requests to a GraphQL service and you don't need to -- just use the types you've already defined on the server (in this case ExpertiseInput:
$expertise: ExpertiseInput!
However, there's no need to use this many variables in the first place:
mutation CreateSpeaker($input: SpeakerInput!) {
createSpeaker(speakerInput: $input) {
name
age
nationality
avatar
expertise {
title
domain
}
}
}
Related
I'm learning Relay to use in a React-Relay project. After my research and learning on the internet, I've run into problems with my graphql schema and resolvers. I can't seem to figure out what resolvers do I need and how go about it correctly. If someone can point me in the right direction, that would be great. I have attached my code below. All I'm trying to do is fetch elements of a list using the relay graphql server specification.
Graphql Schema
interface Node {
id: ID!
}
type Link implements Node {
id: ID!
title: String!
description: String!
}
type LinkConnection {
edges: [LinkEdge]
pageInfo: PageInfo!
}
type LinkEdge {
cursor: String!
node: Link
}
type Query {
links(after: String, before: String, first: Int, last: Int): LinkConnection
node(id: ID!): Node
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
Resolvers
const resolvers = {
Query,
Node: {
__resolveType(node, context, info){
if(node.title){
return 'Link'
}
return null
}
},
}
Query Resolver
const node = async (parent, args, {prisma}) => {
try{
console.log('hit')
const data = await prisma.link.findUnique({
where: {
id: Number(args.id)
}
})
console.log(data)
return data
} catch(err){
return err
}
}
export default {
node,
}
P.S. Im using Apollo Server and Prisma under the hood
Edit:
I solved this issue by realizing that the resolves are invoked in the same order the schema is nested. So by writing separate resolvers for each type and passing the information in parent argument, things worked.
I have a backend made with Express / Apollo Server / GraphQL / MongoDB / Mongoose and a frontend made with React.
I have my component where I want to do mutations and queries, I checked the connection between frontend and backend and it's good.
My mutation on backend:
createUser(input: SignupInput!): AuthUser!
input SignupInput {
name: String!
username: String!
email: String!
password: String!
}
The AuthUser return is an User! that has more fields but I think it's not necessary info.
On my frontend
const NEW_USER = gql`
mutation CreateUser($type: SignupInput!) {
createUser(input: $type) {
user {
id
}
}
}
`;
const [newTest, { data }] = useMutation(NEW_USER);
const onSubmit = (formData) => {
newTest({
variables: {
name: formData.name,
username: formData.username,
email: formData.email,
password: formData.password,
},
});
};
The network response for error 400:
"Variable \"$type\" of required type \"SignupInput!\" was not provided."
I don't really understand why this is happening, I've seen in other people's code that creating the variable with the name of the input type you have on the backend works.
If I try to destructure the SignInput! type and write {$name: !String, ...) I get this error:
Unknown argument \"name\" on field \"Mutation.createUser\".", locations: […], extensions: {…} }
newTest({
variables: {
input: {
name: formData.name,
username: formData.username,
email: formData.email,
password: formData.password,
}
},
});
Input was not passed in the mutation function.
I'm building an event managing database that handles RSVPs and links those RSVPs to a specific event. I'm using React, Apollo, GraphQL and Prisma as my stack and am having trouble correctly writing a mutation that would link an RSVP to an existing event. Still relatively new to the syntax, but I can't grab the event.id when I'm using the createRsvp mutation.
I'm able to pass the event.id down on the front-end through props, and I think a less-elegant way of receiving event.id in the args of the RSVP would be creating a hidden form with the event.id, but I KNOW there's got to be a way through graphQL. I've tried looking through the docs and various examples on grabbing the id from a different object. Any help is much appreciated :)
On the backend, here's my datamodel:
type Rsvp {
id: ID! #id
event: Event! #relation(link: INLINE)
firstName: String!
lastName: String!
email: String! #unique
company: String
jobTitle: String
mobile: String
dietary: String
}
type Event {
id: ID! #id
isPublished: Boolean
title: String!
startDate: String!
endDate: String!
description: String!
image: String
address: String
url: String!
suburb: String
postcode: String
state: String
country: String
guestLimit: Int
rsvps: [Rsvp]
}
The actual resolver for the Mutation.. I think I'm incorrectly retrieving the event.id here. My though process is that data: {...args} is first taking in the data from the RSVP form, and the connection to event is linking it to a certain event.
async createRsvp(parent, args, ctx, info) {
// 1. Query the event that is being registered for
const eventId = ctx.request.event.id;
// 2. Create the RSVP for this specific event
const rsvp = await ctx.db.mutation.createRsvp(
{
data: {
...args,
event: {
connect: { id: eventId }
}
}
},
info
);
console.log(rsvp);
return rsvp;
}
};
On the front end, this is what my Mutation looks like
const RSVP_MUTATION = gql`
mutation RSVP_MUTATION(
$email: String!
$firstName: String!
$lastName: String!
$company: String
$jobTitle: String
$mobile: String
$dietary: String
) {
createRsvp(
email: $email
firstName: $firstName
lastName: $lastName
company: $company
jobTitle: $jobTitle
mobile: $mobile
dietary: $dietary
) {
id
}
}
`;
and finally, the Mutation function in the form:
<Mutation mutation={RSVP_MUTATION} variables={({ id }, this.state)}>
{(createRsvp, { loading, error }) => (
<Form
onSubmit={async e => {
e.preventDefault();
const res = await createRsvp();
}}>
The error I receive in the console is "Uncaught (in promise) Error: GraphQL error: Cannot read property 'id' of undefined" which leads me to believe I'm incorrectly trying to access the event.id. Any tips or advice? Thanks again!
Divide and conquer
You can use /graphiql playground to test queries and mutations (API generally).
Testing this mutation:
mutation RSVP_MUTATION(
$email: String!
$firstName: String!
$lastName: String!
$company: String
$jobTitle: String
$mobile: String
$dietary: String
) {
createRsvp(
email: $email
firstName: $firstName
lastName: $lastName
company: $company
jobTitle: $jobTitle
mobile: $mobile
dietary: $dietary
) {
id
}
}
... with required variables you should quickly notice that you need to pass event id as variable, too. No special (more/less elegant), separate method required, this is a standard, query/mutation and variables, nothing more!!!
Just add eventId variable:
mutation RSVP_MUTATION(
$eventId: ID!
$email: String!
$firstName: String!
$lastName: String!
$company: String
$jobTitle: String
$mobile: String
$dietary: String
) {
createRsvp(
eventId: $eventId
email: $email
firstName: $firstName
lastName: $lastName
company: $company
jobTitle: $jobTitle
mobile: $mobile
dietary: $dietary
) {
id
}
}
Of course in resolver you'll get it within args, you can use f.e.
const { eventId, ...rest } = args;
const rsvp = await ctx.db.mutation.createRsvp(
{
data: {
...rest,
event: {
React variables can be passed f.e. by
variables={{ eventId: id, ...this.state }}
I Want to syntax highlight the code inside typeDef. It is possible?
There is a extension for this? Or I have to code the typeDef other way?
export const typeDef = `
type User {
_id: ID!
email: String!
password: String
createdEvents: [Event!]
}
type AuthData {
userId: ID!
token: String!
tokenExpiration: Int!
}
input UserInput {
email: String!
password: String!
}
`;
Use String.raw to trick VSCode into Syntax Highlighting GraphQL. It works for other languages as well.
export const gql = String.raw
export const typeDef = gql`
type User {
_id: ID!
email: String!
password: String
createdEvents: [Event!]
}
type AuthData {
userId: ID!
token: String!
tokenExpiration: Int!
}
input UserInput {
email: String!
password: String!
}
`
Assuming you're using the right extension, you need to use the gql tag from graphql-tag.
const gql = require('graphql-tag')
const typeDefs = gql`
type User { ... }
`
The tag parses the provided string and returns a DocumentNode object, which is what should be passed to makeExecutableSchema or the ApolloServer constructor. On the client side, the queries used by ApolloClient are also expected to be DocumentNode objects and should be wrapped the same way.
The extension is able to detect usage of the tag and apply syntax highlighting accordingly.
I have a problem regarding GraphQL Schema stitching.
I have two Graphql Schemas:
type Name {
firstname: String!
lastname: String!
}
type Address {
street: String!
number: Int!
}
type User {
name: Name!
address: Address!
}
type Query {
user(userId: String!): User
}
and
type User {
age: String!
}
type Query {
user(userId: String!): User
}
I now tried to merge the schemas using graphql-tools's mergeSchemas Function:
const schema = mergeSchemas({
schemas: [schema1, schema2]
});
But instead of what I'm trying to achieve (an extended User Type):
type Name {
firstname: String!
lastname: String!
}
type Address {
street: String!
number: Int!
}
type User {
name: Name!
address: Address!
age: String!
}
type Query {
user(userId: String!): User
}
it resulted in this:
type Name {
firstname: String!
lastname: String!
}
type Address {
street: String!
number: Int!
}
type User {
name: Name!
address: Address!
}
type Query {
user(userId: String!): User
}
Only one of the UserTypes is displayed in the final schema.
I tried using the onTypeConflict API in mergeSchemas to extend the Type but I haven't made any results.
Is there a way to merge Schemas by extending Types on Conflict?
Here is a possible solution to merge the object types. Maybe it makes sense to filter by type name in onTypeConflict instead of merging every type.
import cloneDeep from 'lodash.clonedeep'
import { GraphQLObjectType } from 'graphql/type/definition'
import { mergeSchemas } from 'graphql-tools'
function mergeObjectTypes (leftType, rightType) {
if (!rightType) {
return leftType
}
if (leftType.constructor.name !== rightType.constructor.name) {
throw new TypeError(`Cannot merge with different base type. this: ${leftType.constructor.name}, other: ${rightType.constructor.name}.`)
}
const mergedType = cloneDeep(leftType)
mergedType.getFields() // Populate _fields
for (const [key, value] of Object.entries(rightType.getFields())) {
mergedType._fields[key] = value
}
if (leftType instanceof GraphQLObjectType) {
mergedType._interfaces = Array.from(new Set(leftType.getInterfaces().concat(rightType.getInterfaces())))
}
return mergedType
}
const schema = mergeSchemas({
schemas: [schema1, schema2],
onTypeConflict: (leftType, rightType) => {
if (leftType instanceof GraphQLObjectType) {
return mergeObjectTypes(leftType, rightType)
}
return leftType
}
})
Credits: The mergeObjectTypes function was written by Jared Wolinsky.
This should help
extend type User {
age: String!
}