GraphQL 既是一种用于 API 的查询语言,也是一个满足你数据查询的运行时。
是一种接口开发标准,支持常见的服务端开发语言。例如:java、php、Python、Node.js......
官网:https://graphql.cn/
GraphQL客户端与服务端交互(接口调用)
实现步骤
总结
只有一个请求地址
按需进行查询
有特定的查询语法规则
apollo-server 是一个实现了GraphQL 规范的框架,可以基于它快速开发基于GraphQL的服务端接口,并且方便客户端进行接口调用。
// 导入相关的包
const express = require('express');
const { ApolloServer, gql } = require('apollo-server-express');
// 定义类型
const typeDefs = gql`
type Query {
hello: String
}
`;
// 解析字段数据
const resolvers = {
Query: {
hello: () => 'Hello world!',
},
};
// 整合ApolloServer和express
const server = new ApolloServer({ typeDefs, resolvers });
const app = express();
server.applyMiddleware({ app });
// 监听端口
app.listen({ port: 4000 }, () =>
console.log(`running at http://localhost:4000${server.graphqlPath}`)
);
// 查询
query {
hello
}
// 响应结果
{
"data": {
"hello": "world"
}
}
GraphQL提供一套完善的类型系统,可以约束服务端可以提供哪些数据类型供客户端查询使用
# 通过type关键字定义类型,type之后是类型名称(自定义名称)
type Course {
cname: String
score: Float
}
type Student {
name: String
scores: [Course]
}
对象类型上的每一个字段都可能有零个或者多个参数,可以按照条件查询数据
type Query {
hello: String
# 定义字段参数,可以有默认值
stu(n: Int = 12): Student
}
# 获取参数(通过resolver函数的第二个参数获取客户端传递的参数数据)
const resolvers = {
Query: {
stu: (obj, args) => {
console.log(args)
}
},
};
{
# 客户端字段参数传递
stu(n: 13) {
name
age
}
}
内置类型中有两个特殊的类型:Query和Mutation
每一个GraphQL服务有右一个Query类型,也可能有一个Mutation类型,这两个类型本质上也是对象类型,只不过有一点区别:它们作为客户端访问的入口
标量类型用于表示基本的字段数据,表示查询数据的叶子节点
枚举类型是一种特殊的标量,它限制在一个特殊的可选值集合内。
enum Favour {
SWIMING
CODING
SINGING
}
上述定义表示只能获取三种值之一,其他类型的值是不可以的
type Student {
name: String!
scores: [Score!]!
}
myField: [String!] 表示数组本身可以为空,但是其不能有任何空值成员
myField: [String]! 表示数组本身不能为空,但是其可以包含空值成员
参数也可以是复杂类型,主要用于变更Mutation场景(需要客户端传递输入类型)
# 定义输入类型
input UserInput {
uname: String
pwd: String
}
定义Mutation,可以用于接收客户端传递的input类型数据
# 接收客户端传递的input类型参数
type Mutation {
addUserByInput(userInput: UserInput): User
}
# 处理客户端传递的input参数
const resolvers = {
Mutation: {
addUserByInput: (obj, args) => {
// args可以获取客户端传递的input类型数据
return {
id: 123,
uname: args.uname,
pwd: args.pwd
}
}
}
}
# 客户端查询操作
mutation {
addUserByInput(userInput: {
uname: "lisi",
pwd: "123"
}) {
id
username
}
}
resolves用于给类型字段提供实际数据
args
可以提供在 GraphQL 查询中传入的参数。context
会被提供给所有解析器,并且持有重要的上下文信息比如当前登入的用户或者数据库访问对象。info
一个保存与当前查询相关的字段特定信息以及 schema 详细信息的值// 服务端数据解析
const resolvers = {
Query: {
hello: () => "Hello World"
},
Mutation: {
addInfo: (parent, args, context, info) => {
// parent表示当前字段的父级对象
console.log(parent);
// args表示客户端传递过来的参数
console.log(args);
// context可以用于操作数据源
console.log(context);
},
}
};
// 类型定义
type Student {
sname: String
age: Int
}
// 数据解析
const resolvers = {
Student: {
// 提供字段的resolver函数,如果不提供,会默认生成
sname: (parent) => {
return parent.sname
}
},
stu: (parent) => {
return {
sname: 'lisi',
age: 12
}
}
};
通过context参数更加方便的对接数据源(数据库、文件、第三方接口),包括异步操作
// context可以用于获取数据源对象
const context = () => {
return {
db: 'dbObj'
}
}
const server = new ApolloServer({ typeDefs, resolvers, context });
// 在resolver函数中可以通过context对象操作数据源
const resolvers = {
Query: {
hello: (parent, args, context) => {
// 通过context可以获取数据源操作对象
let ret = context.db;
}
}
};
// db.js代码:从文件中读取数据,本质上与从数据库读取数据类似,都可以是异步操作
const path = require('path');
const fs = require('fs');
module.exports.getData = () => {
const dataPath = path.join(__dirname, 'data.json');
return new Promise((resolve, reject) => {
fs.readFile(dataPath, (err, data) => {
if(err) {
reject('error');
}
resolve(data);
})
})
}
// 从文件中读取数据作为数据源,db.getData()返回Promise实例对象
const db = require('./db.js');
const context = ({req}) => {
return { db: db }
}
const resolvers = {
Query: {
hello: async (obj, args, context, info) => {
let ret = await context.db.getData();
return ret;
}
}
}
const server = new ApolloServer({ typeDefs, resolvers, context });
按需获取需要的数据
// 查询hero对象的name属性
{
hero {
name
}
}
// 查结果
{
"data": {
"hero": {
"name": "lisi"
}
}
}
有多个操作时,操作名称是必须的,为了调试的方便,推荐添加操作名
规则:操作类型 操作名称(操作名称自定义)
操作类型主要有如下两种
# 查询名称
query helloInfo {
hello
}
# 变更名称
mutation addUser {
addUser(userInput: {
uname: "lisi",
pwd: "123"
}) {
id
username
}
}
有时候需要根据特定的条件查询数据,此时可以使用查询参数
// 查询id是1,姓名是lisi的学生信息
query param {
stu(id: 1, sname: "lisi") {
sname
age
gender
}
}
// 查询结果
{
"data": {
"stu": {
"sname": "lisi",
"age": 12,
"gender": true
}
}
}
有时字段的参数需要动态提供,而不是固定的值,此时可以使用变量,其实类似于函数中的形参
# $id: Int (变量名称: 变量类型)
query param($id: Int, $num: Float) {
stu(id: $id) {
id
sname
scores(num: $num) {
cname
score
}
}
}
// 分离的变量字典
{
"id": 1,
"num": 99
}
有时候查询的字段数量不是固定的,此时可以通过指令的方式进行控制
@include(if: Boolean)
仅在参数为 true
时,包含此字段。@skip(if: Boolean)
如果参数为 true
,跳过此字段。query param($id: Int, $gender: Boolean!) {
stu(id: $id) {
id
sname
gender @include(if: $gender)
}
}
query param($id: Int, $gender: Boolean!) {
stu(id: $id) {
id
sname
gender @skip(if: $gender)
}
}
有时需要通过不同参数值查询相同字段信息,比如查询学生的数学和英语成绩
query alias {
stu {
id
sname
math: scores(cname: "数学") {
cname
score
},
english: scores(cname: "英语") {
cname
score
}
}
}
// 上面的查询结果如下
{
"data": {
"stu": {
"id": 1,
"sname": "张三",
"math": [
{
"cname": "数学",
"score": 98.5
}
],
"english": [
{
"cname": "英语",
"score": 99.5
}
]
}
}
}
改变服务器数据需要用到【变更】(mutation)操作
# 通过具体值方式传递参数
mutation addUserByParam {
addUserByParam(uname: "lisi", pwd:"abc") {
id
uname
pwd
}
}
# 通过变量方式传递参数
mutation addUserByInput($userInput: UserInput) {
addUserByInput(userInput: $userInput) {
id
uname
pwd
}
}
# 参数传递
{
"userInput": {
"uname": "lisi",
"pwd": "123"
}
}
# 核心类型定义
type Data {
weather: Weather
link: [FriendlyLink]
list: [Comment]
}
type Query {
info: Data
}
type Mutation {
createComment(input: CommentInput): Comment
}
info: async (parent, args, context) => {
// 调用数据源对象获取数据
return {
list: list,
link: link,
weather: weather
}
}
import Vue from 'vue'
import VueApollo from 'vue-apollo'
Vue.use(VueApollo)
import ApolloClient from 'apollo-boost'
const apolloClient = new ApolloClient({
uri: 'http://localhost:4000/graphql'
})
const apolloProvider = new VueApollo({
defaultClient: apolloClient,
})
new Vue({
apolloProvider,
render: h => h(App),
}).$mount('#app')
// vue参数中配置查询逻辑
apollo: {
info: {
query: gql`
query list {
info {
list {
username
content
date
}
}
}
`
}
},
data () {
return {
info: []
}
}
this.$apollo.mutate({
mutation: gql`
mutation createComment($commentInput: CommentInput) {
createComment(commentInput: $commentInput) {
username
content
}
}
`,
variables: {
commentInput: {
username: username,
content: content
}
},
refetchQueries:[{query: QueryListTag}]
})