點燈坊

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

使用 GraphQL Query 回傳單一值

Sam Xiao's Avatar 2019-11-19

GraphQL Query 一般來說都會回傳 Array,但若要回傳 sum()count() 結果則為單一 Scalar 值,這在 Apollo GraphQL 與 Knex 該如何實現呢 ?

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
Wink-fp 0.1.30

Apollo GraphQL

src/index.js

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

let mySQL = knex({
  client: 'mysql',
  connection: {
    host: 'localhost',
    port: 3306,
    user: 'root',
    password: 'demo',
    database: 'Bookshelf'
  }
})

let typeDefs = gql`
  type Query {
    totalPrice: Int
  }
`

let totalPrice = () => mySQL('books')
  .sum({ sum: 'price' })
  .then(x => x[0].sum)

let resolvers = {
  Query: {
    totalPrice
  }
}

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

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

15 行

let typeDefs = gql`
  type Query {
    totalPrice: Int
  }
`

定義 totalPrice query,僅回傳 Int

21 行

let totalPrice = () => mySQL('books')
  .sum({ sum: 'price' })
  .then(x => x[0].sum)

Knex 提供 sum() ,相當於 SQL 的 sum(),若要提供 alias,則傳入 object,key 為 alias 名稱,value 為 sum() 所要計算的 column。

sum() 回傳為 array,因此必須在 then() 中取得第一筆資料後,再取其 sum column。

嚴格來說,這算 Knex 問題,如 Sequelize 的 sum() 會直接回傳 scalar,但 Knex 回傳 array,只能在 then() 中自己處理

Ramda

src/index.js

import { ApolloServer, gql } from 'apollo-server'
import knex from 'knex'
import { compose, head, pluck } from 'ramda'

let mySQL = knex({
  client: 'mysql',
  connection: {
    host: 'localhost',
    port: 3306,
    user: 'root',
    password: 'demo',
    database: 'Bookshelf'
  }
})

let typeDefs = gql`
  type Query {
    totalPrice: Int
  }
`
let totalPrice = () => mySQL('books')
  .sum({ sum: 'price' })
  .then(compose(head, pluck('sum')))

let resolvers = {
  Query: {
    totalPrice
  }
}

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

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

21 行

let totalPrice = () => mySQL('books')
  .sum({ sum: 'price' })
  .then(compose(head, pluck('sum')))

then() 的 callback 亦可使用 Ramda 改寫,組合 pluck()head()

Point-free

src/index.js

import { ApolloServer, gql } from 'apollo-server'
import knex from 'knex'
import { compose, head, pluck, useWith, identity } from 'ramda'

let mySQL = knex({
  client: 'mysql',
  connection: {
    host: 'localhost',
    port: 3306,
    user: 'root',
    password: 'demo',
    database: 'Bookshelf'
  }
})

let typeDefs = gql`
  type Query {
    totalPrice: Int
  }
`

let tug = useWith(
  compose(head, pluck), [identity, identity]
)

let totalPrice = () => mySQL('books')
  .sum({ sum: 'price' })
  .then(tug('sum'))

let resolvers = {
  Query: {
    totalPrice
  }
}

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

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

22 行

let tug = useWith(
  compose(head, pluck), [identity, identity]
)

let totalPrice = () => mySQL('books')
  .sum({ sum: 'price' })
  .then(tug('sum'))

由於 Knex 特性,如 sum()count()avg() … 等都會遇到此需求,可自行組合出 tug(),將來只要傳入 alias 即可。

Wink-fp

server/src/index.js

import { ApolloServer, gql } from 'apollo-server'
import knex from 'knex'
import { tug } from 'wink-fp'

let mySQL = knex({
  client: 'mysql',
  connection: {
    host: 'localhost',
    port: 3306,
    user: 'root',
    password: 'demo',
    database: 'Bookshelf'
  }
})

let typeDefs = gql`
  type Query {
    totalPrice: Int
  }
`

let totalPrice = () => mySQL('books')
  .sum({ sum: 'price' })
  .then(tug('sum'))

let resolvers = {
  Query: {
    totalPrice
  }
}

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

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

22 行

let totalPrice = () => mySQL('books')
  .sum({ sum: 'price' })
  .then(tug('sum'))

Wink-fp 已經提供 tug(),可直接使用。

tug()
String -> [{k: v}] -> v
回傳 array 第一筆資料指定 key 的 value

GraphQL Playground

query {
  totalPrice
}

totalPrice query 回傳為單一值 600

scalar000

Conclusion

  • 由於 Knex 所有回傳都是 array,僅管 sum() 回傳為單一值,依然躲在 array 下,必須自行在 then() 中處理
  • then() 的 callback 亦可使用 Ramda 或 Wink-fp 的 function 使其 point-free

Reference

Ramda, compose()
Ramda, head()
Ramda, pluck()