Apollo GraphQL
Apollo 是一个开源的 GraphQL 开发平台, 提供了符合 GraphQL 规范的服务端和客户端实现。使用 Apollo 可以帮助我们更方便快捷的开发使用 GraphQL。
- 官网:https://www.apollographql.com/
- GitHub 相关开源仓库:https://github.com/apollographql
基本用法
1、准备
# 创建项目目录
mkdir graphql-server-example
cd graphql-server-example
# 初始化 package.json 文件
npm init -y
# 安装依赖
npm install apollo-server graphql
# 创建 index.js
touch index.js
2、index.js
const { ApolloServer, gql } = require('apollo-server')
// 1. 定义 schema
const typeDefs = gql`
type Book {
title: String
author: String
}
type Query {
books: [Book]
}
`
const books = [
{
title: 'The Awakening',
author: 'Kate Chopin'
},
{
title: 'City of Glass',
author: 'Paul Auster'
}
]
// 2. 定义 resolver
const resolvers = {
// 所有的 Query 都走这里
Query: {
books: () => books
}
}
const server = new ApolloServer({ typeDefs, resolvers })
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`)
})
与 Node.js 中间件集成
Apollo Server 可以轻松地与几个流行的 Node.js 中间件库集成。要集成,请首先从下表中安装适当的软件包,而不是核心 apollo-server 软件包。
兼容的软件包
应用中间件
与中间件集成时,首先像往常一样初始化 Apollo Server,然后调用 applyMiddleware。
这是为 Hello 服务的基本 Express 示例。从 /graphql 以外的每个路径访问,该路径与 Apollo Server 一起提供GraphQL端点:
const express = require('express');
const { ApolloServer } = require('apollo-server-express');
const { typeDefs, resolvers } = require('./schema');
const app = express();
const server = new ApolloServer({
typeDefs,
resolvers,
});
server.applyMiddleware({ app });
app.use((req, res) => {
res.status(200);
res.send('Hello!');
res.end();
});
app.listen({ port: 4000 }, () =>
console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`)
)
解析器
基本用法
schema:
type Query {
numberSix: Int! # Should always return the number 6 when queried
numberSeven: Int! # Should always return 7
}
resolver:
const resolvers = {
Query: {
numberSix() {
return 6;
},
numberSeven() {
return 7;
}
}
};
处理参数
schema:
type User {
id: ID!
name: String
}
type Query {
user(id: ID!): User
}
resolver:
const users = [
{
id: '1',
name: 'Elizabeth Bennet'
},
{
id: '2',
name: 'Fitzwilliam Darcy'
}
];
const resolvers = {
Query: {
user(parent, args, context, info) {
return users.find(user => user.id === args.id);
}
}
}
解析器链
schema:
# A library has a branch and books
type Library {
branch: String!
books: [Book!]
}
# A book has a title and author
type Book {
title: String!
author: Author!
}
# An author has a name
type Author {
name: String!
}
type Query {
libraries: [Library]
}
有效的查询语法:
query GetBooksByLibrary {
libraries {
books {
author {
name
}
}
}
}
此查询的结果解析器链与查询本身的层次结构匹配:
resolver 处理如下:
const { ApolloServer, gql } = require('apollo-server');
const libraries = [
{
branch: 'downtown'
},
{
branch: 'riverside'
},
];
const books = [
{
title: 'The Awakening',
author: 'Kate Chopin',
branch: 'riverside'
},
{
title: 'City of Glass',
author: 'Paul Auster',
branch: 'downtown'
},
];
// Schema definition
const typeDefs = gql`
# A library has a branch and books
type Library {
branch: String!
books: [Book!]
}
# A book has a title and author
type Book {
title: String!
author: Author!
}
# An author has a name
type Author {
name: String!
}
# Queries can fetch a list of libraries
type Query {
libraries: [Library]
}
`;
// Resolver map
const resolvers = {
Query: {
libraries() {
// Return our hardcoded array of libraries
return libraries;
}
},
Library: {
books(parent) {
// Filter the hardcoded array of books to only include
// books that are located at the correct branch
return books.filter(book => book.branch === parent.branch);
}
},
Book: {
// The parent resolver (Library.books) returns an object with the
// author's name in the "author" field. Return a JSON object containing
// the name, because this field expects an object.
author(parent) {
return {
name: parent.author
};
}
}
// Because Book.author returns an object with a "name" field,
// Apollo Server's default resolver for Author.name will work.
// We don't need to define one.
};
// Pass schema definition and resolvers to the
// ApolloServer constructor
const server = new ApolloServer({ typeDefs, resolvers });
// Launch the server
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
如果现在我们更新查询以同时询问每本书的标题:
query GetBooksByLibrary {
libraries {
books {
title
author {
name
}
}
}
}
解析器参数
context 参数
context 参数对于传递任何解析器可能需要的东西很有用,例如身份验证范围,数据库连接和自定义获取函数。如果您正在使用数据加载器跨解析器批处理请求,则也可以将它们附加到上下文。
解析器决不应破坏性地修改上下文参数。这样可以确保所有解析程序的一致性,并防止意外错误。
要为您的解析器提供上下文,请向 ApolloServer 构造函数添加上下文初始化函数。每个请求都会调用此函数,因此您可以根据请求的详细信息(例如HTTP标头)设置上下文。
// Constructor
const server = new ApolloServer({
typeDefs,
resolvers,
// 任何 GraphQL 请求都会经过这里
// 该函数接收一个参数:Request 请求对象
context: (req) => ({// 返回对象,自定义数据,后续的每个 resolver 都可以直接获取
authScope: getScope(req.headers.authorization)
})
}));
// Example resolver
(parent, args, context, info) => {
if(context.authScope !== ADMIN) throw new AuthenticationError('not admin');
// Proceed
}
上下文初始化可以是异步的,从而可以完成数据库连接和其他操作:
context: async () => ({
db: await client.connect(),
})
// Resolver
(parent, args, context, info) => {
return context.db.query('SELECT * FROM table_name');
}
返回值
Apollo Server根据其类型对解析器函数的返回值进行不同的处理:
连接到数据源
连接到数据库
- SQLDataSource from datasource-sql
- MongoDataSource from apollo-datasource-mongodb
示例代码
index.js
const express = require('express')
const { ApolloServer, gql } = require('apollo-server-express')
const { User } = require('./models/')
const Users = require('./data-sources/user')
// 1. 定义 schema
const typeDefs = gql`
type User {
_id: ID!,
name: String!,
age: Int
}
type Query {
users: [User!]
user(id: ID!): User
}
`
// 2. 定义 resolver
const resolvers = {
// 所有的 Query 都走这里
Query: {
async users (parent, args, { dataSources }) {
const users = await dataSources.users.getUsers()
return users
},
async user (parent, { id }, { dataSources }) {
const user = await dataSources.users.getUser(id)
return user
}
// async users () {
// const users = await User.find()
// return users
// },
// async user (parent, { id }) {
// const user = await User.findById(id)
// return user
// }
}
}
const app = express()
const server = new ApolloServer({
typeDefs,
resolvers,
// 任何 GraphQL 请求都会经过这里
// 该函数接收一个参数:Request 请求对象
context (req) {
return { // 返回对象,自定义数据,后续的每个 resolver 都可以直接获取
foo: 'bar'
}
},
dataSources () {
return {
users: new Users(User)
}
}
})
// 将 Apollo-server 和 express 集合到一起
server.applyMiddleware({ app })
app.use((req, res) => {
res.status(200)
res.send('Hello!')
res.end()
})
app.listen({ port: 4000 }, () =>
console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`)
)
models/index.js
const mongoose = require('mongoose')
mongoose.connect('mongodb://localhost/test', {
useNewUrlParser: true,
useUnifiedTopology: true
})
const db = mongoose.connection
db.on('error', console.error.bind(console, 'connection error:'))
db.once('open', function () {
// we're connected!
console.log('连接数据库成功')
})
module.exports = {
User: require('./user')
}
models/user.js
const mongoose = require('mongoose')
const userSchema = new mongoose.Schema({
name: String,
age: Number
})
module.exports = mongoose.model('User', userSchema)
data-sources/user.js
const { MongoDataSource } = require('apollo-datasource-mongodb')
class Users extends MongoDataSource {
getUser(userId) {
return this.findOneById(userId)
// this.model // 访问数据模型对象
}
getUsers () {
return this.model.find()
}
}
module.exports = Users