I have a very basic graphql mutation in the frontend that I send to my backend. I am using this code on the by graphql-request as a guide.
With primitives it works:
const mutation = gql`
mutation EditArticle($id: ID!, $title: String) {
editArticle(id: $id, title: $title) {
id
}
}
`
Now I'd like to also be able to mutate some meta data about the article, stored in a meta object inside the article:
...,
title: "Hello World",
meta: {
author: "John",
age: 32,
...
}
So my question is: How do I pass over non-primitive object types as arguments to mutations when making the request from the frontend, using graphql-request?
I tried something like this already:
const Meta = new GraphQLObjectType({
name: "Meta",
fields: () => ({
id: { type: GraphQLID },
name: { type: GraphQLString },
age ....
}),
})
const mutation = gql`
mutation EditArticle($id: ID!, $title: String, $meta: Meta) { //??? I don't seem to get what goes here?
editArticle(id: $id, title: $title, meta: $meta) {
id
}
}
`
I also tried it with GraphQLObjectType, but I think I am going wrong here (since this is the frontend).
PS: I looked at this answer, but I didn't understand / believe the solution there might be incomplete.
You need to define the input object type in your serverside schema, as something like
input MetaInput {
name: String
author: String
release: Date
}
and use it in the editArticle definition
extend type Mutation {
editArticle(id: ID!, title: String, meta: MetaInput): Article
}
Then you can also refer to the MetaInput type in the clientside definition of your mutation EditArticle.
Related
Just learning GraphQL using both node and c#. I am trying to port the C# example over to node since it will be a good learning exercise (since I don't know node or graphql that well)
I have 2 types. Account and Owner (i.e. account owner)
Everything works fine with the following (i.e. Fields for Owned Accounts (list) and First Account (single object)
module.exports = new GraphQLObjectType({
name: 'OwnerType',
fields: {
Id: { type: GraphQLID},
Name: {type: GraphQLString},
Address: {type: GraphQLString},
OwnedAccounts: {
type: new GraphQLList(AccountType),
name: "OwnedAccounts",
resolve(obj, args, { mssqlConfig }){
return mssql_account(mssqlConfig).getByOwnerId(obj.Id);
}
},
FirstAccount: {
type: AccountType,
name: "FirstAccount",
resolve(obj, args, {mssqlConfig}){
return mssql_account(mssqlConfig).getFirstByOwnerId(obj.Id);
}
}
}
});
The issue arises when I try to add a field for AccountOwner to the AccountType. I get the error "One of the provided types for building the Schema is missing a name."
I have tried putting a name on everything I could see which didn't help at all.
The offending AccountType definition is:
module.exports = new GraphQLObjectType({
name: 'AccountType',
fields: {
Id: { type: GraphQLID },
Description: { type: GraphQLString },
OwnerId: { type: GraphQLID },
Type: { type: AccountTypeEnum },
AccountOwner: {
type: OwnerType,
resolve(obj, args, { mssqlConfig }){
return mssql_owner(mssqlConfig).get(obj.OwnerId);
}
}
}
});
If you need further info or any other code please let me know.
EDIT: If I change the declaration of the two types (Account and Owner) and put them in the same .js file then it works (see below). I have also changed the fields to return an arrow function which I believe will delay some kind of binding till after everything is loaded.
So now my question is how should I separate the types into different files. (JS isn't my strong point)
EDIT... altered Types...
const {
GraphQLObjectType,
GraphQLID,
GraphQLString,
GraphQLList
} = require('graphql');
const AccountTypeEnum = require('./accountTypeEnum');
const mssql_owner = require('../../database/mssql/owner');
const mssql_account = require('../../database/mssql/account');
const ownerType = new GraphQLObjectType({
name: 'OwnerType',
fields: () => ({
Id: { type: GraphQLID, name: "Id"},
Name: {type: GraphQLString, Name: "Name"},
Address: {type: GraphQLString},
OwnedAccounts: {
type: new GraphQLList(accountType),
name: "OwnedAccounts",
resolve(obj, args, { mssqlConfig }){
return mssql_account(mssqlConfig).getByOwnerId(obj.Id);
}
},
FirstAccount: {
type: accountType,
name: "FirstAccount",
resolve(obj, args, {mssqlConfig}){
return mssql_account(mssqlConfig).getFirstByOwnerId(obj.Id);
}
}
})
});
const accountType = new GraphQLObjectType({
name: 'AccountType',
fields: () => ({
Id: { type: GraphQLID, name: "Id" },
Description: { type: GraphQLString, name: "Description" },
OwnerId: { type: GraphQLID, name: "OwnerId" },
Type: { type: AccountTypeEnum, name: "Type" },
AccountOwnerFoo: {
name: "Wombat",
type: ownerType,
resolve(parent, args, {mssqlConfig}){
return mssql_owner(mssqlConfig).get(parent.OwnerId);
}
}
})
});
module.exports = {
ownerType,
accountType
}
Node.js allows circular dependencies. Not all module systems allow this but it works in Node. The problem is, that one of the modules needs to be undefined in whatever module gets parsed first. In this case, Node.js assigns an empty object as the export of this module.
So basically you are assigning an empty object as a type sometimes - thus complaining that the type is missing the name property. The good thing though is that the reference to this object will not change if you assign properties to it. What you have to do I have answered already here and here.
why this error occurred? Because you are giving the type which is not defined at the compile time. So, when the compiler tries to compile the file, it searches the type you have specified here it is AccountType. which is not compiled. That's why the OwnerType didn't get the AccountType and you are getting an error.
Simple Solution:
You need to import the AccountType inside the OwnerType file.
const AccountType = require('./AccountType.js');
before the OwenerType code. This might be helpful.
I'm using normalizr util to process API response based on non-ids model. As I know, typically normalizr works with ids model, but maybe there is a some way to generate ids "on the go"?
My API response example:
```
// input data:
const inputData = {
doctors: [
{
name: Jon,
post: chief
},
{
name: Marta,
post: nurse
},
//....
}
// expected output data:
const outputData = {
entities: {
nameCards : {
uniqueID_0: { id: uniqueID_0, name: Jon, post: uniqueID_3 },
uniqueID_1: { id: uniqueID_1, name: Marta, post: uniqueID_4 }
},
positions: {
uniqueID_3: { id: uniqueID_3, post: chief },
uniqueID_4: { id: uniqueID_4, post: nurse }
}
},
result: uniqueID_0
}
```
P.S.
I heard from someone about generating IDs "by the hood" in normalizr for such cases as my, but I did found such solution.
As mentioned in this issue:
Normalizr is never going to be able to generate unique IDs for you. We
don't do any memoization or anything internally, as that would be
unnecessary for most people.
Your working solution is okay, but will fail if you receive one of
these entities again later from another API endpoint.
My recommendation would be to find something that's constant and
unique on your entities and use that as something to generate unique
IDs from.
And then, as mentioned in the docs, you need to set idAttribute to replace 'id' with another key:
const data = { id_str: '123', url: 'https://twitter.com', user: { id_str: '456', name: 'Jimmy' } };
const user = new schema.Entity('users', {}, { idAttribute: 'id_str' });
const tweet = new schema.Entity('tweets', { user: user }, {
idAttribute: 'id_str',
// Apply everything from entityB over entityA, except for "favorites"
mergeStrategy: (entityA, entityB) => ({
...entityA,
...entityB,
favorites: entityA.favorites
}),
// Remove the URL field from the entity
processStrategy: (entity) => omit(entity, 'url')
});
const normalizedData = normalize(data, tweet);
EDIT
You can always provide unique id's using external lib or by hand:
inputData.doctors = inputData.doctors.map((doc, idx) => ({
...doc,
id: `doctor_${idx}`
}))
Have a processStrategy which is basically a function and in that function assign your id's there, ie. value.id = uuid(). Visit the link below to see an example https://github.com/paularmstrong/normalizr/issues/256
I am making a blog service using express and apollo-express along with mongodb (mongoose).
I made some mutation queries, but I have no success with obtaining the args of a mutation query.
Now I am asking for how I should structure my mutation query in order to make the thing work. thanks.
error:
"message": "Blog validation failed: title: Path title is required., slug: Path slug is required."
the query:
mutation ($input: BlogInput) {
newBlog(input: $input) {
title
slug
}
}
the query variables:
{
"input": {
"title": "ABC",
"slug": "abc"
}
}
part of my graphql schema:
type Blog {
id: ID!
title: String!
slug: String!
description: String
users: [User]!
posts: [Post]!
}
input BlogInput {
title: String!
slug: String!
description: String
}
extend type Mutation {
newBlog(input: BlogInput): Blog
}
part of my resolvers:
import Blog from './blog.model'
export const blogs = async () => {
const data = await Blog.find().exec()
return data
}
export const newBlog = async (_, args) => {
const data = await Blog.create({ title: args.title, slug: args.slug })
return data
}
part of my database schema (mongoose):
import mongoose from 'mongoose'
const Schema = mongoose.Schema
const blogSchema = Schema({
title: {
type: String,
required: true
},
slug: {
type: String,
required: true,
unique: true
},
description: {
type: String
},
users: {
type: [Schema.Types.ObjectId],
ref: 'User'
},
posts: {
type: [Schema.Types.ObjectId],
ref: 'Post'
}
})
export default mongoose.model('Blog', blogSchema)
You've defined your newBlog mutation to accept a single argument named input. From what I can tell, you're correctly passing that argument to the mutation using a variable. Your resolver receives a map of the arguments passed to the field being resolved. That means you can access individual properties of the input object like this:
export const newBlog = async (_, args) => {
const data = await Blog.create({ title: args.input.title, slug: args.input.slug })
return data
}
Note, you may want to make input non-nullable (i.e. set the type to BlogInput!), otherwise your resolver will need to handle the possibility of args.input returning undefined.
The goal of my app is for a user to be able to save and recall a list of forms to be filled out and edited. One user will have many forms, and a single form will be made up of several fields of its own.
If my have my types set up like this, for example:
const FormType = new GraphQLObjectType({
name: 'FormType',
fields: {
id: { type: GraphQLID },
name: { type: GraphQLString },
email: { type: GraphQLString }
}
});
const UserType = new GraphQLObjectType({
name: 'UserType',
fields: {
id: { type: GraphQLID },
email: { type: GraphQLString },
firstName: { type: GraphQLString, default: '' },
lastName: { type: GraphQLString, default: '' },
phone: { type: GraphQLString, default: '' },
forms: { type: new GraphQLList(FormType) }
}
});
And my mutation like this:
const mutation = new GraphQLObjectType({
name: 'Mutation',
fields: {
saveForm: {
type: UserType,
args: {
userID: { type: GraphQLID },
form: { type: FormType } // ???
},
resolve(parentValue, { userID, form }) {
// I'll figure out what to do here when the time comes
}
}
}
});
I can't seem to create a mutation that works with GraphQLList. I keep getting this error:
Error: Mutation.saveForm(form:) argument type must be Input Type but
got: FormType.
...which my research is telling me means that FormType needs to be a GraphQLInputObjectType. When I change it accordingly, I just get the error:
Error: Mutation.saveForm(form:) argument type must be Output Type but
got: FormType.
So nothing really changes. Does anyone know of a working example of this problem?
The problem you are seeing is because while you've correctly changed your input type to be a GraphQLInputObjectType, that changes the type of field UserType.forms to an input type too - remember UserType is an output type. Here you would need to make a FormType and also a FormInputType, and use each in the appropriate place.
Separately: why do you need a full input type for a form? Wouldn't it function equally well to just pass in the ID? Or, if the intention is to have FormType represent form data, why does it need an ID? Also, perhaps it could be named better to FormData?
Finally, while I believe this provides an answer, your question didn't provide completely cut+pasteable code for a minimal, complete and verifiable example so that I could easily check.
I'm querying for 2 objects which are both needed in the same component. The problem is that one of the queries have to wait on the other and use its id field as an argument for the other. Not sure how to implement this.
const PlayerQuery = gql`query PlayerQuery($trackId: Int!, $duration: Int!, $language: String!) {
subtitle(trackId: $trackId, duration: $duration) {
id,
lines {
text
time
}
}
translation(trackId: $trackId, language: $language, subtitleId: ???) {
lines {
translation
original
}
}
}`;
So in the query above translation needs subtitleId as an argument which is returned by the subtitle query.
I'm using Apollo both on the client and on the server.
That's a great question because it illustrates a significant difference between REST/RPC style APIs and GraphQL. In REST style APIs the objects that you return only contain metadata about how to fetch more data, and the API consumer is expected to know how to run the JOINs over those tables. In your example, you have a subtitle and a translation that you need to JOIN using the ID property. In GraphQL, objects rarely exists in isolation and the relationships encoded into the schema itself.
You didn't post your schema but from the looks of it, you created a translation object and a subtitle object and exposed them both in your root query. My guess is that it looks something like this:
const Translation = new GraphQLObjectType({
name: "Translation",
fields: {
id: { type: GraphQLInt },
lines: { type: Lines }
}
});
const SubTitle = new GraphQLObjectType({
name: "SubTitle",
fields: {
lines: { type: Lines }
}
});
const RootQuery = new GraphQLObjectType({
name: "RootQuery",
fields: {
subtitle: { type: SubTitle },
translation: { type: Translation }
}
});
module.exports = new GraphQLSchema({
query: RootQuery
});
What you should do instead, is to make a relationship to translations INSIDE OF subtitle like this. The goal of GraphQL is to first create a graph or relationships in your data, then to figure out how to expose entry points to that data. GraphQL lets you select arbitrary sub-trees in a graph.
const Translation = new GraphQLObjectType({
name: "Translation",
fields: {
id: { type: GraphQLInt },
lines: { type: Lines }
}
});
const SubTitle = new GraphQLObjectType({
name: "SubTitle",
fields: {
lines: { type: Lines }
translations: {
type: Translation,
resolve: () => {
// Inside this resolver you should have access to the id you need
return { /*...*/ }
}
}
}
});
const RootQuery = new GraphQLObjectType({
name: "RootQuery",
fields: {
subtitle: { type: SubTitle }
}
});
module.exports = new GraphQLSchema({
query: RootQuery
});
Note: For clarity, I left out the arguments fields and any additional resolvers. I'm sure your code will be a bit more sophisticated, I just wanted to illustrate the point :).