GraphQL 定位為 API Query Language,本質只是 Middleware 負責 Parse GraphQL,最終還是得搭配資料庫,本文以 Apollo GraphQL + Knex + MySQL 為例。
Version
macOS Catalina 10.15.1
WebStorm 2019.2.4
MySQL 8.0.18
Node 13.1.0
Knex 0.20.2
Apollo GraphQL 2.9.6
MySQL
mysql/docker-compose.yml
version: "3"
services:
mysql:
image: mysql:latest
container_name: MySQL
volumes:
- ${MYSQL_HOST_DIR}:/var/lib/mysql
ports:
- ${MYSQL_PORT}:3306
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
command: --default-authentication-plugin=mysql_native_password
restart: always
使用 Docker 執行 MySQL,優點是每個 project 都可有自己的 MySQL 與 database,而不必如傳統所有 project 都在同一個 MySQL。
11 行
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
MySQL 8 的 authentication 從原本 mysql_native_password
改成 caching_sha2_password
,這導致 Knex 無法連上 MySQL 8 以上,特別在 command
加上 --default-authentication-plugin=mysql_native_password
,命令 MySQL 8 以 mysql_native_password
方式啟動。
mysql/.env
MYSQL_HOST_DIR=.mysql
MYSQL_PORT=3306
MYSQL_ROOT_PASSWORD=demo
設定 MySQL 環境變數。
Install Package
$ yarn add apollo-server graphql
$ yarn add knex mysql
- Apollo Server 安裝
apollo-server
與graphql
- Knex 安裝
knex
與mysql
src/index.js
import { ApolloServer, gql } from 'apollo-server'
import knex from 'knex'
let mySQL = knex({
client: 'mysql',
connection: {
host: process.env.MYSQL_HOST || 'localhost',
port: process.env.MYSQL_PORT || 3306,
user: 'root',
password: process.env.MYSQL_ROOT_PASSWORD || 'demo',
database: 'Bookshelf'
}
})
let typeDefs = gql`
type Query {
books(category: BookCategory!): [Book]
}
type Book {
title: String
category: BookCategory
}
enum BookCategory {
FP
FRP
JS
}
`
let books = (_, { category }) => mySQL('books')
.select()
.where({
'category': category
})
let resolvers = {
Query: {
books
}
}
let apolloServer = new ApolloServer({ typeDefs, resolvers })
apolloServer.listen()
.then(({ url }) => `GraphQL Server ready at ${url}`)
.then(console.log)
第 1 行
import { ApolloServer, gql } from 'apollo-server'
從 apollo-server
引入 AplloServer
與 gql
。
第 2 行
import knex from 'knex'
從 knex
引入 knex
。
第 4 行
let mySQL = knex({
client: 'mysql',
connection: {
host: process.env.MYSQL_HOST || 'localhost',
port: process.env.MYSQL_PORT || 3306,
user: 'root',
password: process.env.MYSQL_ROOT_PASSWORD || 'demo',
database: 'Bookshelf'
}
})
knex()
為 higher order function,透過傳入 configuration object,建立 mySQL()
。
Knex 支援 Postgres、MSSQL、MySQL、MariaDB、SQLite3 與 Oracle,可在 client
指定。
connection
則指定連線資訊。
15 行
let typeDefs = gql`
type Query {
books(category: BookCategory!): [Book]
}
type Book {
title: String
category: BookCategory
}
enum BookCategory {
FP
FRP
JS
}
`
使用 typeDefs
定義 GraphQL schema。
38 行
let resolvers = {
Query: {
books
}
}
在 resolvers
內宣告 books
query。
32 行
let books = (_, { category }) => mySQL('books')
.select()
.where({
'category': category
})
實作 books
query,在此調用 Knex 存取 MySQL,其中 Knex 回傳為 promise。
由於 GraphQL 是 API query language,因此在其 resolver 可使用任意 ORM 或 query builder,只是本文使用了 Knex
44 行
let apolloServer = new ApolloServer({ typeDefs, resolvers })
apolloServer.listen()
.then(({ url }) => `GraphQL Server ready at ${url}`)
.then(console.log)
啟動 Apollo GraphQL。
GraphQL Playground
Conclusion
- 與原本 Express 的 REST API 比較,GraphQL API 嚴格來說只多了 schema,resolver 使用 ORM 是本來就要寫的,因此後端要多的功並沒有想像中多
- 至於 ORM 或 query builder 可依個人喜好選擇,Knex 的寫法很精簡,可讀性亦高,設定好 connection 就可使用,不需再定義額外 model
Sample Code
完整範例可在我的 GitHub 上找到