Random Points on Apollo GraphQL, or GraphQL
A Series Blog for Deep Dive into Apollo GraphQL from Backend to Frontend
This is a 4th of the series blogs on deep dive into Apollo GraphQL from backend to frontend. A lot of information is Apollo GraphQL Doc or GraphQL Doc as well as their source code on Github — all tributes go to them. For my part, I would like to give you my “destructuring” of the original knowledge and my reflection on it, analysis on the code examples/source code as well as some extra examples.
This blog is going to be a bit unusual as it has a collection of topics relating to GraphQL and not limited to Apollo GraphQL framework. I encountered these questions in my learning process, and would like to share the answers that I found. In my situation, they helped me deepen my understanding of GraphQL, and Apollo GraphQL.
Graph in GraphQL
Have you ever wondered that why GraphQL is called GraphQL? Its query statement seems to be a variation of JSON writing, and JSON is a Tree data structure. Why not call it TreeQL, but GraphQL?
To understand that we need to understand a Tree and a Graph.
This is a tree.
Tree has one and only one Root node, and for each non-Root node, there is one and only one parent node; they form a hierarchical structure. For any two nodes, there is one and only one connection path; there is no loop, and no recursive reference.
This is a graph.
Between the nodes in the Graph, there may be more than one connection path, there may be cycles, there may be recursive references, and there may be no Root nodes. They form a network structure.
We can compress the connection path of the Graph network structure into a simplified form where any node has only a unique connection path. In this way, the network structure degenerates into a hierarchical structure, and it becomes a Tree.
That is to say, Graph is a more complex data structure than Tree, which is a simplified form of the former. With Graph, we can derive different Trees with different tailoring methods. However, a Tree can not easily transform to a Graph structure without additional information added.
GraphQL, How?
When you shop for a GraphQL solution to your project(within JavaScript), Apollo GraphQL is not the only option. There are 3 popular solutions:
- graphql-js:The implementation officially provided by Facebook.
- Apollo GraphQL: The implementation and GraphQL ecosystem provided by Apollo, it’s rich in content, and comes with a variety of tools.
- type-graphql:The implementation is built with TypeScript at design, and is mainly to output executable Schema.
Of all the three (possibly others uncovered as well), graphql-js is the foundation and starting point.
The differences are mainly in how schema is created. Let’s say a comparison of these three.
graphql-js
=====================schema creation
import {
graphql,
GraphQLList,
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
} from 'graphql'const article = new GraphQLObjectType({
fields: {
time: {
type: GraphQLString,
description: 'article publish time',
resolve (parentValue) {
return parent.date
}
},
title: {
type: GraphQLString,
description: 'article heading',
}
}
})const author = new GraphQLObjectType({
fields: {
name: {
type: GraphQLString,
description: 'article author',
},
articles: {
type: GraphQLList(article),
description: 'list of articles',
resolve(parentValue, args, ctx, info) {
//...
},
}
},
})const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'RootQuery',
fields: {
contractedAuthor: {
type: author,
description: 'contracted author',
resolve(parentValue, args, ctx, info) {
// ...
},
},
},
}),
})=====================schema execution
import { parse, execute, graphql } from 'graphql'
import { schema } from 'schema-from-above'const document = `
query {
contractedAuthor {
name
articles {
title
}
}
}`const response = await execute({
schema,
document: parse(document),
})
Apollo GraphQL
Apollo provides a complete GraphQL Node.js service framework, but for a more intuitive experience of the executable Schema creation process by using the graphql-tools (provided by Apollo) to create an executable Schema.
npm install graphql-tools graphql
As you can see, graphql-tools
is dependent on graphql
.
import { makeExecutableSchema } from 'graphql-tools'const typeDefs = `
type Article {
time: String
title: String
}type Author {
name: String
articles: [Article]
}type Query {
contractedAuthor: Author
}schema {
query: Query
}
`
const resolvers = {
Query: {
contractedAuthor (parentValue, args, ctx, info) {
//...
}
},
Author: {
articles (parentValue, args, ctx, info) {
// ...
}
},
Article: {
time (article) {
return article.date
}
}
}
const executableSchema = makeExecutableSchema({
typeDefs,
resolvers,
})
As you can see, when generating executable Schema, Schema and Resolver will be mapped as a 1–1 relationship.
type-graphql
npm i graphql @types/graphql type-graphql reflect-metadata
You can see that type-graphql also requires graphql-js (graphql) as a dependency.
import 'reflect-metadata'
import { buildSchemaSync } from 'type-graphql'@ObjectType({ description: "Object representing cooking recipe" })
class Recipe {
@Field()
title: string
}@Resolver(of => Recipe)
class RecipeResolver {
@Query(returns => Recipe, { nullable: true })
async recipe(@Arg("title") title: string): Promise<Recipe> {
// return await this.items.find(recipe => recipe.title === title);
}@Query(returns => [Recipe], { description: "Get all the recipes from around the world " })
async recipes(): Promise<Recipe[]> {
// return await this.items;
}@FieldResolver()
title(): string {
return '标题'
}
}
const schema = buildSchemaSync({
resolvers: [RecipeResolver]
})
The core of type-graphql is Classes, which use decorator annotations to reuse classes to generate Schema structure, and the annotation information is extracted by reflect-metadata. For example, @ObjectType() and @Field map the class Recipe to the schema Recipe type with the title field. The recipe and recipes methods are mapped to the root field under the schema query by the @Query annotation. The title() method is mapped to the Resolver of the title field of type Recipe by @Resolver(of => Recipe) and @FieldResolver().
Differences and Similarities
- Execution:
As you might have noticed, I skipped the execution part for Apollo GraphQL and type-graphql, because the executable Schema generated by these two implementations are the same. Their type definitions are all using GraphQLObjectType
provided by graphql-js, etc., and you can choose to use the execution functions (graphql
, execute
) provided by graphql-js or the service execution provided by apollo-server to do the execution work.
For example, for a simple Apollo execution:
var { graphql, buildSchema } = require('graphql');var schema = buildSchema(`
type Query {
hello: String
}
`);var root = {
hello: () => {
return 'Hello world!';
},
};graphql(schema, '{ hello }', root).then((response) => {
console.log(response);
});//=================>node server.js//================>{
data: {
hello: 'Hello world!';
}
}
- Structure:
The differences emerge themselves in how the schema is constructed. As an official implementation, graphql-js keeps the structure (Schema) and behaviour (Resolver) together. It does not directly use SDL to define the Schema.
The Apollo separates the structure and behaviour, and uses SDL. The structure and behaviour use property names to form a corresponding relationship.
GraphQL without Schema?
This is an interesting fact, but you may not realise it yet. So there are two components in the GraphQL language design:
- Data provider writes GraphQLSchema
- Data consumers write GraphQLQuery
They go hand in hand, but not always.
The GraphQL TypeSystem is a static type system. We can call it statically typed GraphQL. In addition, the community has a dynamic type of GraphQL practice, which you can view it in graphql-anywhere.
The difference between this and the static type of GraphQL is that there is no Schema-based data validation , and is directly based on the fields in the query query to trigger the Resolver function.
Obviously it loses some of its merits but it also opens the window for great possibilities.
Other Best Practice
I found this on the Graphql-tools Doc and it helps me a lot on designing my project. You can also view some other resources here.
- Use SDL when possible. As it’s more readable comparing to other ways of constructing a schema, e.g.
buildSchema
and separates concerns withresolverMap
- GraphQL is a thin API layer routing the queries from the Http server to the business logic. So don’t do the heavy lifting at the resolver level, dispatch the action down the chain to e.g. datasources/models however you name them, and hide the implementation.
- Optimising performance at both server and client side. For server side, you can easily run into N+1 questions with over-fetching. In this case, you a dataloader or other batch request mechanism for that. You can also implement caching at the server end. For client side, it is important to use the InMemoryCache to plan out your cache policies. I myself find Apollo Client being a very good package providing caching+state management two in one.
That’s it !
It’s a bit bits and pieces today but these are the information I really benefit from myself.
Happy Reading!