Using The GraphQL Args Property In A Mutation - javascript

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.

Related

Express - Mongoose - How to add user data to POST request when submitting a form?

I'm new to Mongoose and NodeJS and I'm building a ticket management system where logged in users can fill up a form to create a ticket. It has two fields (title and description) but when submitting it, I'd like to also add some user's data to the form data object.
On the front end I'm using React with Formik to handle the form.
My user data object is stored in local storage using JWT.
Here are my current models for the ticket and for the user:
//ticket.model.js
module.exports = (mongoose) => {
const Ticket = mongoose.model(
'ticket',
mongoose.Schema(
{
title: String,
description: String,
},
{ timestamps: true }
)
);
return Ticket;
};
//user.model.js
const User = mongoose.model(
'User',
new mongoose.Schema({
firstName: String,
lastName: String,
email: String,
password: String,
roles: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Role',
},
],
})
);
module.exports = User;
Here is the Formik function:
const formik = useFormik({
initialValues: {
title: '',
description: '',
},
validationSchema,
validateOnBlur: false,
validateOnChange: false,
onSubmit: (data) => {
TicketService.create(data).then(() => {
navigate('/home');
window.location.reload();
});
},
});
Ideally, when the ticket is being created I'd like to query Mongoose with user's ObjectId to retrieve his firstName and lastName. If it's too complicated I don't mind just adding the user's names to the form data using JSON.parse(localStorage.getItem('user')). Or if you have better practices, please let me know.
Thank you!
Never mind, my formik object was actually missing the user element (see below).
const formik = useFormik({
initialValues: {
title: '',
description: '',
authorId: JSON.parse(localStorage.getItem('user')).id,
authorName: `${JSON.parse(localStorage.getItem('user')).firstName} ${
JSON.parse(localStorage.getItem('user')).lastName
}`,
},
validationSchema,
validateOnBlur: false,
validateOnChange: false,
onSubmit: (data) => {
console.log(data);
TicketService.create(data).then(() => {
navigate('/home');
window.location.reload();
});
},
});
From there I just updated my model and controller accordingly:
module.exports = (mongoose) => {
const Ticket = mongoose.model(
'ticket',
mongoose.Schema(
{
title: String,
description: String,
authorId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
authorName: String,
},
{ timestamps: true }
)
);
return Ticket;
};

Passing Objects as Argument to GraphQL Mutation (graphql-request)

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.

How to fix 'Variable "$_v0_data" got invalid value' caused from data types relation - Mutation Resolver

I am trying to setup relations between types and wrote a resolver to run a mutation that create the list values but getting the below error
here is my mutation file
async createList(parent, args, ctx, info) {
const list = await ctx.db.mutation.createList(
{
data: {
project: {
connect: {
id: args.projectId
}
},
...args
}
},
info
);
return list;
}
and here is my datamodel
type Board {
id: ID! #id
title: String!
createdAt: DateTime! #createdAt
updatedAt: DateTime! #updatedAt
lists: [List]!
}
type List {
id: ID! #id
title: String!
createdAt: DateTime! #createdAt
updatedAt: DateTime! #updatedAt
project: Board!
}
and my schema is
type Mutation {
createList(title: String!, projectId: ID!): List!
}
and the generated prisma file
type Mutation {
createList(data: ListCreateInput!): List!
}
input ListCreateInput {
id: ID
title: String!
project: BoardCreateOneWithoutListsInput!
}
I expected this mutation to run and create the values but got this error instead
Error: Variable "$_v0_data" got invalid value { project: { connect: [Object] }, title: "to do", projectId: "cjyey7947hh6x0b36m231qhbc" }; Field "projectId" is not defined by type ListCreateInput. Did you mean project?
at new CombinedError (/Users/gabroun/Documents/Sites/react-kanban/server/node_modules/graphql-tools/dist/stitching/errors.js:82:28)
at Object.checkResultAndHandleErrors (/Users/gabroun/Documents/Sites/react-kanban/server/node_modules/graphql-tools/dist/stitching/errors.js:98:15)
at CheckResultAndHandleErrors.transformResult (/Users/gabroun/Documents/Sites/react-kanban/server/node_modules/graphql-tools/dist/transforms/CheckResultAndHandleErrors.js:9:25)
at /Users/gabroun/Documents/Sites/react-kanban/server/node_modules/graphql-tools/dist/transforms/transforms.js:18:54
at Array.reduce (<anonymous>)
at applyResultTransforms (/Users/gabroun/Documents/Sites/react-kanban/server/node_modules/graphql-tools/dist/transforms/transforms.js:17:23)
at /Users/gabroun/Documents/Sites/react-kanban/server/node_modules/graphql-tools/dist/stitching/delegateToSchema.js:97:50
at step (/Users/gabroun/Documents/Sites/react-kanban/server/node_modules/graphql-tools/dist/stitching/delegateToSchema.js:31:23)
at Object.next (/Users/gabroun/Documents/Sites/react-kanban/server/node_modules/graphql-tools/dist/stitching/delegateToSchema.js:12:53)
at fulfilled (/Users/gabroun/Documents/Sites/react-kanban/server/node_modules/graphql-tools/dist/stitching/delegateToSchema.js:3:58)
Consider using the following code
async function createList(parent, { title, projectId }, ctx, info) {
const list = await ctx.db.mutation.createList(
{
data: {
project: {
connect: {
id: projectId,
},
},
title,
},
},
info,
)
return list
}
The reason for getting the error is because ...args is used, so all the attributes in args will be passed to data as follows
data:{
project:{...},
title:'',
projectId:'',
}
ListCreateInput only needs title and project. The extra projectId becomes accidentally causing an error.

Cannot delete a post with Graphql

I am having an issue with getting my resolver function to work properly.
Here is my resolver function:
const resolvers = {
Query: {
info: () => `This is the API of a Hackernews Clone`,
// 2
feed: () => links,
},
// 3
Mutation: {
// 2
post: (parent, args) => {
const link = {
id: `link-${idCount++}`,
description: args.description,
url: args.url,
}
links.push(link)
return link
},
deleteLink: (parent, args) => {
const id = args.id
//delete links[id1]
return id
}
}
}
Here is my schema:
type Query {
info: String!
feed: [Link!]!
}
type Mutation {
post(url: String!, description: String!): Link!
deleteLink(id: ID!): Link
}
type Link {
id: ID!
description: String!
url: String!
}
When I use this block to run my deleteLink resolver:
mutation {
deleteLink(
id: "link-1"
){
id
}
}
I get an error like this one:
{
"data": {
"deleteLink": null
},
"errors": [
{
"message": "Cannot return null for non-nullable field Link.id.",
"locations": [
{
"line": 3,
"column": 5
}
],
"path": [
"deleteLink",
"id"
]
}
]
}
Please let me know what I am doing wrong. I am not sure why i get the error: cannot return null for non-nullable field Link.id. Is this a result of the wrong way to query the mutation or is this a result of a bad resolver function?
According to your schema, your deleteLink mutation returns a Link object type and a Link returns id, description, url as required fields.
In your resolver, you are only returning a id and null for all the rest.
The best approach in my opinion would be to change your mutation return type into a String or ID type. When you delete a record, you can't (should not) return the same record, but should return a status/id message.
Something like:
type Mutation {
post(url: String!, description: String!): Link!
deleteLink(id: ID!): String! // Or ID! if you want to return the input id
}

GraphQL Node.js: determine what types are used within a query

Given I have a schema that has types: User, Comment, Post, Image; Is it possible to determine the GraphQL types being used in a query, given the query and schema?
e.g. if a client had a query
{
user(userName: "username") {
email
comments
}
}
In this case, the query has types User and Comment. Is it possible to determine the programmatically using either the graphql-js or graphql packages for node.js?
For anyone else who runs into this, I found the answer in visit and TypeInfo
Here's a function that takes the GraphQL query (document) and a schema and returns which data types from the schema are being used.
import { visit } from 'graphql/language/visitor'
import { parse } from 'graphql/language'
import { TypeInfo, visitWithTypeInfo } from 'graphql'
import { schema as _schema } from '../src/schema/schema'
const getQueryTypes = (schema, query) => {
const typeInfo = new TypeInfo(schema)
var typesMap = {}
var visitor = {
enter(node) {
typeInfo.enter(node)
typesMap[typeInfo.getType()] = true
},
leave(node) {
typesMap[typeInfo.getType()] = true
typeInfo.leave(node)
}
}
visit(parse(query), visitWithTypeInfo(typeInfo, visitor))
return Object.keys(typesMap)
}
const _query = `
query {
...
}
`
console.log(getQueryTypes(_schema, _query))
Given a valid document string representing some GraphQL operation, you can parse the string into an AST.
import { parse, validate } from 'graphql'
const document = parse(someDocumentString)
// if you want to validate your document to verify it matches your schema
const errors = validate(schema, document)
AFAIK, there's no utility function for getting an array of the types in a document, if that's what you're asking for, but you can just traverse the AST and gather whatever information from it. As an example, here's how GraphiQL does just that to generate a map of variables to their corresponding types:
import { typeFromAST } from 'graphql'
export function collectVariables(schema, documentAST) {
const variableToType = Object.create(null);
documentAST.definitions.forEach(definition => {
if (definition.kind === 'OperationDefinition') {
const variableDefinitions = definition.variableDefinitions;
if (variableDefinitions) {
variableDefinitions.forEach(({ variable, type }) => {
const inputType = typeFromAST(schema, type);
if (inputType) {
variableToType[variable.name.value] = inputType;
}
});
}
}
});
return variableToType;
}
You have to create types using GraphQLObjectType, for example:
export default new GraphQLObjectType(
({
name: 'User',
description: 'Represents a User',
fields: () => ({
_id: {
type: GraphQLNonNull(GraphQLString),
resolve: user => user._id,
},
name: {
type: GraphQLNonNull(GraphQLString),
description: 'Name of the user',
resolve: user => user.name,
},
createdAt: {
type: GraphQLString,
description: '',
resolve: user => user.createdAt.toISOString(),
},
updatedAt: {
type: GraphQLString,
description: '',
resolve: user => user.updatedAt.toISOString(),
},
}),
}: GraphQLObjectTypeConfig<User, GraphQLContext>),
);
Then you can use this UserType on another type, by declaring type: GraphQLNonNull(UserType) for example.
https://graphql.org/graphql-js/constructing-types/
Hope it helps :)

Categories

Resources