點燈坊

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

使用 Apollo GraphQL 連接 Redis

Sam Xiao's Avatar 2019-11-26

Node 只要安裝 Redis Client,就能存取 Redis,但其提供為 Callback-based API,要轉成 Promised-based API 後才方便在 Resolver 使用。

Version

macOS Catalina 10.15.1
Docker Desktop 2.1.0.5 (40693)
WebStorm 2019.3
Node 13.2.0
Apollo GraphQL 2.9.6
Redis 5.0.6
Redis Client 2.8.0

Docker Compose

redis/docker-compose.yml

version: "3"

services:
  redis:
    image: redis:latest
    container_name: MyRedis
    volumes:
      - ${REDIS_HOST_DIR}:/data
    ports:
      - ${REDIS_PORT}:6379
    restart: always

第 5 行

image: redis:latest

使用 Redis 最新版 image。

第 6 行

container_name: MyRedis

使用 container_name 定義 container 名稱為 MyRedis

第 7 行

volumes:
  - ${REDIS_HOST_DIR}:/data

設定 Redis database 實際儲存在 macOS 路徑,避免 container 結束後 database 也跟著刪除。

第 9 行

ports:
  - ${REDIS_PORT}:6379

設定 Redis 對外使用 host os 的 port。

.env

REDIS_HOST_DIR=.redis
REDIS_PORT=6379

建立 .env 設定 docker-compose.yml 所需環境變數。

  • REDIS_HOST_DIR:設定 Redis database 要存到 Host OS 位置
  • REDIS_PORT:設定 Redis 對應到 Host OS 的 port

Install Package

$ yarn add apollo-server graphql
$ yarn add redis
  • Apollo GraphQL 安裝 apollo-servergrapql
  • Redis Client 安裝 redis

Apollo GraphQL

src/index.js

import { ApolloServer, gql } from 'apollo-server'
import { createClient } from 'redis'
import { promisify } from 'util'

let redis = createClient({
  host: 'localhost',
  port: 6379
})

let getAsync = promisify(redis.get).bind(redis)

let typeDefs = gql`
  type Query {
    hello: String
  }
`

let hello = async () => await getAsync('hello')

let resolvers = {
  Query: {
    hello
  }
}

new ApolloServer({ typeDefs, resolvers })
  .listen()
  .then(({ url }) => `GraphQL Server ready at ${ url }`)
  .then(console.log)

第 1 行

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

apollo-server 引入 AplloServergql

第 2 行

import { createClient } from 'redis'

redis 引入 createClient 建立 Redis client object。

第 3 行

import { promisify } from 'util'

util 引入 promisify 將 Redis client 的 api 從 callback-based 轉成 promised-based。

第 5 行

let redis = createClient({
  host: 'localhost',
  port: 6379
})

使用 createClient() 建立 Redis client,傳入 option object 指定 hostport

第 10 行

let getAsync = promisify(redis.get).bind(redis)

使用 promisify()redis.get() 由 callback-based 轉成 promised-based,但因 promisify() 回傳為 standalon function,要使用 bind()clientthis 重新綁定回去。

12 行

let typeDefs = gql`
  type Query {
    hello: String
  }
`

在 GraphQL schema 宣告 hello query。

19 行

let resolvers = {
  Query: {
    hello
  }
}

resolvers 宣告 hello query。

17 行

let hello = async () => await getAsync('hello')

由於 getAysnc() 回傳 promise,因此 hello() 要宣告成 async function 搭配 await

25 行

new ApolloServer({ typeDefs, resolvers })
  .listen()
  .then(({ url }) => `GraphQL Server ready at ${ url }`)
  .then(console.log)

建立 Apollo GraphQL 並使用 listen() 啟動之。

GraphQL Playground

redis000

Conclusion

  • Apollo client 原本提供的 get() 為 callback-based API,搭配 GraphQL 的 resolver 較不方便,透過 promisify() 轉成 promised-based API 後,在 resolver 只要使用 async await 就可輕鬆使用

Sample Code

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