详解 GraphQL

刘俊峰

2017-12-01

GraphQL 是什么?

GraphQL 是一种适用于 API 的数据查询语言。

 

仅仅是一种规范,可以有各种前后端的实现方案。

 

Facebook 于 2012 年开发,2015 年开源。

GraphQL 的优点

  • 一次查询就能获取视图所需的全部数据
  • 要什么给什么,没有冗余数据
  • 自动生成数据格式的描述文档
  • 强类型,自动校验参数类型
  • 灵活,一个接口适用多个场景,前后端解耦
  • API 可持续进化,不受版本号限制
  • 大量的工具生态系统

Architecture

支持多种操作:query 是查询,mutation 是修改

Architecture

GraphQL server with a connected database

Architecture

GraphQL layer that integrates existing systems

GraphQL 与 REST 的对比

GraphQL 与 REST 的对比

GraphQL Sepecification

  • GraphQL schema language
    定义类型系统,类似于 SQL create table
    但 GraphQL 有层次结构,而不是表格
  • GraphQL query language
    创建具体的查询,类似于 SQL select 语句

GraphQL schema language

Every GraphQL service has a query type and may or may not have a mutation type.

schema {
  query: Query
  mutation: Mutation
}

type Query {
  hero(episode: Episode): Character
  droid(id: ID!): Droid
}

GraphQL schema language

type Character {
  name: String!
  appearsIn: [Episode]!
}

type Starship {
  id: ID!
  name: String!
  length(unit: LengthUnit = METER): Float
}

Every GraphQL service defines a set of types which completely describe the set of possible data you can query on that service.

GraphQL schema language

  • Scalar types

    • Int: A signed 32‐bit integer.
    • Float: A signed double-precision floating-point value.
    • String: A UTF‐8 character sequence.
    • Boolean: true or false.
    • ID: A unique identifier.
    • Custom scalars, e.g. scalar Date.

GraphQL schema language

  • Enumeration types

  • Union types

  • Lists

  • Non-Null 

  • Interfaces

  • Input types

enum Episode {
  NEWHOPE
  EMPIRE
  JEDI
}

union SearchResult = Human | Droid | Starship

interface Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
}

input ReviewInput {
  stars: Int!
  commentary: String
}

GraphQL query language

  • Fields

  • Arguments

  • Aliases

  • Fragments 

  • Variables

  • Directives

  • Mutations

{
  hero(id: "1000") {
    name
    height
    # Queries can have comments!
    friends {
      name
    }
  }
  empireHero: hero(episode: EMPIRE) {
    name
  }
  jediHero: hero(episode: JEDI) {
    name
  }
}

Query Execution Steps

While query fields are executed in parallel, mutation fields run in series, one after the other.

 

Returned result mirrors the shape of the requested query, typically as JSON.

 

GraphQL server does this automatically, developer only need to write resolvers.

  1. Parse
  2. Validate
  3. Execute

Client side solution

yarn add apollo-client apollo-cache-inmemory apollo-link-http react-apollo graphql-tag graphql
// App.jsx
import { ApolloProvider } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';

const client = new ApolloClient({
  // By default, this client will send queries to the
  //  `/graphql` endpoint on the same host
  // Pass the configuration option { uri: YOUR_GRAPHQL_API_URL } to the `HttpLink` to connect
  // to a different host
  link: new HttpLink(),
  cache: new InMemoryCache()
});


ReactDOM.render(
  <ApolloProvider client={client}>
    <MyRootComponent/>
  </ApolloProvider>,
  document.getElementById('root'),
);


// TodoApp.jsx
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';

export default graphql(gql`
  query TodoAppQuery {
    todos {
      id
      text
    }
  }
`)(TodoApp);

function TodoApp({ data: { todos, refetch } }) {
  return (
    <div>
      <button onClick={() => refetch()}>
        Refresh
      </button>
      <ul>
        {todos && todos.map(todo => (
          <li key={todo.id}>
            {todo.text}
          </li>
        ))}
      </ul>
    </div>
  );
}

Text

Server side solution

Server side solution

import { makeExecutableSchema } from 'graphql-tools';

const typeDefs = `
type Author {
  id: Int
  firstName: String
  lastName: String
  posts: [Post]
}
type Post {
  id: Int
  title: String
  text: String
  views: Int
  author: Author
}
type Query {
  author(firstName: String, lastName: String): Author
  getFortuneCookie: String
}
`;

const resolvers = {
  Query: {
    author(root, args){
      return { id: 1, firstName: 'Hello', lastName: 'World' };
    },
  },
  Author: {
    posts(author){
      return [
        { id: 1, title: 'A post', text: 'Some text', views: 2},
        { id: 2, title: 'Another post', text: 'Some other text', views: 200}
      ];
    },
  },
  Post: {
    author(post){
      return { id: 1, firstName: 'Hello', lastName: 'World' };
    },
  },
};

export default makeExecutableSchema({ typeDefs, resolvers });


// routes.js
const database = require('./database');
const schema  = require('./graphql/schema');

const graphql = new Router();
graphql.post('/graphql',graphqlKoa({ schema, context: {database} }));
graphql.get('/graphiql', graphiqlKoa({ endpointURL: '/graphql' }));

GraphiQL

An in-browser IDE for exploring GraphQL

参考资料

详解 GraphQL

By Liu Junfeng

详解 GraphQL

  • 821