在本文中,我们将研究在 GraphQL API 中创建我们自己的数据结构。 我们将要讨论的所有内容都可以在 官方 GraphQL 文档 中进行更深入的探索。
先决条件
由于我们正在设置一个基本的 API,因此最好先知道如何进行 查询 来测试我们的数据。
安装
为了简单起见,我推荐使用 Prepros 或 babel,这样我们就可以使用一些最新的 JavaScript 功能,例如 import。
如果您使用 Prepros,请确保您的 server.js 文件的 Babel 和 Auto Compile 选项已打开
为了设置我们的服务器,我们将使用 graphql-yoga 和 nodemon。 nodemon
将允许我们在文件更改时自动重新加载服务,graphql-yoga
为我们提供了简单的工具来设置基于 Express 的 GraphQL 服务器。
$ npm i graphql-yoga nodemon
nodemon 设置
让我们从让 nodemon
工作开始。 在项目的 package.json
文件中的 scripts
下,我们只需添加 "start": "nodemon server.js"
,因此我们将运行 npm run start
而不是运行 node server
.
包.json
{ "name": "graphql-api", "version": "1.0.0", "description": "", "main": "server.js", "dependencies": { "graphql-yoga": "^1.16.7" }, "devDependencies": { "nodemon": "^1.19.1" }, "scripts": { "start": "nodemon server.js" }, "author": "", "license": "ISC" }
服务器设置
在我们的主文件中,我们将从 graphql-yoga
导入 GraphQLServer
以使我们的 API 在 localhost:4000
上工作。 我们的服务器将需要两件事,我们的类型定义,它将初始化和构造我们的数据,以及我们的解析器,它告诉服务器如何获取和过滤数据。 为了让事情井井有条,我们将为我们的定义和解析器创建变量,并将它们传递给我们的服务器函数。
服务器.js
import { GraphQLServer } from 'graphql-yoga' const typeDefs = ``; // Our definitions need to be in template literals const resolvers = {}; const server = new GraphQLServer({ typeDefs, resolvers }) ; server.start(() => console.log('server running'));
由于我们尚未在 typeDefs
中设置模式,因此这将暂时返回错误。
类型定义
我们可以通过简单地添加 type Query {}
来设置基本查询,其中包含我们想要提供的数据列表以及返回数据的类型。 虽然我们可以自己制作,但 GraphQL 带有一些烘焙类型; String
、Int
(所有实数)、Float
(十进制数)、Boolean
、ID
,以及这些中的任何一个括号是该类型的数组。 任何以 !
结尾的类型都被设置为不可为空的类型,这意味着如果没有发回任何内容,它将返回错误。
这是不同可能变化的示例。 请注意,同一字段不会有多个变体,因为这仅用于说明目的:
服务器.js
const typeDefs = ` type Query { user: String # null user: String! # error 'Cannot return null for non-nullable field Query.user' users: [String!]! # must return an array of non-null strings } `;
在 localhost:4000
中,您会注意到在键入其中任何一个时不再出现错误,但在运行查询时会出现错误或 null,因为我们还没有告诉它如何获取数据.
解析器
为了能够获取数据,我们需要创建一个与我们正在查询的函数同名的函数,并让它返回我们想要的。
服务器.js
const resolvers = { Query: { user(){ return 'I am a string'; }, users(){ const names = ['Chomp', 'Jaws', 'Alli']; return names; } } };
自定义类型
String
、Int
和 Boolean
等类型是 GraphQL 开箱即用的,但我们也可以创建自己的类型,返回更多复杂的数据并将该类型传递给我们的查询以使其可请求。
在 Query
之外但仍在 typeDefs
中,我们可以创建自己的类型,如 User
(将自定义类型大写的最佳实践)并设置我们想要的数据有。 就像在 Query
中一样,我们类型中的每个项目也必须键入,例如 age: Int!
,我们将获得相应解析器返回的数据。
服务器.js
const typeDefs = ` type Query { user: User! # Must be of our custom type, User } type User { name: String! age: Int! friends: [String]! } `;
在我们的解析器中,我们可以将一个带有我们数据的对象传回。
const resolvers = { Query: { user(){ return { name: 'Chomp', age: 47, friends: ['Alli', 'Jaws'] }; } } };
数据设置
在现实世界的场景中,我们不会像这样在解析器中返回所有数据,而是从数据库等其他来源获取数据。 我们只需要设置一些虚拟数据来使用,而不是担心后端。
const users = [ { name: 'Chomp', age: 47, friends: ['Alli', 'Jaws'] },{ name: 'Alli', age: 16, friends: ['Chomp', 'Jaws'] },{ name: 'Jaws', age: 35, friends: ['Alli', 'Chomp'] }, ];
由于我们只是要获取整个用户数组,我们将告诉 typeDefs
我们想要一个类型为 User
的数组,解析器将只返回整个用户数组。
const typeDefs = ` type Query { user: [User!]! } type User { name: String! age: Int! friends: [String]! } `; const resolvers = { Query: { user(){ return users; } } };
论据
现在我们唯一的选择是返回一个特定的数据,一切。 如果我们想将响应过滤到我们想要的内容怎么办?
我们可以通过在查询中设置参数并在解析器的条件语句中使用它们来做到这一点。 我们的论点也必须输入。
const typeDefs = ` type Query { user(name: String!): [User!]! } `;
在我们的解析器中,我们可以访问 GraphQl 提供的一些非常有用的对象。 现在我们只需要 parent
和 args
。
parent
:嵌套自定义类型时父元素的信息。 因此,如果我们有一个访问Post
类型的User
类型,则每个帖子都可以访问用户的数据。args
:我们所有的参数都传递到查询中。ctx
:context 的缩写,查询可能需要的任何内容,例如身份验证数据。info
:关于查询状态的信息。
在这里,我们将检查 args
上是否有任何参数,如果有,则使用 JavaScript 的 filter()
仅返回名称与我们的参数匹配的用户。
服务器.js
const resolvers = { Query: { user(parent, args, ctx, info){ if (!args.name) return users; else return users.filter(user => { return user.name.toLowerCase().includes(args.name.toLowerCase()); }); } } };
在 localhost:4000
中,我们可以像这样使用我们的参数:
{ user(name: "alli") { name friends } }
关系数据
为了允许更多嵌套数据,我们可以将自定义类型添加到其他自定义类型中。 当我们这样做时,我们需要在 Query
之外为该类型的数据添加另一个解析器。
我们将做到这一点,因此每当我们获得用户时,我们都可以取回他们每个朋友的用户数据。
服务器.js
我们的解析器将创建一个空对象,遍历 parent
上的每个朋友,将每个匹配的用户放入 friends
数组,并将 friends
返回到我们的查询。
const typeDefs = ` type Query { user(name: String!): [User!]! } type User { name: String! age: Int! friends: [User!]! } `; const resolvers { Query: { ... }, User: { // The nested type that we're querying for friends(parent, args, ctx, info) { const friends = []; parent.friends.forEach(friend => users.filter(user => { if (user.name.toLowerCase().includes(friend.toLowerCase())) friends.push(user); })); return friends; } } };
结论
尽管 GraphQL 仍有许多需要探索的地方,但我希望这可以作为对设置我们自己的 API 的足够全面的介绍。
如果您在让我们的示例正常工作时遇到任何困难,您可以随时参考 this repo。