GraphQL - A Query Language for APIs前端
GraphQL is a new API standard that provides a more efficient, powerful and flexible alternative to REST. It was developed and open-sourced by Facebook and is now maintained by a large community of companies and individuals from all over the world. At its core, GraphQL enables declarative data fetching where a client can specify exactly what data it needs from an API. Instead of multiple endpoints that return fixed data structures, a GraphQL server only exposes a single endpoint and responds with precisely the data a client asked for.express
这是官方关于 GraphQL 的介绍,浓缩成一句话解释: an API defines how a client can load data from a server
后端
大概能够达到这些优势:bash
① It lets the client specify exactly what data it needs. / 由前端决定须要哪些资料 ② It uses a type system to describe data. / 资料是具备严谨的型态规范 ③ It makes it easier to aggregate data from multiple sources. / 只须要单一的 API 端口架构
跟传统的 REST 的相比,能够用这张图解释:app
根据 REST 的作法,咱们依照每个 Resource 为单位(一般 depend on 资料库的表格)。也就是说,前端须要一个资源时即须要呼叫一次 API,N 个资源时须要呼叫 N 次。可能会隐性的增长 Request 与增长新资源的开发成本。 RESTFul 分为几个阶段:Request → URL Route → Handle Controller -> Multiple Endpointide
在 GraphQL 的架构中将 URL Route → Handle Controller 的动做直接压缩设计在 API 中透过 Schema 跟 Resolver 取代,将 Resources 视为是一个 Graph,仅须要透过 One Endpoint API 就能够作到对资料存取的操做。像这样使用:工具
目前应用程式的发展是很快速的,资料的定义也很难在一开始就设定得很完美,很常都是前、后端同时开发同时串接。而由于 API 的兴起,一个后端一般须要应付不少前端平台,像是网页前、后台、Android、iOS app 之类的。多个呈现画面的前端平台仰赖于一个后端与一个资料库的来源,RESTFul 实际上是一个相对温馨的被动解法。反正后端就是开好在那边,请你们依照文件各取所需就好。可是随着开发时程拉长、规模变大以后,会发现不一样的前端可能会有不一样的要求,此时后端可能就须要一个每一个来源进行调整或是吵架 (?)测试
GraphQL 能够优雅地处理这个问题,大概有如下几点好处:fetch
① No more Over and Less - Overfetching and Underfetching / 前端须要什么本身决定,能够避免 Response 太肥或不足的问题 ② Benefits of a Schema & Type System / 资料类型的定义可让达到初步的验证效果 ③ Rich open-source ecosystem and an amazing community / 开源的社群资料不少,也有许多有趣的配套解法能够混搭
前面有提到, GraphQL 将资源视为一个 Graph,这边的资源能够想成是资料库中的每一种资料。
GraphQL 由几个部分所组成:
=> API contain Schema contain Queries contain TypeDefs and Resolvers
资料能够定义成下面这样子,Query 跟 Mutation 做为 Graph 的 Root,其中在包含 Resource 的组合:
type Query {
person: person!
persons: [person]
}
type Mutation {
create(name: String!, age: Int!): String
}
type Person {
name: String!
age: Int!
}
复制代码
每一种资料均可以设置属性与类型,能够看成初步的验证!
能够想成一个 Function,负责解析怎样的需求该对应怎样的操做。
Queries 分为 Query、Mutation 两种,定义前端的 API 要如何跟后端沟通。
query {
Person(last: 1) {
name
},
Persons {
id
name
}
}
# Return:
# {
# "data": {
# "Person": {
# name: "Sarah"
# },
# "Persons": [{
# "id": 1, "name": "Johnny"
# }, {
# "id": 2, "name": "Sarah"
# }]
# }
# }
复制代码
mutation {
create(name: "Bob", age: 36) {
id
}
}
# Return:
# {
# "data": {
# "create": {
# "id": 3
# }
# }
# }
复制代码
Schema 定义 API 的抽象层,将最上层的 Query Root 往下视为一个 API Graph。
简单总结以下:
目前常见的 GraphQL API Server 大概有如下几种作法与分别来自不一样的套件:
graphql
graphql
graphql-tools
apollo-server
graphql-yoga
这边都是以 Node + Express 为范例,也先将资料来源假设是静态变数,实务应用的时候能够把 Resolver 改成直接存取资料库的 Function。
// 1. 先定义资料来源 & 操做资料的 resolvers
let users = [
{ id: 0, name: 'Tom', sex: 0, },
{ id: 1, name: 'Bob', sex: 0, },
{ id: 2, name: 'Alick', sex: 1, },
];
const userResolver = ({id}) => users.filter(u => u.id == id)[0];
const usersResolver = () => users;
// 2. 利用 GraphQLObjectType 定义 data 的 TypeDefs
const userType = new graphql.GraphQLObjectType({
name: 'user',
fields: {
id: { type: graphql.GraphQLInt },
name: { type: graphql.GraphQLString },
sex: { type: graphql.GraphQLInt },
}
})
const usersType = new graphql.GraphQLList(userType)
// 3. 再利用 GraphQLObjectType 定义 Query 的 TypeDefs 且将 resolvers 定义在其中
const queryType = new graphql.GraphQLObjectType({
name: 'Query',
fields: {
user: {
type: userType,
resolve: (_, args) => userResolver(args),
args: {
id: { type: graphql.GraphQLInt }
},
},
users: {
type: usersType,
resolve: () => usersResolver()
}
}
})
// 4. 利用 GraphQLSchema 将 Query 封装成 Schema
const schema = new graphql.GraphQLSchema({
query: queryType
});
// 5. 利用 graphqlHTTP 将 Schema 封装成 API
app.use(
'/',
graphqlHTTP({
schema: schema,
graphiql: true
})
);
复制代码
// 1. 先定义资料来源 & 操做资料的 resolvers
let users = [
{ id: 0, name: 'Tom', sex: 0, },
{ id: 1, name: 'Bob', sex: 0, },
{ id: 2, name: 'Alick', sex: 1, },
];
const userResolver = ({id}) => users.filter(u => u.id == id)[0];
const usersResolver = () => users;
const resolvers = {
Query: {
user: (_, args) => userResolver(args),
users: () => usersResolver()
},
}
// 2. 利用 template string 定义 data 跟 Query 的 TypeDefs
const typeDefs = `
type Query {
user(id: Int!): User
users: [User]
},
type User {
id: Int
name: String
sex: Int
}
`;
// 3. 利用 buildSchema 将 Query 封装成 Schema
const schema = graphql.buildSchema(typeDefs);
// 4. 利用 graphqlHTTP + rootValue 将 Query 与 resolver 串接且封装成 API
app.use(
'/',
graphqlHTTP({
schema: schema,
rootValue: {
user: userResolver,
users: usersResolver
},
graphiql: true
})
);
复制代码
// 1. 先定义资料来源 & 操做资料的 resolvers
let users = [
{ id: 0, name: 'Tom', sex: 0, },
{ id: 1, name: 'Bob', sex: 0, },
{ id: 2, name: 'Alick', sex: 1, },
];
const userResolver = ({id}) => users.filter(u => u.id == id)[0];
const usersResolver = () => users;
const resolvers = {
Query: {
user: (_, args) => getCourse(args),
users: () => getCourses()
},
}
// 2. 利用 template string 定义 data 跟 Query 的 TypeDefs
const typeDefs = `
type Query {
user(id: Int!): User
users: [User]
},
type User {
id: Int
name: String
sex: Int
}
`;
// 3. 利用 makeExecutableSchema 将 Query 跟 resolver 封装成 Schema
const schema = makeExecutableSchema({
typeDefs,
resolvers,
});
// 4. 利用 graphqlHTTP 将 Schema 封装成 API
app.use(
'/',
graphqlHTTP({
schema: schema,
graphiql: true
})
);
复制代码
// 1. 先定义资料来源 & 操做资料的 resolvers
let users = [
{ id: 0, name: 'Tom', sex: 0, },
{ id: 1, name: 'Bob', sex: 0, },
{ id: 2, name: 'Alick', sex: 1, },
];
const userResolver = ({id}) => users.filter(u => u.id == id)[0];
const usersResolver = () => users;
const resolvers = {
Query: {
course: (_, args) => getCourse(args),
courses: () => getCourses()
},
}
// 2. 利用 template string 定义 data 跟 Query 的 TypeDefs
const typeDefs =gql`
type Query {
user(id: Int!): User
users: [User]
},
type User {
id: Int
name: String
sex: Int
}
`;
// 3. 利用 ApolloServer 将 Query 跟 resolver 封装成 Schema
const server = new ApolloServer({
typeDefs,
resolvers,
});
// 4. 利用 applyMiddleware 将 server 做为 express 的 Middleware API
const app = express();
server.applyMiddleware({ app });
复制代码
// 1. 先定义资料来源 & 操做资料的 resolvers
let users = [
{ id: 0, name: 'Tom', sex: 0, },
{ id: 1, name: 'Bob', sex: 0, },
{ id: 2, name: 'Alick', sex: 1, },
];
const userResolver = ({id}) => users.filter(u => u.id == id)[0];
const usersResolver = () => users;
const resolvers = {
Query: {
course: (_, args) => getCourse(args),
courses: () => getCourses()
},
}
// 2. 利用 template string 定义 data 跟 Query 的 TypeDefs
const typeDefs = `
type Query {
user(id: Int!): User
users: [User]
},
type User {
id: Int
name: String
sex: Int
}
`;
// 3. 利用 makeExecutableSchema 将 Query 跟 resolver 封装成 Schema API
const server = new GraphQLServer({
typeDefs,
resolvers,
})
server.start()
复制代码
官方将常见的使用案例分为三种,也提出三种能够导入的方式:
① GraphQL server with a connected database ② GraphQL layer that integrates existing systems ③ Hybrid approach with connected database and integration of existing system
GraphQL 也能够分为前端与后端:
① A GraphQL server that serves your API. => 套件有: Apollo Client、Relay ② A GraphQL client that connects to your endpoint. => 套件有: express-graphql、 apollo-server、 graphql-yoga
除了先后端以外,也能够加一些额外的工具:
GraphQL 的第一步能够这样开始:
能够搭配 投影片 阅读:)
本著做由Chang Wei-Yaun (v123582)制做, 以创用CC 姓名标示-相同方式分享 3.0 Unported受权条款释出。