1. 程式人生 > >試一試 GraphQL

試一試 GraphQL

## GraphQL 簡介 ![](https://gitee.com/guangzan/imagehost/raw/master/markdown/ql9.png) 一種用於 API 的查詢語言。 > GraphQL 既是一種用於 API 的查詢語言也是一個滿足你資料查詢的執行時。 GraphQL 對你的 API 中的資料提供了一套易於理解的完整描述,使得客戶端能夠準確地獲得它需要的資料,而且沒有任何冗餘,也讓 API 更容易地隨著時間推移而演進,還能用於構建強大的開發者工具。 對比 restful api: - restful —個介面只能返回一個資源, graphql 一次可以獲取多個資源。 - restful 用不同的 url 來區分資源, graphql 用型別區分資源。 ## 安裝 使用 JavaScript 語言,express node.js 框架開始。 ``` npm install express express-graphql graphql ``` 建立 server.js 並 使用命令 `node server.js` 執行 demo。 ```javascript const express = require('express'); const app = express(); const { graphqlHTTP } = require('express-graphql'); const { buildSchema } = require('graphql'); const schema = buildSchema(` type Query { hello: String } `); const root = { hello: () => 'Hello world!' }; app.use('/graphql', graphqlHTTP({ schema: schema, rootValue: root, graphiql: true, })); app.listen(4000, () => console.log('Now browse to localhost:4000/graphql')); ``` ![](https://gitee.com/guangzan/imagehost/raw/master/markdown/ql1.png) ## 引數型別 - 基本型別: **String, Int, Float, Boolean, ID**。 - [型別]代表陣列,例如:[Int]代表整型陣列。 ## 引數傳遞 - 和 JavaScript 傳遞引數一樣,小括號內定義形參,但是注意 **:** 引數需要定義型別。 - **!** 歎號代表引數不能為空。 ```javascript type Query { rollDice (numDice: Int!, numSide: Int): [Int] } ``` ## 自定義引數型別 GraphQL 允許使用者自定義引數型別,通常用來描述要獲取的資源的屬性。 ```javascript type Account { name: String age: Int sex: String department: String salary: (city: String): Int } type Query { account (name: string): Account } ``` server.js ```javascript const schema = buildSchema(` type Account { name: String age: Int sex: String department: String salary(city: String): Int } type Query { hello: String getClassMates(classNo: Int!): [String] account (username: String): Account } `) const root = { hello: () => 'Hello world!', getClassMates: ({ classNo }) => { const obj = { 31: ['張三', '李四', '王五'], 61: ['張大三', '李大四', '王大五'], } return obj[classNo] }, account: ({ username }) => { const name = username const sex = 'nan' const age = 10 const department = '開發部' const salary = ({ city }) => { if (city === '北京' || city === '上海' || city === '深圳' || city === '廣州') { return 10000 } return 3000 } return { name, sex, age, department, salary, } }, } ``` ![](https://gitee.com/guangzan/imagehost/raw/master/markdown/ql2.png) ## 在客戶端訪問GraphQL介面 在 server.js 公開資料夾 供使用者訪問靜態資源。 ```javascript app.use(express.static('public')) ``` index.html ```html Document
``` > 後端的 `username` 對應 `variales` 的值中的 `username`, 和 `query` 中的 `$username`。 ![](https://gitee.com/guangzan/imagehost/raw/master/markdown/ql3.png) ## 使用 Mutations 修改資料 不能單獨使用 `Mutation`, 需結合 `Query`。 ```javascript const schema = buildSchema(` type Account { name: String age: Int sex: String department: String salary(city: String): Int } input AccountInput { name: String age: Int sex: String department: String salary: Int } type Query { accounts: [Account] } type Mutation { createAccount(input: AccountInput): Account updateAccount(id: ID!, input: AccountInput): Account } `) // 模擬資料庫 const fakeDb = {} const root = { createAccount({ input }) { // 模擬資料庫儲存 fakeDb[input.name] = input // 返回儲存結果 return fakeDb[input.name] }, updateAccount({ id, input }) { // 模擬更新資料庫 const updatedAccount = Object.assign({}, fakeDb[id], input) fakeDb[id] = updatedAccount return updatedAccount }, accounts() { let arr = [] for (const key in fakeDb) { arr.push(fakeDb[key]) } return arr }, } ``` 試著建立一個 account。 ![](https://gitee.com/guangzan/imagehost/raw/master/markdown/ql4.png) 通過 accounts,查詢到剛剛建立的 account。 ![](https://gitee.com/guangzan/imagehost/raw/master/markdown/ql5.png) 試著修改資料,我們將 age 18 改為 20,並將該 account 更改後的資訊返回。 ![](https://gitee.com/guangzan/imagehost/raw/master/markdown/ql6.png) >
這裡將 name 作為記錄的主鍵了。 ## Constructing Types 上文通過字串的形式構建 `schema`,還可以通過建構函式來構建。 以前: ```javascript const schema = buildSchema(` type Account { name: String age: Int sex: String department: String salary(city: String): Int } type Query { account (username: String): Account } `) ``` 現在 ```javascript const AccountType = new graphql.GraphQLObjectType({ name: 'Account', fields: { name: {type: graphql.GraphQLString}, age: {type: graphiql.GraphQLInt}, sex: {type: raphql.GraphQLString}, department: {type: graphql.GraphQLString}, } }) const queryType = new graphiql.GraphQLObjectType({ name: 'Query', fields: { account: { type: AccountType, args: { username: {type: graphiql.GraphQLString} }, resolve(_, {username}){ const name = username const sex = 'nan' const age = 18 const department = '開發部' return { name, age, sex, department } } } } }) const schema = new graphiql.GraphQLSchema({query: queryType}) ``` >
程式碼量提升,編輯器提示,可維護性提升,報錯資訊更精準。 ## 結合 MySql CURD 接下來需要稍作更改,拼接幾個 SQL 語句, 操作資料庫。 建立資料庫 test , 表 account,並新增幾個欄位如下: ![](https://gitee.com/guangzan/imagehost/raw/master/markdown/ql8.png) ```javascript npm i mysql -S ``` ```javascript const mysql = require('mysql') const pool = mysql.createPool({ connectionLimit: 10, host: 'localhost', user: 'root', password: '123456', database: 'test', }) const schema = buildSchema(` type Account { name: String age: Int sex: String department: String salary(city: String): Int } input AccountInput { name: String age: Int sex: String department: String salary: Int } type Query { accounts: [Account] } type Mutation { createAccount(input: AccountInput): Account updateAccount(id: ID!, input: AccountInput): Account deleteAccount(id: ID!): Boolean } `) const rootValue = { createAccount({ input }) { const data = { name: input.name, age: input.age, sex: input.sex, department: input.department, } // query 是非同步操作, 使用 promise return new Promise((resolve, reject) => { pool.query('insert into account set ?', data, err => { if (err) { console.log('出錯了' + err.message) return } resolve(data) }) }) }, deleteAccount({ id }) { return new Promise((resolve, reject) => { pool.query('delete from account where id = ?', id, err => { if (err) { console.log('出錯了' + err) reject(false) return } resolve(true) }) }) }, updateAccount({ id, input }) { const data = input return new Promise((resolve, reject) => { pool.query('update account set ? where id = ?', [data, id], err => { if (err) { console.log('出錯了' + err.message) return } resolve(data) }) }) }, accounts() { return new Promise((resolve, reject) => { pool.query('select name, age, sex, department from account', (err, res) => { if (err) { console.log('出錯了' + err) return } let arr = [] for (let i = 0; i < res.length; i++) { arr.push({ name: res[i].name, sex: res[i].sex, age: res[i].age, department: res[i].department, }) } resolve(arr) }) }) }, } ``` ## 程式碼片段 為方便操作,我將完整程式碼片段放在最後,供你試一試。
fakeDb ```javascript const express = require('express') const app = express() const { graphqlHTTP } = require('express-graphql') const { buildSchema } = require('graphql') const schema = buildSchema(` type Account { name: String age: Int sex: String department: String salary(city: String): Int } input AccountInput { name: String age: Int sex: String department: String salary: Int } type Query { hello: String getClassMates(classNo: Int!): [String] account (username: String): Account accounts: [Account] } type Mutation { createAccount(input: AccountInput): Account updateAccount(id: ID!, input: AccountInput): Account } `) const fakeDb = {} const rootValue = { hello: () => 'Hello world!', getClassMates: ({ classNo }) => { const obj = { 31: ['張三', '李四', '王五'], 61: ['張大三', '李大四', '王大五'], } return obj[classNo] }, account: ({ username }) => { const name = username const sex = 'nan' const age = 10 const department = '開發部' const salary = ({ city }) => { if (city === '北京' || city === '上海' || city === '深圳' || city === '廣州') { return 10000 } return 3000 } return { name, sex, age, department, salary, } }, createAccount({ input }) { // 模擬資料庫儲存 fakeDb[input.name] = input // 返回儲存結果 return fakeDb[input.name] }, updateAccount({ id, input }) { // 模擬更新資料庫 const updatedAccount = Object.assign({}, fakeDb[id], input) fakeDb[id] = updatedAccount return updatedAccount }, accounts() { let arr = [] for (const key in fakeDb) { arr.push(fakeDb[key]) } return arr }, } // 公開資料夾 供使用者訪問靜態資源 app.use(express.static('public')) // const middleware = (req, res, next) => { // // console.log(req.headers.cookie) // if (req.url.indexOf('/graphql') !== -1) { // res.send( // JSON.stringify({ // error: '您沒有權訪問這個介面', // }) // ) // return // } // next() // } // app.use(middleware) app.use( '/graphql', graphqlHTTP({ schema, rootValue, graphiql: true, }) ) app.listen(4000, () => console.log('Now browse to localhost:4000/graphql')) ```
MySQL ```javascript const express = require('express') const app = express() const { graphqlHTTP } = require('express-graphql') const { buildSchema } = require('graphql') const mysql = require('mysql') const { resolve } = require('path') const pool = mysql.createPool({ connectionLimit: 10, host: 'localhost', user: 'root', password: '123456', database: 'test', }) const schema = buildSchema(` type Account { name: String age: Int sex: String department: String salary(city: String): Int } input AccountInput { name: String age: Int sex: String department: String salary: Int } type Query { hello: String getClassMates(classNo: Int!): [String] account (username: String): Account accounts: [Account] } type Mutation { createAccount(input: AccountInput): Account updateAccount(id: ID!, input: AccountInput): Account deleteAccount(id: ID!): Boolean } `) const rootValue = { hello: () => 'Hello world!', getClassMates: ({ classNo }) => { const obj = { 31: ['張三', '李四', '王五'], 61: ['張大三', '李大四', '王大五'], } return obj[classNo] }, account: ({ username }) => { const name = username const sex = 'nan' const age = 10 const department = '開發部' const salary = ({ city }) => { if (city === '北京' || city === '上海' || city === '深圳' || city === '廣州') { return 10000 } return 3000 } return { name, sex, age, department, salary, } }, accounts() { return new Promise((resolve, reject) => { pool.query('select name, age, sex, department from account', (err, res) => { if (err) { console.log('出錯了' + err) return } let arr = [] for (let i = 0; i < res.length; i++) { arr.push({ name: res[i].name, sex: res[i].sex, age: res[i].age, department: res[i].department, }) } resolve(arr) }) }) }, createAccount({ input }) { const data = { name: input.name, age: input.age, sex: input.sex, department: input.department, } return new Promise((resolve, reject) => { pool.query('insert into account set ?', data, err => { if (err) { console.log('出錯了' + err.message) return } resolve(data) }) }) }, updateAccount({ id, input }) { const data = input return new Promise((resolve, reject) => { pool.query('update account set ? where id = ?', [data, id], err => { if (err) { console.log('出錯了' + err.message) return } resolve(data) }) }) }, deleteAccount({ id }) { return new Promise((resolve, reject) => { pool.query('delete from account where id = ?', id, err => { if (err) { console.log('出錯了' + err) reject(false) return } resolve(true) }) }) }, } // 公開資料夾 供使用者訪問靜態資源 app.use(express.static('public')) app.use( '/graphql', graphqlHTTP({ schema, rootValue, graphiql: true, }) ) app.listen(4000, () => console.log('Now browse to localhost:4000/graphql')) ```