Using facebook's reference library, I found a way to hack generic types like this:
type PagedResource<Query, Item> = (pagedQuery: PagedQuery<Query>) => PagedResponse<Item>
interface PagedQuery<Query> {
query: Query;
take: number;
skip: number;
}
interface PagedResponse<Item> {
items: Array<Item>;
total: number;
}
function pagedResource({type, resolve, args}) {
return {
type: pagedType(type),
args: Object.assign(args, {
page: { type: new GraphQLNonNull(pageQueryType()) }
}),
resolve
};
function pageQueryType() {
return new GraphQLInputObjectType({
name: 'PageQuery',
fields: {
skip: { type: new GraphQLNonNull(GraphQLInt) },
take: { type: new GraphQLNonNull(GraphQLInt) }
}
});
}
function pagedType(type) {
return new GraphQLObjectType({
name: 'Paged' + type.toString(),
fields: {
items: { type: new GraphQLNonNull(new GraphQLList(type)) },
total: { type: new GraphQLNonNull(GraphQLInt) }
}
});
}
}
But I like how with Apollo Server I can declaratively create the schema. So question is, how do you guys go about creating generic-like types with the schema language?
You can create an interface or union to achieve a similar result. I think this article does a good job explaining how to implement interfaces and unions correctly. Your schema would look something like this:
type Query {
pagedQuery(page: PageInput!): PagedResult
}
input PageInput {
skip: Int!
take: Int!
}
type PagedResult {
items: [Pageable!]!
total: Int
}
# Regular type definitions for Bar, Foo, Baz types...
union Pageable = Bar | Foo | Baz
You also need to define a resolveType method for the union. With graphql-tools, this is done through the resolvers:
const resolvers = {
Query: { ... },
Pageable {
__resolveType: (obj) => {
// resolve logic here, needs to return a string specifying type
// i.e. if (obj.__typename == 'Foo') return 'Foo'
}
}
}
__resolveType takes the business object being resolved as its first argument (typically your raw DB result that you give GraphQL to resolve). You need to apply some logic here to figure out of all the different Pageable types, which one we're handling. With most ORMs, you can just add some kind of typename field to the model instance you're working with and just have resolveType return that.
Edit: As you pointed out, the downside to this approach is that the returned type in items is no longer transparent to the client -- the client would have to know what type is being returned and specify the fields for items within an inline fragment like ... on Foo. Of course, your clients will still have to have some idea about what type is being returned, otherwise they won't know what fields to request.
I imagine creating generics the way you want is impossible when generating a schema declaratively. To get your schema to work the same way it currently does, you would have to bite the bullet and define PagedFoo when you define Foo, define PagedBar when you define Bar and so on.
The only other alternative I can think of is to combine the two approaches. Create your "base" schema programatically. You would only need to define the paginated queries under the Root Query using your pagedResource function. You can then use printSchema from graphql/utilities to convert it to a String that can be concatenated with the rest of your type definitions. Within your type definitions, you can use the extend keyword to build on any of the types already declared in the base schema, like this:
extend Query {
nonPaginatedQuery: Result
}
If you go this route, you can skip passing a resolve function to pagedResource, or defining any resolvers on your programatically-defined types, and just utilize the resolvers object you normally pass to buildExecutableSchema.
Related
I've been reading through the graphQL docs and found that they've explained the implementation of the graphql server in 2 ways: one using graphql-yoga which is a fully featured graphql server and another one is using graphql, express-graphql and express. In both cases, we pass the schema and resolver functions while creating the server instance.
But the implementation of resolver function differs. While using graphql-yoga, the resolver function is provided with 4 arguments which contains information about the parent object, arguments received, context, info. whereas in the other case (using graphql), the resolver function only gets the arguments object.
Why is that so ? If I want the info, context objects, how do I get it ?
Using graphql-yoga example: https://graphql.org/learn/execution/
Using graphql example: https://graphql.github.io/graphql-js/mutations-and-input-types/
// Code example using graphql
var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');
var schema = buildSchema(`
type Query {
rollDice(numDice: Int!, numSides: Int): [Int]
}
type Mutation {
addDice(numDice: Int): String
}
`);
var root = {
rollDice({numDice, numSides}) {
return [1, 2];
},
addDice({numDice}) {
console.log("Adding something");
return "Added";
}
};
var app = express();
app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true,
}));
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');
// Code example using graphql-yoga
let graphqlServer = require("graphql-yoga");
const typeDefs = `
type Query {
rollDice(numDice: Int!, numSides: Int): [Int]
}
type Mutation {
addDice(numDice: Int): String
}
`;
const resolvers = {
Query: {
rollDice(parent, args, context, info) {
console.log(args.numDice);
console.log(args.numSides);
return [1, 2];
}
},
Mutation: {
addDice(parent, args, context, info) {
console.log(args.numDice);
return "Added";
}
}
};
const server = new graphqlServer.GraphQLServer({
typeDefs,
resolvers
});
server.start(() => {
console.log("server started on localhost:4000");
});
Difference between these 2 code snippets:
The resolver functions are present inside appropriate types (i.e. Query, Mutation) in one case. In the other case, they are present inside one root object. This means that I can have methods with same name in Query and Mutation in the first case, whereas in the second case that's not possible since they are keys of a single object and keys should be unique.
Why is this so ? Am I basically missing something ? How can the implementation details differ from one package to another ?
REAL TALK: the GraphQL.js docs are not that great. In my opinion, they never should have used examples with buildSchema in the first place because it understandably leads to this kind of confusion.
GraphQL.js (i.e. the graphql package) is the JavaScript implementation of GraphQL. Building a schema in GraphQL.js is done programmatically, by constructing an instance of the GraphQLSchema class:
const userType = new GraphQLObjectType({
name: 'User',
fields: {
id: {
type: GraphQLID,
},
email: {
type: GraphQLString,
},
},
});
const queryType = new GraphQLObjectType({
name: 'Query',
fields: {
user: {
type: userType,
resolve: () => ({ id: 1, email: 'john.doe#example.com' }),
},
},
});
const schema = new GraphQLSchema({
query: queryType,
})
If we print this schema in Schema Definition Language (SDL), it looks like this:
type Query {
user: User
}
type User {
id: ID
email: String
}
Working with SDL is much easier than having to write out all that code. However, GraphQL.js does not provide a way to build a fully-featured schema from SDL. It does provide a buildSchema function, but this utility constructs a schema without any resolvers (and a number of other features like union/interface type resolution).
The graphql-tools package provides a makeExecutableSchema function that lets you build a schema from SDL and a resolver map object. This is what's used under the hood by apollo-server and graphql-yoga. makeExecutableSchema constructs a schema from SDL using buildSchema and then mutates the resulting object, adding the resolvers in after the fact.
In GraphQL.js, the resolve function (or resolver) for a field takes four parameters -- the parent value, the field's arguments, the context and a GraphQLResolveInfo object. If we're creating a GraphQLObjectType like userType in the above example, this is the optional function we can provide for each of the fields in our object. This is the same function you define when you construct a resolver map to use with graphql-yoga. This is the only implementation of a field resolver.
So what's the deal with buildSchema??
The examples in the docs take advantage of GraphQL's default field resolver:
export const defaultFieldResolver: GraphQLFieldResolver<any, *> = function(
source,
args,
contextValue,
info,
) {
if (typeof source === 'object' || typeof source === 'function') {
const property = source[info.fieldName];
if (typeof property === 'function') {
return source[info.fieldName](args, contextValue, info);
}
return property;
}
};
As you can see, the default resolution logic looks for a property with the same name as the field on the source (parent) value. In our example above, the user resolver returns {id: 1, email: 'john.doe#example.com'} -- this is the value the field resolves to. The field is of the type User. We do not have a resolver defined for our id field, so the default resolver does its thing. The id field resolves to 1 because that's the value of the property named id on the parent object the resolver receives.
However, the parent value can also be a function instead of an object. If it's a function, it gets called first and then the return value is used. What does the function get called with? Well, it can't pass it a parent value (because of infinite recursion), but it can pass it the remaining three parameters (args, context and info). So that's what it does.
Now for the magic trick 🎩🐇
In our example, I can omit the resolver for the user field and pass a function to the root value instead.
const root = {
user: () => ({id: 1, email: 'john.doe#example.com'})
}
The root object is just an optional object that's passed down as the parent value to resolvers at the root level (like your Query or Mutation types). Otherwise, those resolvers would not have a parent value.
Query is an operational root type -- it serves as an "entry point" to the rest of your schema. Any fields on the Query type will be passed the root object as the parent value. If I omit a resolver for the user field, the default resolver will 1) examine the parent object for a property with the same name, 2) find a property and determine that it's a function, 3) call the function, 4) resolve the field to the return value of the function.
TADA!
However, because the function is called by the default resolver, and is not used as a resolver itself, it will only receive the three aforementioned parameters, instead of 4.
This is a neat way to work around not being able to actually provide custom resolvers for a schema, but it's very limited. It only works for root types, so we can't similarly provide fake resolvers for User fields or other types. We can't use interfaces or unions in our schema because we can't provide resolveType functions. And so on...
Hopefully that provides some clarity. And hopefully we can get the docs updated in the near future to avoid all this confusion in the first place.
Is it possible to use inheritance with GraphQL input types?
Something like that (this, of course, doesn't work with input types):
interface UserInputInterface {
firstName: String
lastName: String
}
input UserInput implements UserInputInterface {
password: String!
}
input UserChangesInput implements UserInputInterface {
id: ID!
password: String
}
No, the spec does not allow input types to implement interfaces. And GraphQL type system in general does not define any form of inheritance (the extends keyword adds fields to an existing type, and isn't for inheritance). The spec is intentionally constrained to stay simple. This means that you're stuck repeating fields across input types.
That said, depending on the way you construct your schema, you could build some kind of type transformer that appends the common fields programmatically based on some meta-data, e.g. a directive.
Better yet, you might be able to solve your problem via composition (always keep composition over inheritance in mind).
E.g.
input Name {
firstName: String
lastName: String
}
input UserInput {
name: Name
password: String!
}
input UserChangesInput {
name: Name
id: ID!
password: String
}
The client now has to send an object a level deeper, but that doesn't sound like much of a price for avoiding big repeating chunks. It might actually be good for the client as well, as they can now have common logic for building names, regardless of the query/mutation using them.
In this example, where it's only 2 simple fields, this approach is an overkill, but in general - I'd say it's the way to go.
Starting with the June2018 stable version of the GraphQL spec, an Input Object type can extend another Input Object type:
Input object type extensions are used to represent an input object type which has been extended from some original input object type.
This isn't inheritance per se; you can only extend the base type, not create new types based on it:
extend input MyInput {
NewField: String
}
Note there is no name for the new type; the existing MyInput type is extended.
The JavaScript reference implementation has implemented Input Object extensions in GraphQL.js v14 (June 2018), though it's unclear how to actually pass the extended input fields to a query without getting an error.
For actual type inheritance, see the graphql-s2s library.
It's doable using a custom directive.
Code Summary
const typeDefs = gql`
directive #inherits(type: String!) on OBJECT
type Car {
manufacturer: String
color: String
}
type Tesla #inherits(type: "Car") {
manufacturer: String
papa: String
model: String
}
type Query {
tesla: Tesla
}
`;
const resolvers = {
Query: {
tesla: () => ({ model: 'S' }),
},
Car: {
manufacturer: () => 'Ford',
color: () => 'Orange',
},
Tesla: {
manufacturer: () => 'Tesla, Inc',
papa: () => 'Elon',
},
};
class InheritsDirective extends SchemaDirectiveVisitor {
visitObject(type) {
const fields = type.getFields();
const baseType = this.schema.getTypeMap()[this.args.type];
Object.entries(baseType.getFields()).forEach(([name, field]) => {
if (fields[name] === undefined) {
fields[name] = { ...field };
}
});
}
}
const schemaDirectives = {
inherits: InheritsDirective,
};
Query:
query {
tesla {
manufacturer
papa
color
model
}
}
Output:
{
"data": {
"tesla": {
"manufacturer": "Tesla, Inc",
"papa": "Elon",
"color": "Orange",
"model": "S",
}
}
}
Working example at https://github.com/jeanbmar/graphql-inherits.
If you came here looking for an explanation for the "implements", keyword, here it is:
An object type must be a super‐set of all interfaces it implements. The object type must include a field of the same name for every field defined in an interface.
(Excerpt taken from the June 2018 GraphQL spec.)
Here's an example
interface Foo {
id: ID!
foo: Int!
}
type Bar implements Foo #entity {
id: ID!;
foo: Int!;
bar: Int!;
}
So the Bar type doesn't inherit from the Foo interface, but it implements it. The former must include all the fields that are listed in the latter.
I think that this is a nice way to annotate types that should be like other types.
I think I'm missing something obvious in the way GraphQL resolvers work. This is a simplified example of my schema (a Place that can have AdditionalInformation):
import { ApolloServer, gql } from 'apollo-server';
const typeDefs = gql`
type Place {
name: String!
additionalInformation: AdditionalInformation
}
type AdditionalInformation {
foo: String
}
type Query {
places: [Place]
}
`;
And the associated resolvers:
const resolvers = {
Query: {
places: () => {
return [{name: 'Barcelona'}];
}
},
AdditionalInformation: {
foo: () => 'bar'
}
};
const server = new ApolloServer({typeDefs, resolvers});
server.listen().then(({ url }) => {
console.log(`API server ready at ${url}`);
});
When I execute a basic query:
{
places {
name,
additionalInformation {
foo
}
}
}
I always get null as the additionalInformation:
{
"data": {
"places": [
{
"name": "Barcelona",
"additionalInformation": null
}
]
}
}
It's my first GraphQL app, and I still don't get why the AdditionalInformation resolver is not automatically executed. Is there some way to let GraphQL know it has to fire it?
I've found this workaround but I find it a bit tricky:
Place: {
additionalInformation: () => { return {}; }
}}
Let's assume for a moment that additionalInformation was a Scalar, and not an Object type:
type Place {
name: String!
additionalInformation: String
}
The value returned by the places resolver is:
[{name: 'Barcelona'}]
If you were to make a similar query...
query {
places {
name
additionalInformation
}
}
What would you expect additionalInformation to be? It's value will be null because there is no additionalInformation property on the Place object returned by the places resolver.
Even if we make additionalInformation an Object type (like AdditionalInformation), the result is the same -- the additionalInformation field will resolve to null. That's because the default resolver (the one used when you don't specify a resolver function for a field) simply looks for a property with the same name as the field on the parent object. If it fails to find that property, it returns null.
You may have specified a resolver for a field on AdditionalInformation (foo), but this resolver is never fired because there's no need -- the whole additionalInformation field is null so all of the resolvers for any fields of the associated type are skipped.
To understand why this is a desirable behavior, imagine a different schema:
type Article {
title: String!
content: String!
image: Image
}
type Image {
url: String!
copyright: String!
}
type Query {
articles: [Article!]!
}
We have a database with an articles table and an images table as our data layer. An article may or may not have an image associated with it. My resolvers might look like this:
const resolvers = {
Query: {
articles: () => db.getArticlesWithImages()
}
Image: {
copyright: (image) => `©${image.year} ${image.author}`
}
}
Let's say our call getArticlesWithImages resolves to a single article with no image:
[{ title: 'Foo', content: 'All about foos' }]
As a consumer of the API, I request:
query {
articles {
title
content
image
}
}
The image field is optional. If I get back an article object with a null image field, I understand there was no associated image in the db. As a front end client, I know not to render any image.
What would happen if GraphQL returned a value for the image regardless? Obviously, our resolver would break, since it would not be passed any kind of parent value. Moreover, however, as a consumer of the API, I would have to now parse the contents of image and somehow determine whether an image was in fact associated with the article and I should do something with it.
TLDR;
As you already suggested, the solution here is to specify a resolver for additionalInfo. You can also simply return that value in your places resolver, i.e.:
return [{name: 'Barcelona', additionalInfo: {}}]
In reality, if the shape of your schema aligns with the shape of your underlying data layer, it's unlikely you'll encounter this sort of issue when working with real data.
Excuse the vague code, I can't really copy/paste. :)
I have type in GraphQL like this:
type Thing {
toBe: Boolean
orNot: Boolean
}
I'm trying to create a new property on this type that is an... aggregate of those two. Basically return a new value based upon those values. The code would be like:
if (this.toBe && !this.orNot) { return "To be!"; }
if (!this.toBe && !this.orNot) { return "OrNot!"; }
Does this make sense? So it would return something like:
Thing1 {
toBe: true;
orNot: false;
newProp: "To be!"
}
Yes, you can easily create aggregated fields in your graphql Object types by handling your required logic in that aggregated field resolver. While creating object types, you have instance of that object, and therefore, you can easily create aggregated fields which are not present in your domain models using object's data and this is one of the beauty of graphql. Note that this can differ on each implementation of GraphQL libraries. Following is the example for such use case in JavaScript and Scala.
Example in Graphql.js:
var FooType = new GraphQLObjectType({
name: 'Foo',
fields: {
toBe: { type: GraphQLBoolean},
orNot: { type: GraphQLBoolean},
newProp: { type: GraphQLString,
resolve(obj) {
if (obj.toBe && !obj.orNot) { return "To be!"; }
else { return "OrNot!"; }
}
}
});
Example in Sangria-graphql:
ObjectType(
"Foo",
"graphql object type for foo",
fields[Unit, Foo](
Field("toBe",BooleanType,resolve = _.value.name),
Field("orNot",BooleanType,resolve = _.value.path),
Field("newProp",StringType,resolve = c => {
if (c.value.toBe && !c.value.orNot) "To be!" else "OrNot!"
})
)
)
The various GraphQL server library implementations all have ways to provide resolver functions that can provide the value for a field. You'd have to include it in your schema and write the code for it, but this is a reasonable thing to do and the code you quote is a good starting point.
In Apollo in particular, you pass a map of resolvers that get passed as a resolvers: option to the ApolloServer constructor. If a field doesn't have a resolver it will default to returning the relevant field from the native JavaScript object. So you can write
const resolvers = {
Thing: {
newProp: (parent) => {
if (parent.toBe && !parent.orNot) { return "To be!"; }
if (!parent.toBe && !parent.orNot) { return "OrNot!"; }
return "That is the question";
}
}
};
I have a Apollo GraphQL server talking to an API returning responses with roughly the following structure:
{
"pagination": {
"page": 1,
// more stuff
},
sorting: {
// even more stuff
},
data: [ // Actual data ]
}
This structure is going to be shared across pretty much all responses from this API, that I'm using extensively. data is going to be an array most of the time, but can also be an object.
How can I write this in an efficient way, so that I don't have to repeat all these pagination and sorting fields on every data type in my schemas?
Thanks a lot!
I've sorted your problem by creating a lib called graphql-s2s. It enhances your schema by adding support for type inheritance, generic types and metadata. In your case, creating a generic type for your Paginated object could be a viable solution. Here is an example:
const { transpileSchema } = require('graphql-s2s')
const { makeExecutableSchema } = require('graphql-tools')
const schema = `
type Paged<T> {
data: [T]
cursor: ID
}
type Node {
id: ID!
creationDate: String
}
type Person inherits Node {
firstname: String!
middlename: String
lastname: String!
age: Int!
gender: String
}
type Teacher inherits Person {
title: String!
}
type Student inherits Person {
nickname: String!
questions: Paged<Question>
}
type Question inherits Node {
name: String!
text: String!
}
type Query {
students: Paged<Student>
teachers: Paged<Teacher>
}
`
const executableSchema = makeExecutableSchema({
typeDefs: [transpileSchema(schema)],
resolvers: resolver
})
I've written more details about this here (in Part II).
When you define your schema, you will end up abstracting out pagination, sorting, etc. as separate types. So the schema will look something like:
type Bar {
pagination: Pagination
sorting: SortingOptions
data: BarData # I'm an object
}
type Foo {
pagination: Pagination
sorting: SortingOptions
data: [FooData] # I'm an array
}
# more types similar to above
type Pagination {
page: Int
# more fields
}
type SortingOptions {
# more fields
}
type BarData {
# more fields
}
So you won't have to list each field within Pagination multiple times regardless. Each type that uses Pagination, however, will still need to specify it as a field -- there's no escaping that requirement.
Alternatively, you could set up a single Type to use for all your objects. In this case, the data field would be an Interface (Data), with FooData, BarData, etc. each implementing it. In your resolver for Data, you would define a __resolveType function to determine which kind of Data to return. You can pass in a typename variable with your query and then use that variable in the __resolveType function to return the correct type.
You can see a good example of Interface in action in the Apollo docs.
The downside to this latter approach is that you have to return either a single Data object or an Array of them -- you can't mix and match -- so you would probably have to change the structure of the returned object to make it work.