試一試 GraphQL
GraphQL 簡介
一種用於 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。
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'));
參數類型
- 基本類型: String, Int, Float, Boolean, ID。
- [類型]代表數組,例如:[Int]代表整型數組。
參數傳遞
- 和 JavaScript 傳遞參數一樣,小括弧內定義形參,但是注意 : 參數需要定義類型。
- ! 嘆號代表參數不能為空。
type Query {
rollDice (numDice: Int!, numSide: Int): [Int]
}
自定義參數類型
GraphQL 允許用戶自定義參數類型,通常用來描述要獲取的資源的屬性。
type Account {
name: String
age: Int
sex: String
department: String
salary: (city: String): Int
}
type Query {
account (name: string): Account
}
server.js
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,
}
},
}
在客戶端訪問GraphQL介面
在 server.js 公開文件夾 供用戶訪問靜態資源。
app.use(express.static('public'))
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button onclick="getData()">獲取數據</button>
<script>
function getData() {
const query = `
query Account ($username: String!) {
account(username: $username) {
name
age
sex
salary(city: "北京")
}
}`
const variables = { username: '李大四' }
fetch('/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
query,
variables
})
})
.then(res => res.json)
.then(json => {
console.log(json)
})
}
</script>
</body>
</html>
後端的
username
對應variales
的值中的username
, 和query
中的$username
。
使用 Mutations 修改數據
不能單獨使用 Mutation
, 需結合 Query
。
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。
通過 accounts,查詢到剛剛創建的 account。
試著修改數據,我們將 age 18 改為 20,並將該 account 更改後的資訊返回。
這裡將 name 作為記錄的主鍵了。
Constructing Types
上文通過字元串的形式構建 schema
,還可以通過構造函數來構建。
以前:
const schema = buildSchema(`
type Account {
name: String
age: Int
sex: String
department: String
salary(city: String): Int
}
type Query {
account (username: String): Account
}
`)
現在
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,並添加幾個欄位如下:
npm i mysql -S
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
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
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'))