(Apollo) GraphQL Merging schema - node.js

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.

Related

How to represent a map or object with key-value pairs in GraphQL?

How can I query for the following object?
{
result: {
'1': {
^^^ these are dynamic keys, never constant
id: 'id1',
},
'20': {
id: 'id2',
},
'300': {
id: 'id3',
},
}
}
I know that I can define the result object fairly simply, if it wasn't a key-value pair object.
const ResultQueryType = new GraphQLObjectType({
name: 'ResultQueryType',
fields: () => ({
id: { type: GraphQLString }
})
})
But this is clearly not what I need. I haven't encountered such a scenario with GraphQL yet, what can I do here?
You can try the dynamic key as suggested here. https://graphql.org/graphql-js/type/#graphqlobjecttype
const ResultQueryType = new GraphQLObjectType({
name: "ResultQueryType",
fields: () => ({
[fieldName: string]: { type: GraphQLString },
}),
});
You can only query fields that have been explicitly defined in the schema.
Spec: The target field of a field selection must be defined on the scoped type of the selection set.
Docs Every GraphQL service defines a set of types which completely describe the set of possible data you can query on that service. Then, when queries come in, they are validated and executed against that schema.
In other words, you can't have a Results map type (and therefore can't query it) if its fields are not known to the schema definition. There are a couple of workarounds:
Use JSON. Many implementations let you define custom scalars as JSON or
have a JSON type that is a String alias. You keep the map structure but lose type awareness. It's left to the client to parse the result.
Refactor your map to an array. If you can merge the top-level key into each record, you can return an [Item] array.
You have to abandon the map, but you keep full GraphQL type-awareness.

How to add a `resolveType` to GraphQL?

I am trying to query a single MongoDB document (trivia) using GraphQL, but am having trouble with one of the document fields. It's the trivia.rounds field that should return an array of objects (either LightningRound or MultipleChoiceRound).
schema.graphql
type Trivia {
_id: String!
createdAt: String!
rounds: [Round]!
}
interface Round {
type: String!
theme: String!
pointValue: Int!
}
type LightningRound implements Round {
type: String!
theme: String!
pointValue: Int!
questions: [LightningRoundQuestion]
}
type MultipleChoiceRound implements Round {
type: String!
theme: String!
pointValue: Int!
questions: [MultipleChoiceRoundQuestion]
}
// ...
trivia.js // resolver
require('dotenv').config()
const { ObjectId } = require('mongodb')
const trivia = (app) => {
return async (root, { _id }) => {
return app
.get('db')
.collection(process.env.DB_COLLECTION_TRIVIA)
.findOne(ObjectId(_id))
}
}
module.exports = {
trivia
}
graphql query
query {
trivia(_id: "5e827a4e1c9d4400009fea32") {
_id
createdAt
rounds {
__typename
... on MultipleChoiceRound {
type
theme
}
... on PictureRound {
type
theme
}
... on LightningRound {
type
theme
}
}
}
}
I keep getting the error:
"message": "Abstract type \"Round\" must resolve to an Object type at runtime for field \"Trivia.rounds\" with value { questions: [[Object], [Object]] }, received \"undefined\". Either the \"Round\" type should provide a \"resolveType\" function or each possible type should provide an \"isTypeOf\" function."
I don't understand what it means by resolveType or isTypeOf. I've seen this in other questions, but have no clue what to implement in my setup. The db connection and resolver works fine if I remove the rounds field, so it's something there...
GraphQL supports two kinds of abstract types -- unions and interfaces. An abstract type is a type that represents two or more possible types. Abstract types allow you to specify a single type for your field that could be one of several possible types at runtime (i.e. when the query is executed). When executing a query, GraphQL can never return an abstract type -- instead, the type has to be resolved into one of the possible types when the query is executed.
If a field returns a list, then the type for each item in the list will resolved separately. This type resolution happens before any of the fields on each item are resolved. More to the point, the type that's resolved determines which fields need to be resolved in the first place.
In your example above, you've defined an abstract type (the interface Round) and several possible types for it (LightningRound, MultipleChoiceRound, etc.). However, you have not told GraphQL how to determine whether a Round is a LightningRound, a MultipleChoiceRound or another possible type. This is the purpose of providing a resolveType function. You typically define a resolveType function for each abstract type in your schema. Assuming you're using graphql-tools or apollo-server, you provide this function through the same resolver map object you use to define your resolvers:
const resolvers = {
Round: {
__resolveType: (round) => {
// your code here
},
},
}
resolveType will be passed the Round object (i.e. one of the objects returned by your rounds resolver) -- you can use that value to determine what kind of Round it is. Based on your code, I'm guessing you'd use the type property to differentiate between the different types. resolveType should return a string value with the name of the matched type. So it could be as simple as:
const resolvers = {
Round: {
__resolveType: (round) => {
return round.type
},
},
}
For additional examples, see the docs.
isTypeOf is an alternative approach to resolving the type. Instead of defining a resolveType function for the abstract type, you can define a isTypeOf function for each possible type. This function returns true or false to indicate whether the object it received is in fact the type. There are uses for isTypeOf, but it's typically easier to just use resolveType instead.

Resolving nested data in express GraphQL

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).

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.

Foxx model schema with nested objects

All the examples I've found for Foxx.Model schemas are flat - i.e. they don't include nested objects.
I'm trying to add a hash to save geo info on a model like this:
var Foo = Foxx.Model.extend({
schema: {
name: joi.string().required(),
location: joi.object().keys({
lat: joi.number(),
lng: joi.number()
})
}
});
This shows up in the Foxx interface Data Type as this:
foo {
name (string),
location (object, optional)
}
How do I get it to show the key names 'lat' and 'lng' for the location object?
Or am I thinking about this incorrectly?
You are using it correctly and it will work and check your object correctly. This is just a limitation of the documentation tool used in the admin interface of ArangoDB.

Resources