GraphQL

Introduce

Communication

Between

Front & Back

Restful API

GET

POST

PUT

PATCH

DELETE

/members/:memberId

Problems?

{
  "id": 1,
  "name": "Stanney Yen",
  "grade": "senior"
}

GET /members/1

{
  "id": 1,
  "name": "Stanney Yen",
  "grade": "senior",
  "projects": [{
    "id": 1,
    "name": "YiGuang"
  }, {
    "id": 2,
    "name": "BaiYi"
  }]
}

GET /members/1/withProjects

{
  "id": 1,
  "name": "Stanney Yen",
  "grade": "senior",
  "projects": [{
    "id": 1,
    "name": "YiGuang",
    "repo": {
      "url": ".../Rytass/YiGuang",
      "branch": "master"
    }
  }, {
    "id": 2,
    "name": "BaiYi",
    "repo": {
      "url": ".../Rytass/BaiYi",
      "branch": "develop"
    }
  }]
}

GET /members/1/withProjectURL

GraphQL

JSON like request

Strong type

Client cache

What about

CREATE / Edit

Mutation

Structure change

Strong type checker

Client model update

Update new data with mutation request

How-to

Official Library

JS framework

Relay

Apollo Client

Relay js

Single (less) data model

Pagenation

Automatic cache

Apollo Client

Less restrict

Multi-platform (iOS / Android)

Semi-automatic cache

Redux friendly

apollo Client

Define Schema

import fs from 'fs';
import {
  GraphQLSchema,
} from 'graphql';
import {
  introspectionQuery,
  printSchema,
} from 'graphql/utilities';

/* Define queryType and mutationType */

const graphqlSchema = new GraphQLSchema({
  query: queryType,
  mutation: mutationType,
});

// Build schema to graphql structure

graphql(graphqlSchema, introspectionQuery)
  .then((result) => {
    fs.writeFileSync('schema.json', JSON.stringify(result));
  });


// Build schema to .graphql file
const graphqlString = printSchema(graphqlSchema);
fs.writeFileSync('schema.graphql', graphqlString);

query Type

import {
  GraphQLSchema,
  GraphQLObjectType,
  GraphQLList,
} from 'graphql';

/* Define memberType and advertiserType */

const queryType = new GraphQLObjectType({
  name: 'Query',
  fields: () => ({
    viewer: {
      type: new GraphQLObjectType({
        name: 'Viewer',
        fields: () => ({
          me: {
            type: memberType,
            resolve: () => Member.findMe(),
          },
          advertisers: {
            type: new GraphQLList(advertiserType),
            resolve: () => Advertiser.findAll({}),
          },
        }),
      }),
    },
  }),
});

member Type

import {
  GraphQLObjectType,
  GraphQLString,
  GraphQLInt,
} from 'graphql';
import moment from 'moment';

export default new GraphQLObjectType({
  name: 'Member',
  fields: () => ({
    id: {
      type: GraphQLInt,
    },
    name: {
      type: GraphQLString,
      resolve: member => member.name,
    },
    account: {
      type: GraphQLString,
      resolve: member => member.account,
    },
    createdAt: {
      type: GraphQLString,
      resolve: medium => moment(medium.createdAt).toISOString(),
    },
    createdDate: {
      type: GraphQLString,
      resolve: medium => moment(medium.createdAt).format('YYYY-MM-DD'),
    },
  }),
});

mutation Type

import {
  GraphQLObjectType,
  GraphQLNonNull,
  GraphQLString,
} from 'graphql';

/* Define advertiserType */

const mutationType = new GraphQLObjectType({
  name: 'Mutations',
  fields: () => ({
    createAdvertiser: {
      type: advertiserType,
      args: {
        name: {
          type: new GraphQLNonNull(GraphQLString),
        },
      },
      resolve: (value, { name }) => (
        Advertiser.createWithName(name)
      ),
    },
  }),
});

compiled schema

{
  "data": {
    "__schema": {
      "queryType": {
        "name": "Query"
      },
      "mutationType": {
        "name": "Mutations"
      },
      "subscriptionType": null,
      "types": [
        // ...some types
      ]
    }
  }
}

graphql literal

schema {
  query: Query
  mutation: Mutations
}

type Query {
  viewer: Viewer
}

type Advertiser {
  id: Int!
  name: String!
  agencies: [Agency]
  createdAt: String
  createdDate: String
}

type Viewer {
  me: Member
  advertisers: [Advertiser]
}

type Mutations {
  createAdvertiser(name: String!): Advertiser
}

Express Route

import express from 'express';
import debug from 'debug';
import schema from './graphql/schema.js';
import graphqlHTTP from 'express-graphql';

const debugServer = debug('GraphQLTester');
const app = express();

app.use('/graphql', graphqlHTTP({
  schema,
  graphiql: process.env.NODE_ENV !== 'production',
}));

app.listen(3000, (err) => {
  if (err) {
    debugServer(err);
  } else {
    debugServer('GraphQL Server Up!');
  }
});

React Binding

config

import React from 'react';
import { ApolloProvider } from 'react-apollo';

/* ...initial redux and react-router */


const networkInterface = createNetworkInterface({
  uri: '/api/graphql', // API Entrypoint
});

// Bind authorization bearer to request header
networkInterface.use([{
  applyMiddleware(req, next) {
    if (!req.options.headers) {
      req.options.headers = {};
    }

    const token = localStorage.getItem('accessToken');
    req.options.headers.authorization = token ? `Bearer ${token}` : null;
    next();
  },
}]);

const client = new ApolloClient({
  networkInterface,
});

export default (
  <ApolloProvider client={client} store={store}>
    <Router history={history}>
      // ...routes
    </Router>
  </ApolloProvider>
);

Query

import React from 'react';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';

function AdvertiserList({ data }) {
  const {
    loading,
    viewer,
  } = data;

  if (loading) return null;

  return (
    <div>
      // ...nodes
    </div>
  );
}

const queryHook = graphql(gql`
  query AdvertiserList {
    viewer {
      advertisers {
        id
        name
      }
    }
  }
`);

export default queryHook(AdvertiserList);

Fragment

import React from 'react';
import gql from 'graphql-tag';

function AdvertiserListItem({
  id,
  name,
}) {
  return (
    <tr>
      // ...nodes
    </tr>
  );
}

AdvertiserListItem.fragments = {
  advertiserItem: gql`
    fragment AdvertiserItem on Advertiser {
      id
      name
    }
  `,
};

export default AdvertiserListItem;

compose fragment

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

function AdvertiserList({ data }) {
  const {
    loading,
    viewer,
  } = data;

  if (loading) return null;

  return (
    <div>
      <table>
        <thead>
          //...titles
        </thead>
        <tbody>
          {viewer.advertisers.map(a => (
            <AdvertiserListItem name={a.name} id={a.id} key={a.id} />
          )}
        </tbody>
      </table>
    </div>
  );
}

const queryHook = graphql(gql`
  query AdvertiserList {
    viewer {
      advertisers {
        ...AdvertiserItem
      }
    }
    ${AdvertiserListItem.fragments.advertiserItem}
  }
`);

export default queryHook(AdvertiserList);

mutation

import React, {
  Component,
} from 'react';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
import AdvertiserListItem from './AdvertiserListItem.jsx';

class AdvertiserCreateForm extend Component {
  submit() {
    this.props.createAdvertiser(this.refs.form.name.value)
  }

  render() {
    return (
      <form ref="form" onSubmit={() => this.submit()}>
        //...forms
      </form>
    );
  }
}

const mutationsHook = graphql(gql`
  mutation createAdvertiser($name: String!) {
    createAdvertiser(name: $name) {
      ...AdvertiserItem
    }
  }
  ${AdvertiserListItem.fragments.advertiserItem}
`, {
  props: ({ mutate }) => ({
    createAdvertiser: name => mutate({
      variables: {
        name,
      },
    }),
  }),
});

export default mutationsHook(AdvertiserCreateForm);

GraphQL Introduce

By Chia Yu Pai

GraphQL Introduce

  • 422