Defining Schema with graphql-tools - node.js

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.

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.

Can make graphql query to be strongly types with typescript?

I'm using typescript and graphql and every time I want to send request to my graphql I need to write a query.
My question is there is anyway to use type/interface to create the query?
for example take a look at this interface:
interface Document {
id: string;
name: string;
author: {
name: string;
}
}
The graphql query for this is
query document {
id
name
author {
name
}
}
and I use axois to get the data:
const data = await axios.get("/graphql", { query });
Is there easy way to get the data using strongly typed? something like:
const data = await axois.get('/graphql', { fields: ['id', 'name', 'author.name'] })
And typescript will throw an error if some string from fields doesn't include in the interface.
axios.get can take a generic which will provide the types on the return type. An example should make it clear. Using your code above
// vvvvvvvv==> your expected return data
const result = await axios.get<Document>("/graphql", { query });
// data will be strongly typed (but NOT checked)
result.data.id; // => string
result.data.author.name; // => string
For more info check axios' index.d.ts file.

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

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

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.

Resources