GraphQL How to properly implement chain resolvers? - graphql

One of the main concepts of GraphQL is being able to select the data we're interested in, while omitting the unnecessary fields. To achieve that we can use resolvers. Each such is responsible for providing data for a particular type.
I've created a small example to present my problem.
The error it returns is:
"Cannot return null for non-nullable field Parent.child."
I could just let the Parent create the whole object, but then where's the benefit of resolving delegation, what would be the point of creating the Child resolver and then the whole GraphQL concept of resolver chains?
How to make the parent call the Child resolver to resolve its child field?
const { ApolloServer, gql } = require('apollo-server');
const typeDefs = gql`
type Child {
name: String!
}
type Parent {
name: String!
child: Child!
}
type Query {
getParent: Parent!
}
`;
(async function () {
const server = new ApolloServer({
typeDefs,
resolvers: {
Query: {
getParent: () => {
return {
name: "parent",
};
},
},
Child: {
name: () => "child",
},
}
});
await server.listen();
})();

Here is how you should write your resolver:
import { gql,ApolloServer } from "apollo-server";
const typeDefs = gql`
type Child {
name: String!
}
type Parent {
name: String!
child: Child!
}
type Query {
getParent: Parent!
}
`;
(async function () {
const server = new ApolloServer({
typeDefs,
resolvers: {
Query: {
getParent: () => {
return {
name: "parent",
};
},
},
Parent: {
child: () => {
return {
name: "child",
}
}
},
}
});
await server.listen().then(({url}) => {
console.log(url)
});
})();
I'm also a beginner in gql, but I will give my thoughts.
When you define a new ObjectType:Parent, you should write the ObjectField Resolver for it if you have nested ObjectType, which means you should write the resolver specific the Object and its child's resolver function. that way , the nested object could found the way to fetch the data.
The Query and Mutation is just two special entry point.
refer to this: https://graphql.org/learn/schema/#the-query-and-mutation-types

Related

Are fields with list types forbidden in GraphQL schema stitching selection sets?

I have an array of entities that look like this:
const aEntities = [
{
id: 1,
name: 'Test',
oneToManyRelation: [
{
id: 2
},
{
id: 3
}
],
oneToOneRelation: {
id: 1
}
}
];
The entities are represented by the type AType. I want to make an extension of this type in a separate subschema and prove that it is possible to add fields that derive their values from the contents of oneToOneRelation and oneToManyRelation respectively.
The following schema, implementing a derived field based on oneToOneRelation, works fine:
const aSchema = makeExecutableSchema({
resolvers: {
Query: {
aEntities: () => aEntities
}
},
schemaTransforms: [stitchingDirectivesValidator],
typeDefs: gql`
${allStitchingDirectivesTypeDefs}
type AType {
id: ID!
name: String!
oneToOneRelation: AEmbeddedType!
}
type AEmbeddedType {
id: ID!
}
type Query {
aEntities: [AType!]!
}
`
});
const bSchema = makeExecutableSchema({
resolvers: {
AType: {
oneToOneId: ({ oneToOneRelation }) => oneToOneRelation.id
},
Query: {
aEntities_fromBSchema: (_, { keys }) => keys,
}
},
schemaTransforms: [stitchingDirectivesValidator],
typeDefs: gql`
${allStitchingDirectivesTypeDefs}
type AType #key(selectionSet: "{ oneToOneRelation { id } }") {
oneToOneId: String!
}
scalar Key
type Query {
aEntities_fromBSchema(keys: [Key!]!): [AType!]! #merge
}
`
})
const schema = stitchSchemas({
subschemaConfigTransforms: [stitchingDirectivesTransformer],
subschemas: [
{
schema: aSchema
},
{
schema: bSchema,
}
]
})
But once I add oneToManyRelation { id } to the selectionSet i run into problems:
const aSchema = makeExecutableSchema({
resolvers: {
Query: {
aEntities: () => aEntities
}
},
schemaTransforms: [stitchingDirectivesValidator],
typeDefs: gql`
${allStitchingDirectivesTypeDefs}
type AType {
id: ID!
name: String!
oneToManyRelation: [AEmbeddedType!]!
oneToOneRelation: AEmbeddedType!
}
type AEmbeddedType {
id: ID!
}
type Query {
aEntities: [AType!]!
}
`
});
const bSchema = makeExecutableSchema({
resolvers: {
AType: {
oneToManyIds: ({ oneToManyRelation }) => oneToManyRelation.map(({ id }) => id),
oneToOneId: ({ oneToOneRelation }) => oneToOneRelation.id
},
Query: {
aEntities_fromBSchema: (_, { keys }) => keys,
}
},
schemaTransforms: [stitchingDirectivesValidator],
typeDefs: gql`
${allStitchingDirectivesTypeDefs}
type AType #key(selectionSet: "{ oneToOneRelation { id }, oneToManyRelation { id } }") {
oneToOneId: String!
oneToManyIds: [String!]!
}
scalar Key
type Query {
aEntities_fromBSchema(keys: [Key!]!): [AType!]! #merge
}
`
})
I get the following error:
oneToManyRelation.map is not a function
And when I log the keys parameter in the aEntities_fromBSchema resolver it seems that oneToManyRelation haven't been resolved to be an array at all, but rather an (empty) object:
[
{
oneToOneRelation: [Object: null prototype] { id: '1' },
oneToManyRelation: [Object: null prototype] { id: undefined },
__typename: 'AType'
}
]
Is referencing list types in key selection sets simply forbidden as of graphql-tools v 7.0.2? It looks like I actually can circumvent the issue by using a subschema merge config defined outside of the SDL (without batching, instead using the args and selectionSet config parameters), but for validation/gateway reasons I'd prefer to have all my subschemas contain all of their type merging instructions as SDL directives.
Nb. This is a simplified representation of a real world problem.
Nb2. In the real world application one of my subschemas is a remote GraphQL application that I don't control, hence the need for some advanced tailoring in the stitching layer.
Edit: Simply adding the following to the merge options on the subschema config seems to solve the problem. Someone know of a good reason why this doesn't seem to be reproducible with SDL directives? (Or a good way to do so?)
// AType
{
argsFromKeys: (keys) => ({ keys }),
fieldName: 'aEntities_fromBSchema',
key: ({ oneToOneRelation, oneToManyRelation }) => ({ oneToManyRelation, oneToOneRelation }),
selectionSet: '{ oneToOneRelation { id }, oneToManyRelation { id } }'
}
You have likely found a bug! Please open an issue on the GitHub repo so we can track it. :)

GraphQL - does context propagate to downstream resolvers?

If you pass a modified context to a GraphQL resolver does this propagate to all downstream resolvers? Is this specified in the GraphQL specification or implementation specific?
To clarify with an example say I have a query like the following
{
companies {
employees {
positions {
title
}
}
}
}
let's say I start with contextA coming into the companies query and then I have my CompanyResolvers where I get a superSpecialContext and pass this on to the employees dataloader
export const CompanyResolvers = {
employees: async ({ id }: CompanyDTO, args: object, contextA: Context) => {
const superSpecialContext = await getSuperSpecialContext();
return context.dataLoaders.employees.load({ id: company.id, context: superSpecialContext });
}
};
when I get to the positions resolver am I now working with the superSpecialContext or the original contextA (I would actually prefer this to be the case)?
export const EmployeeResolvers = {
positions: async ({ id }: EmployeeDTO, args: object, context: Context) => {
// is my context here contextA or superSpecialContext?
}
};
If you pass a modified context to a GraphQL resolver does this propagate to all downstream resolvers.
Yes, each request gets its own context object for the duration of the request. It gets created in the context function on the GraphQL server.
import { ApolloServer, gql } from 'apollo-server'
import { ExpressContext } from 'apollo-server-express/dist/ApolloServer';
const typeDefs = gql`
type Book {
title: String
author: String
}
type Query {
books: [Book]
}
`;
const books = [
{
title: 'Harry Potter and the Chamber of Secrets',
author: 'J.K. Rowling',
},
{
title: 'Jurassic Park',
author: 'Michael Crichton',
},
];
const resolvers = {
Query: {
books: (obj: any, args: any, context: any) => {
console.log(context.name); // Khalil Stemmler
context.name = 'Billy Bob Thorton'
return books;
},
},
Book: {
title: (obj: any, args: any, context: any) => {
console.log(context.name); // Billy Bob Thorton.
// Should print "Billy Bob Thorton twice", once for each book.
return obj.title
},
}
};
const server = new ApolloServer({
typeDefs,
resolvers,
context: (expressContext: ExpressContext) => {
// The Apollo implementation of context allows you hook into the
// Express request to get access to headers, tokens, etc- in order
// to grab an authenticated user's session data and put it on context.
const { connection, res, req } = expressContext;
// A new context object is created for every request. This function
// should return an object.
return {
name: 'Khalil Stemmler'
}
}
});
// The `listen` method launches a web server.
server.listen().then(({ url }: { url: string }) => {
console.log(`🚀 Server ready at ${url}`);
});
Running the following query:
{
books {
title
author
}
}
We get:
🚀 Server ready at http://localhost:4000/
Khalil Stemmler
Billy Bob Thorton
Billy Bob Thorton
Reference: "Context Argument - Apollo Docs".

Nested query and mutation in type-Graphql

I found a feature in graphql to write nested query and mutation, I tried it but got null. I found the best practices of building graphqL schema on Meetup HolyJs and the speaker told that one of the best ways is building "Namespaced" mutations/queries nested, in this way you can write some middlewares inside the "Namespaced" mutations/queries and for get the Child mutation you should return an empty array because if you return an empty array, Graphql understand it and go one level deep.
Please check the example code.
Example in graphql-tools
const typeDefs = gql`
type Query { ...}
type Post { ... }
type Mutation {
likePost(id: Int!): LikePostPayload
}
type LikePostPayload {
recordId: Int
record: Post
# ✨✨✨ magic – add 'query' field with 'Query' root-type
query: Query!
}
`;
const resolvers = {
Mutation: {
likePost: async (_, { id }, context) => {
const post = await context.DB.Post.find(id);
post.like();
return {
record: post,
recordId: post.id,
query: {}, // ✨✨✨ magic - just return empty Object
};
},
}
};
This is my Code
types
import { ObjectType, Field } from "type-graphql";
import { MeTypes } from "../User/Me/Me.types";
#ObjectType()
export class MeNameSpaceTypes {
#Field()
hello: string;
#Field({ nullable: true })
meCheck: MeTypes;
}
import { Resolver, Query } from "type-graphql";
import { MeNameSpaceTypes } from "./MeNamespace.types";
#Resolver()
export class MeResolver {
#Query(() => MeNameSpaceTypes)
async Me() {
const response = {
hello: "world",
meCheck:{}
};
return response;
}
}
Result of code
query {
Me{
hello
meCheck{
meHello
}
}
}
--RESULT--
{
"data": {
"Me": {
"hello": "world",
"meCheck": {
"meHello": null
}
}
}
}
I got a null instead a meHello resolver. Where am I wrong?
Namespaced mutations are against GraphQL spec as they are not guarranted to run sequentially - more info in this discussion in GitHub issue related to your problem:
https://github.com/MichalLytek/type-graphql/issues/64

#client Apollo GQL tag breaks query

I have a vue-apollo (using nuxt) query that is supposed to have a local client field show. However, when I have the show #client line included in the query the component does not render. For some reason it also seems to fail silently.
query myAccounts {
accounts: myAccounts {
email
calendars {
id
name
hex_color
is_enabled
show #client
}
}
}
I am extending the Calendar type in an extensions.js file (pasted below) with two mutations.
import gql from 'graphql-tag'
export const typeDefs = gql`
extend type Calendar {
show: Boolean
}
type Mutation {
showCalendar(id: ID!): Boolean
hideCalendar(id: ID!): Boolean
}
`
Here is the resolver that sets the value, along with the Apollo config:
import { InMemoryCache } from 'apollo-cache-inmemory'
import { typeDefs } from './extensions'
import MY_ACCOUNTS_QUERY from '~/apollo/queries/MyAccounts'
const cache = new InMemoryCache()
const resolvers = {
Mutation: {
showCalendar: (_, { id }, { cache }) => {
const data = cache.readQuery({ query: MY_ACCOUNTS_QUERY })
const found = data.accounts
.flatMap(({ calendars }) => calendars)
.find(({ id }) => id === '1842')
if (found) {
found.show = true
}
cache.writeQuery({ query: todoItemsQuery, data })
return true
}
}
}
export default context => {
return {
cache,
typeDefs,
resolvers,
httpLinkOptions: {
credentials: 'same-origin'
},
}
}
along with the nuxt config:
apollo: {
defaultOptions: {
$query: {
loadingKey: 'loading',
fetchPolicy: 'cache-and-network',
},
},
errorHandler: '~/plugins/apollo-error-handler.js',
clientConfigs: {
default: '~/apollo/apollo-config.js'
}
}
Querying local state requires the state to exist (i.e. it should be initialized) or for a local resolver to be defined for the field. Apollo will run the resolver first, or check the cache directly for the value if a resolver is not defined. There's not really a good way to initialize that value since it's nested inside a remote query, so you can add a resolver:
const resolvers = {
Calendar: {
show: (parent) => !!parent.show,
},
// the rest of your resolvers
}
See the docs for additional examples and more details.

Hello world example for Apollo Client 2 + React?

Im trying to return a string with React and GraphQL but I'm getting stuck at the first stage. Here is my attempt:
import { makeExecutableSchema } from 'graphql-tools';
const typeDefs = `
type Query {
author: Person
}
type Person {
name: String
}
`;
const resolvers = {
Query: {
author: { name: 'billy' },
},
};
const schema = makeExecutableSchema({
typeDefs,
resolvers,
});
createApolloServer({ schema });
And this is my understanding of that code:
In my schema I've defined a Query called author which should return a Person.
A Person has a name field which is a string.
My resolver has a Query called author which should return an object with a name field of value 'billy'
However in my Graphicool browser tools this query:
query {
author{
name
}
}
Returns this:
{
"data": {
"author": null
}
}
Resolvers are functions which GraphQL will call when resolving that particular field. That means your resolvers object should look more like this:
const resolvers = {
Query: {
author: () => ({ name: 'billy' }),
},
}
Or, alternatively,
const resolvers = {
Query: {
author() {
return { name: 'billy' }
},
},
}
You can check out the docs for more information.
import { createApolloServer } from 'meteor/apollo';
import { makeExecutableSchema } from 'graphql-tools';
import merge from 'lodash/merge'; // will be useful later when their are more schemas
import GroupsSchema from './Groups.graphql';
import GroupsResolvers from './resolvers';
const typeDefs = [GroupsSchema];
const resolvers = merge(GroupsResolvers);
const schema = makeExecutableSchema({
typeDefs,
resolvers,
});
createApolloServer({ schema });
In ./Groups.graphql:
type Query {
hi: String
groups: [Group]
group: Group
}
type Group {
name: String
}
In './resolvers':
export default {
Query: {
hi() {
return 'howdy';
},
groups() {
return [{ name: 'one', _id: '123' }, { name: 'two', _id: '456' }];
// return Groups.find().fetch();
},
group() {
return { name: 'found me' };
},
},
};
In a React component:
const mainQuery = gql`
{
groups {
name
}
}
`;
export default graphql(mainQuery)(ComponentName);

Resources