带有 DataLoader 和嵌套端点的 GraphQL 解析器
GraphQL resolver with DataLoader and nested endpoints
采用 Apollo 团队 (https://github.com/apollographql/GitHunt-API/tree/master/api) 创建的 GraphQL 最佳实践示例,我很难想出一个解析器来生成 Person 列表 使用 DataLoader。
这是 api 的示例(数据来自:https://github.com/steveluscher/zero-to-graphql/tree/master/zero-node)
给定 /people/ 端点的输出如下:
{
"people": [
{
"username": "steveluscher",
"id": "1",
},
{
"username": "aholovaty",
"id": "2",
},
{
"username": "swillison",
"id": "3",
},
{
"username": "gvr",
"id": "4",
}
]
}
还有一个人来自端点/people/1/
{
"person": {
"last_name": "Luscher",
"username": "steveluscher",
"friends": [
"/people/2/",
"/people/3/"
],
"id": "1",
"email": "steveluscher@fb.com",
"first_name": "Steven"
}
我想要一个解析器,可以给我一个 Person 的列表,例如:
[
{
"person": {
"last_name": "Luscher",
"username": "steveluscher",
"friends": [
"/people/2/",
"/people/3/"
],
"id": "1",
"email": "steveluscher@fb.com",
"first_name": "Steven"
}
},
{
"person": {
"last_name": "Holovaty",
"username": "aholovaty",
"friends": [
"/people/1/",
"/people/4/"
],
"id": "2",
"email": "a.holovaty@django.com",
"first_name": "Adrian"
}
},
...
]
这是我目前得到的:
server.js
import { ApiConnector } from './api/connector';
import { People } from './api/models';
import schema from './schema';
export function run() {
const PORT = 3000;
const app = express();
app.use(bodyParser.json());
app.use('/graphql', graphqlExpress((req) => {
const query = req.query.query || req.body.query;
if (query && query.length > 2000) {
throw new Error('Query too large.');
}
const apiConnector = new ApiConnector();
return {
schema,
context: {
People: new People({ connector: apiConnector }),
},
};
}));
app.use('/graphiql', graphiqlExpress({
endpointURL: '/graphql',
}));
const server = createServer(app);
server.listen(PORT, () => {
console.log(`API Server is now running on http://localhost:${PORT}`);
});
return server;
}
models.js
export class People {
constructor({ connector }) {
this.connector = connector;
}
getPeople() {
return this.connector.get(`/people/`);
}
getPerson(id) {
return this.connector.get(`/people/${id}/`);
}
}
connector.js
const API_ROOT = 'http://localhost:8080';
export class ApiConnector {
constructor() {
this.rp = rp;
this.loader = new DataLoader(this.fetch.bind(this));
}
fetch(urls) {
const options = {
json: true,
resolveWithFullResponse: true,
headers: {
'user-agent': 'Request-Promise',
},
};
return Promise.all(urls.map((url) => {
return new Promise((resolve) => {
this.rp({
uri: url,
...options,
}).then((response) => {
const body = response.body;
resolve(body);
}).catch((err) => {
console.error(err);
resolve(null);
});
});
}));
}
get(path) {
return this.loader.load(API_ROOT + path);
}
架构中的解析器将具有类似的内容:
const rootResolvers = {
Query: {
people(root, args, context) {
return context.People.getPeople();
},
person(root, { id }, context) {
return context.People.getPerson(id)
}
},
};
到目前为止,我可以从 /people/id/ 获得第一个端点 /people/ 和一个人。但是如何将其更改为具有人员列表?我不太确定 how/where 应该是这段代码。
非常感谢!
您可以将您的人员解析器更改为类似于以下代码的内容:
const rootResolvers = {
Query: {
people(root, args, context) {
const list = context.People.getPeople();
if (list && list.length > 0) {
return list.map(item => context.People.getPerson(item.id))
}
},
...
},
};
Ps:你说你正在使用dataLoader,所以我认为你的API调用只是被缓存了,但如果不是这样,你需要实现一些缓存以避免多次调用相同的端点。
采用 Apollo 团队 (https://github.com/apollographql/GitHunt-API/tree/master/api) 创建的 GraphQL 最佳实践示例,我很难想出一个解析器来生成 Person 列表 使用 DataLoader。
这是 api 的示例(数据来自:https://github.com/steveluscher/zero-to-graphql/tree/master/zero-node)
给定 /people/ 端点的输出如下:
{
"people": [
{
"username": "steveluscher",
"id": "1",
},
{
"username": "aholovaty",
"id": "2",
},
{
"username": "swillison",
"id": "3",
},
{
"username": "gvr",
"id": "4",
}
]
}
还有一个人来自端点/people/1/
{
"person": {
"last_name": "Luscher",
"username": "steveluscher",
"friends": [
"/people/2/",
"/people/3/"
],
"id": "1",
"email": "steveluscher@fb.com",
"first_name": "Steven"
}
我想要一个解析器,可以给我一个 Person 的列表,例如:
[
{
"person": {
"last_name": "Luscher",
"username": "steveluscher",
"friends": [
"/people/2/",
"/people/3/"
],
"id": "1",
"email": "steveluscher@fb.com",
"first_name": "Steven"
}
},
{
"person": {
"last_name": "Holovaty",
"username": "aholovaty",
"friends": [
"/people/1/",
"/people/4/"
],
"id": "2",
"email": "a.holovaty@django.com",
"first_name": "Adrian"
}
},
...
]
这是我目前得到的:
server.js
import { ApiConnector } from './api/connector';
import { People } from './api/models';
import schema from './schema';
export function run() {
const PORT = 3000;
const app = express();
app.use(bodyParser.json());
app.use('/graphql', graphqlExpress((req) => {
const query = req.query.query || req.body.query;
if (query && query.length > 2000) {
throw new Error('Query too large.');
}
const apiConnector = new ApiConnector();
return {
schema,
context: {
People: new People({ connector: apiConnector }),
},
};
}));
app.use('/graphiql', graphiqlExpress({
endpointURL: '/graphql',
}));
const server = createServer(app);
server.listen(PORT, () => {
console.log(`API Server is now running on http://localhost:${PORT}`);
});
return server;
}
models.js
export class People {
constructor({ connector }) {
this.connector = connector;
}
getPeople() {
return this.connector.get(`/people/`);
}
getPerson(id) {
return this.connector.get(`/people/${id}/`);
}
}
connector.js
const API_ROOT = 'http://localhost:8080';
export class ApiConnector {
constructor() {
this.rp = rp;
this.loader = new DataLoader(this.fetch.bind(this));
}
fetch(urls) {
const options = {
json: true,
resolveWithFullResponse: true,
headers: {
'user-agent': 'Request-Promise',
},
};
return Promise.all(urls.map((url) => {
return new Promise((resolve) => {
this.rp({
uri: url,
...options,
}).then((response) => {
const body = response.body;
resolve(body);
}).catch((err) => {
console.error(err);
resolve(null);
});
});
}));
}
get(path) {
return this.loader.load(API_ROOT + path);
}
架构中的解析器将具有类似的内容:
const rootResolvers = {
Query: {
people(root, args, context) {
return context.People.getPeople();
},
person(root, { id }, context) {
return context.People.getPerson(id)
}
},
};
到目前为止,我可以从 /people/id/ 获得第一个端点 /people/ 和一个人。但是如何将其更改为具有人员列表?我不太确定 how/where 应该是这段代码。
非常感谢!
您可以将您的人员解析器更改为类似于以下代码的内容:
const rootResolvers = {
Query: {
people(root, args, context) {
const list = context.People.getPeople();
if (list && list.length > 0) {
return list.map(item => context.People.getPerson(item.id))
}
},
...
},
};
Ps:你说你正在使用dataLoader,所以我认为你的API调用只是被缓存了,但如果不是这样,你需要实现一些缓存以避免多次调用相同的端点。