點燈坊

失くすものさえない今が強くなるチャンスよ

使用 Import GraphQL 將 Schema 獨立出來

Sam Xiao's Avatar 2019-12-22

雖然 Apollo GraphQL 允許我們在 ECMAScript 內以 String 定義 GraphQL Schema,但實務上 Schema 會相當龐大,比較好的方式是拆成獨立的 *.graphql 方便維護。

Version

macOS Catalina 10.15.2
WebStorm 2019.3.1
Node 12.14.0
Apollo GraphQL 2.9.14
Babel 7.7.7
Import GraphQL 2.7.0

Apollo GraphQL

src/index.js

let { ApolloServer, gql } from 'apollo-server'

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 }
]

let typeDefs = gql`
  type Query {
    books: [Book]
  }

  type Mutation {
    addBook(book: BookInput!): Book
  }

  type Book {
    title: String
    price: Int
  }

  input BookInput {
    title: String
    price: Int
  }
`

let books = () => data

let addBook = (_, { book }) => {
  data = [...data, book]
  return book;
}

let resolvers = {
  Query: { books },
  Mutation: { addBook }
}

let apolloServer = new ApolloServer({ typeDefs, resolvers })

apolloServer.listen()
  .then(({ url }) => `GraphQL Server ready at ${ url }`)
  .then(console.log)

第 9 行

let typeDefs = gql`
  type Query {
    books: [Book]
  }

  type Book {
    title: String
    price: Int
  }
`

傳統後端會使用 string 定義 GraphQL schema,然後再透過 gql 將 string 轉成 GraphQL AST。

這種寫法有幾個缺點:

  • 實務上 schema 會很龐大,僅使用單一 string 難以維護
  • GraphQL 畢竟是獨立語言,藏在 ECMAScript 內稍嫌不妥
  • 須在 runtime 將 string 轉成 GraphQL AST,稍微影響速度

比較好的方式是藉由 Import GraphQL Babel plugin,將 GraphQL schema 獨立成 *.graphql,在 Babel 轉譯階段將 string 轉成 GraphQL AST。

Package Installation

$ yarn add babel-plugin-import-graphql --dev

使用 Yarn 安裝 babel-plugin-import-graphql,由於只是給 Babel 使用,安裝成 devDependencies 即可。

import000

Babel Configuration

.babelrc

{
  "presets": [
    "@babel/preset-env"
  ],
  "plugins": [
    "import-graphql"
  ]
}

除了原本的 presets 外,在 plugins 加掛 import-graphql

Apollo GraphQL

src/index.js

import { ApolloServer } from 'apollo-server'
import typeDefs from './schema/index.graphql'

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 }
]

let books = () => data

let addBook = (_, { book }) => {
  data = [...data, book]
  return book;
}

let resolvers = {
  Query: { books },
  Mutation: { addBook }
}

let apolloServer = new ApolloServer({ typeDefs, resolvers })

apolloServer.listen()
  .then(({ url }) => `GraphQL Server ready at ${ url }`)
  .then(console.log)

第 2 行

import typeDefs from './schema/index.graphql'

index.js 內不再定義 GraphQL schema,只需從 index.graphql import 即可。

GraphQL Schema

src/schema/index.graphql

#import 'query.graphql'
#import 'mutation.graphql'

index.graphql 負責統整所有 GraphQL schema。

babel-plugin-import-graphql 自定義 #import,可在 *.graphql 內再 import 其他 *.graphql

src/schema/query.graphql

type Query {
  books: [Book]
}

type Book {
  title: String
  price: Int
}

將 query 部分單獨定義在 query.graphql

src/schema/mutation.graphql

type Mutation {
  addBook(book: BookInput!): Book
}

input BookInput {
  title: String
  price: Int
}

將 mutation 部分單獨定義在 mutation.graphql

import001

Start Development Server

$ yarn serve

使用 yarn serve 啟動 Apollo GraphQL,babel-plugin-import-graphql 會一併將 *.graphql 轉譯成 GraphQL AST。

import002

Build Docker Image

$ yarn docker:build
$ yarn docker:up

使用 yarn docker:build 命令 Babel 重新轉譯,並打包成 Docker image。

import003

GraphQL Playground

query {
  books {
    title
    price
  }
}

可成功在執行 GraphQL query。

import004

Conclusion

  • 使用 Import GraphQL 最大優點是將 GraphQL schema 獨立成 *.graphql,適合大型專案使用,先轉譯成 GraphQL AST 算額外賺到功能
  • 至於要如何將 GraphQL schema 拆分,可視實際需求自行調整

Sample Code

完整範例可在我的 GitHub 上找到

Reference

Apollo Docs, Compiling queries with Babel
Daniel Cooke, Import GraphQL files without Webpack