Resolving nested data in express GraphQL - node.js

I am currently trying to resolve a simple recipe list that has a reference to ingredients.
The data layout looks like this:
type Ingredient {
name: String!
amount: Int!
unit: Unit!
recipe: Recipe
}
type Recipe {
id: Int!
name: String!
ingredients: [Ingredient]!
steps: [String]!
pictureUrl: String!
}
As I understand it, my resolvers should look like this:
The first one resolves the recipes and second one resolves the ingredient field in the recipe. It can (from my understanding) use the argument provided by recipe. In my recipe object, the ingredient is referenced by id (int), so this should be the argument (at least that's what I think).
var root = {
recipe: (argument) => {
return recipeList;
},
Recipe: {
ingredients: (obj, args, context) => {
//resolve ingredients
}
},
These resolvers are passed to the app like this:
app.use('/graphql', graphqlHTTP({
schema: schema,
graphiql: true,
rootValue: root,
}));
However, my resolver does not seem to be called. I would like the ingredients to be resolved on the fly when queried in my query.
The endpoint works, but as soon as I query for ingredients, an error with this message "message": "Cannot return null for non-nullable field Ingredient.name.", is returned.
When trying to log the incoming arguments in my resolver, I can see that it is never executed. Unfortunately, I can't find examples on how to do this with express-graphql when using it like I am.
How do I write seperate resolvers for nested types in express-graphQL?

Only resolvers for queries and mutations can be defined through root, and even then this is bad practice. I'm guessing you're building your schema using buildSchema, which is generally a bad idea since the generated schema will only use default resolvers.
The only way to define resolvers for a field like ingredients when using plain GraphQL.js is to not use buildSchema. Instead of generating your schema from a string, you would define it programatically (i.e. defining a GraphQLSchema and all the types it uses).
Doing the above is a huge pain, especially if you already have your schema defined in a string or document. So the alternative option is to use graphql-tools' makeExecutableSchema, which lets you inject those resolvers into your type definitions like you're trying to do. makeExecutableSchema returns a GraphQLSchema object, so you can use it with your existing code (you don't have to change your middleware to apollo-server if you don't want to).

Related

What is a named Apollo Server field within a resolver? How does it work?

In a server I'm working on, I've noticed it has different named resolvers and I'm unsure how they work:
export const resolver = {
Query: {
getUsersById(...
},
Mutation: {
updateUserById(...
},
User: {
accounts(...
},
I understand that the Query field will mean that the resolver getUserById will be a query and the same with resolvers within the Mutation field. I can query those by doing:
query {
getUsersById(...)
}
I don't understand how this works with named fields since obviously I can't do:
user {
accounts(...)
}
I can't find any documentation on this either, so any clarification would be appreciated!
Those may be field resolvers. When Apollo finishes a query or mutation resolver and is about to return an object, e.g. in your example, Users or accounts, it runs it through any field resolvers that are present for that object. The field resolver can modify or add fields that are part of the object.

(Apollo) GraphQL Merging schema

I am using GraphQL tools/libraries offered by Apollo.
It is possible to merge remote GraphQL schema into a nested structure?
Assume I have n remote schemas, and I would like to merge the Query, Mutation and Subscription from different schemas into a single schema, except, each remote schema is placed under their own type.
Assume we have a remote schema called MenuX with:
type Item {
id: Int!
description: String
cost: Int
}
type Query {
items: [Item]
}
and a remote schema called MenuY with:
type Item {
id: Int!
name: String
cost: Int
}
type Query {
items: [Item]
}
I would like to merge the schema into a single schema but under their own types. A contrived example:
type MenuXItem {
id: Int!
description: String
cost: Int
}
type MenuYItem {
id: Int!
name: String
cost: Int
}
type MenuXQuery {
items: [MenuXItem]
}
type MenuYQuery {
items: [MenuYItem]
}
type Query {
MenuX: MenuXItem
MenuY: MenuYItem
}
As we can see that under the Query type it contains two new types which contain the query type from the remote schemas. Item from schema MenuX have been renamed by using the transformers from graphql-tools, similarly Item from schema MenuY has been transformed as well.
But is it possible to transform the structure as well?
With the actual remote schemas, we are looking at hundreds of types from each schema, and ideally, I would like to not pollute the root types and the Introspection documentation in GraphiQL.
Apollo's graphql-tools includes a module to transform schemas. You should be able to rename everything in MenuX with something like
import {
makeRemoteExecutableSchema,
transformSchema,
RenameTypes
} from 'graphql-tools';
const schema = makeRemoteExecutableSchema(...);
const menuXSchema = transformSchema(schema, [
RenameTypes((name) => `MenuX${name}`)
]);
Then you can use the transformed schema as the input to mergeSchemas.
Note that the top-level Query and Mutation types are somewhat special and you may want to try to more directly merge those types without renaming them, particularly if they don't conflict.
There is a plugin for Gatsby that contains a transformer that does what you want: https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-graphql/src/transforms.js
It namespaces the types of an existing GraphQL schema, so that you end up with:
type Namespace1Item {
...
}
type Namespace2Item {
...
}
type Namespace1Query {
items: [Namespace1Item]
}
type Namespace2Query {
items: [Namespace2Item]
}
type Query {
namespace1: Namespace1Query
namespace2: Namespace2Query
}
So, if you transform your schemas and them merge them, you should be good.
Its possible to achieve schema where Query type has root fields as entry points into source schemas, but only for Query type as Mutation type doesn't support nesting of mutations under names.
For this reason, prefixing names is prefered solution for schema stitching.

What is the use of `schema` typedef in GraphQL?

I'm getting started on GraphQL with Apollo + Express and I see that the example adds a schema name at the bottom of the typedefs:
let typeDefs = [`
type Query {
hello: String
}
schema {
query: Query
}`];
And after defining the resolvers it generates the schema with makeExecutableSchema:
let schema = makeExecutableSchema({typeDefs, resolvers});
However if I remove the schema part of the typedefs I can still use my endpoint normally, e.g.:
http://localhost:3000/graphql/?query={hello}
returns:
{"data":{"hello":"world"}}
But if I change the query part for something else, the server fails:
let typeDefs = [`
type Query {
hello: String
}
schema {
testquery: Query
}`];
GraphQLError: Syntax Error: Unexpected Name "testquery"
I have read through Apollo's tutorial pages and also the How To GraphQL tutorial for Node.js + GraphQL but can't find reference to that schema part.
What is it used for?
About the schema
A schema can have up to three root operation types but it requires only one. The query type must be present in every schema and needs to be an object type (Spec). This type is the root type when making queries to GraphQL. Furthermore there is a mutation root type that is used when making mutations. The latest addition is the subscription type.
About root types
Root types are simple object types. They can have fields and these fields are arguments. By default the query type is called Query, the mutation type is called Mutation and the subscription root type is called Subscription. If you follow these standard names the schema specification can be omitted.
# Doesn't need further specification, Query is the default name
type Query {
# ...
}
Still you can name these types however you want. To define which of your object types is a root type to enter the graph you have to use the schema definition.
# Non standard query type name
type MyQuery {
# ...
}
schema {
# Needs to be defined in the schema declaration
query: MyQuery
}
The same thing can be done for mutation and subscription types.

Leveraging both GraphQL and Mongoose

Is it possible to leverage both GraphQL and Mongoose?
So far, I have been able to integrate both GraphQL and Mongoose to handle populating the database, but I am struggling to understand how this can work to retrieve data, specifically data with nested references.
Consider this schema:
const fooSchema = new Schema({
name: { type: 'String', required: true },
bar: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Bar',
required: false,
}],
});
The Bar schema is essentially the same, with just a field for "name".
Is it possible to run a GraphQL query to populate the data with the references in 'bar'?
Currently, we are using GraphQL-Tools to create our typeDefs, Mutations, and Queries which looks something like this:
const typeDefs = `
type Foo {
name: String!,
bars:[Bar]
}
type Bar {
_id: ID,
name: String,
}
type Query {
allFoos: [Foo!]!
foo(_id: ID!): Foo
}
type Mutation {
...
}
`;
module.exports = makeExecutableSchema({typeDefs, resolvers});
And finally a query directive that looks like this:
const allFoos = async (root, data) => {
return await Foo.find({});
};
I am able to change the query directive to use .populate() to get Bar, but that does not actually end up populating the results, which I think is because of the way the typeDefs are set up.
So is it possible to make these two concepts work together? Does it even make sense to use them both?
As they describe GraphQL:
GraphQL is a query language for your API, and a server-side runtime
for executing queries by using a type system you define for your data.
GraphQL isn't tied to any specific database or storage engine and is
instead backed by your existing code and data.
Where as mongoose is
Writing MongoDB validation, casting and business logic boilerplate is
a drag. That's why we wrote Mongoose
Monogoose work with mongodb validation whereas graphql is a query language for the API.
You can read a basic example from here about Setting up a simple GraphQL Server with Node, Express and Mongoose.
These two are completely different. Mongoose work when you are performing any operation on database, whereas grapgl comes in picture when you call a API. Graphql validate your API input parameter and return parameter. If you are adding these two in single app. It will work well.
Mongoose will validate your db operation.
GraphQL will validate your API input and output parameter.

Defining Schema with graphql-tools

I am trying to define my schema for my API. I am running into an issue where each resource has many different sections. Ideally I would like to just be able to say sections is a JSON object rather than define all the different modules within the sections. Is there a way I can do this? As far as I can tell, there doesn't seem to be a JSON type definition using graphql-tools
// Define your types here.
const typeDefs = `
type Resource {
id: ID,
title: String!,
slug: String!,
path: String!,
template: String!,
published: String!,
sections: Sections
}
type Sections {
// ...
}
type Query {
allLinks(filter: LinkFilter): [Link!]!
findResource(filter: ResourceFilter): [Resource!]!
}
`;
You'll need to import a custom JSON scalar. This module is one of the more popular ones available.
Anywhere in your typeDefs, add the following line:
scalar JSON
And then inside the resolvers object you pass to makeExecutableSchema:
const GraphqlJSON = require('graphql-type-json')
const resolvers = {
Query: //queries
Mutation: //mutations
// All your other types
JSON: GraphqlJSON
}
One word of warning: anytime you use JSON as a scalar, you lose flexibility. Part of the charm of GraphQL is that it allows clients to only query the fields they need. When you use JSON, your client will only be able to either query the whole chunk of JSON, or not at all -- they won't be able to pick and choose parts of it.

Resources