使用 react-apollo 和 next.js 来实现最快的 GraphQL 应用程序(包括模拟服务器)
我在尝试使用 react-apollo 的过程中,顺便进行了一些杂项工作,包括模拟服务器等。由于体验相当不错,我想给大家推荐一下。
我也想给世界前端工程师推广Apollo Client- Qiita参考。
请参考以下的GitHub链接:https://github.com/mizchi-sandbox/graphql-playground,其中提供了一个运行此代码的示例。您可以使用 git clone 命令将代码克隆到本地,并使用 yarn install 进行安装。然后使用 yarn start 命令进行运行。
目标
最终的情况是,客户端实现(next.js)将能够运行。
import React from "react";
import { Query } from "react-apollo";
import gql from "graphql-tag";
const GET_USER = gql`
{
user {
id
name
}
}
`;
export default () => {
return (
<Query query={GET_USER} ssr={true}>
{props => {
if (props.loading) {
return "Loading...";
}
return `Hello, ${props.data.user.name} - ${props.data.user.id}`;
}}
</Query>
);
};
用户:虽然查询的实现遵循了模式,但这个响应是从模拟返回的。
即使没有完全实现GraphQL服务器,只要从模式定义返回类似的行为,就可以开始开发。
实施服务器
首先,我們需要撰寫GraphQL的架構。由於本次不涉及對GraphQL本身的說明,因此我們只需進行最基本的架構定義。
type User {
id: ID!
name: String!
}
type Query {
user: User!
}
当执行查询 query { user { name } } 时,将字符串存储在 user.name 中,这是一个简单的实现。
我们来简单实现一个遵循此模式的 GraphQL 服务器。这仅用于测试,并且在创建模拟服务器时可以重复使用此代码,因此即使最终是用另一种语言实现也没问题。
yarn add express body-parser apollo-graphql-express graphql-tools
请先完成所需部分。如果不够,可以适时追加。
由于在服务器端安装 Babel 很麻烦,所以这次服务器端使用 commonjs,客户端使用 ESM 的 import 来编写。
const path = require("path");
const fs = require("fs");
const express = require("express");
const bodyParser = require("body-parser");
const { graphqlExpress, graphiqlExpress } = require("apollo-server-express");
const {
makeExecutableSchema,
addMockFunctionsToSchema
} = require("graphql-tools");
const cors = require("cors");
const resolvers = {
Query: {
user() {
return { name: "hoge", id: "hoge" }
}
}
}
const schema = makeExecutableSchema({
typeDefs: fs
.readFileSync(path.join(__dirname, "schema.graphql"))
.toString(),
resolvers
});
const app = express();
app.use(cors());
app.use("/graphql", bodyParser.json(), graphqlExpress({ schema }));
app.use("/graphiql", graphiqlExpress({ endpointURL: "/graphql" }));
app.listen(3001);
虽然说是最小的,但它也支持cors和graphiql,这样做是为了方便调试。
需要关注的是,resolvers 是服务器实现的核心部分。只需返回满足 User 类型的值或 Promise 即可,如果违反了模式,将引发异常。
当使用node server.js运行此命令时,将在http://localhost:3001/graphql上创建一个GraphQL的端点。
当你打开 http://localhost:3001/graphiql ,你可以在一个名为 graphiql 的交互式播放区中执行查询。

最后可能需要在此处进行令牌验证并进行认证。
实现GraphQL客户端
这次为了省去搭建环境等步骤,我们直接用next快速运行。
yarn add next react react-dom react-apollo isomorphic-fetch apollo-boost
首先,作为准备叩击 ReactApollo ,我们会在所有页面的根元素 pages/_app.js 中添加 ApolloProvider 。这个文件名遵循 next.js 的规定。
import React from "react";
import { Container } from "next/app";
import { ApolloClient } from "apollo-boost";
import { HttpLink } from "apollo-boost";
import { InMemoryCache } from "apollo-boost";
import fetch from "isomorphic-unfetch";
import { ApolloProvider } from "react-apollo";
const IS_BROWSER = !!process.browser;
if (!IS_BROWSER) {
global.fetch = fetch;
}
const URI_ENDPOINT = "http://localhost:3001/graphql";
function createClient(initialState) {
return new ApolloClient({
connectToDevTools: IS_BROWSER,
ssrMode: !IS_BROWSER, // Disables forceFetch on the server (so queries are only run once)
link: new HttpLink({
uri: URI_ENDPOINT, // Server URL (must be absolute)
credentials: "same-origin" // Additional fetch() options like `credentials` or `headers`
}),
cache: new InMemoryCache().restore(initialState || {})
});
}
const client = createClient();
export default props => {
const { Component, pageProps, apolloClient } = props;
return (
<Container>
<ApolloProvider client={client}>
<Component {...pageProps} />
</ApolloProvider>
</Container>
);
};
在这里,重要的是,将 HttpLink 设定为 http://localhost:3001/graphql。通过更改这一点,我们可以连接到不同的通信适配器。我们将在之后使用它。
除此之外,为了 SSR 的行为,我们还查看 process.browser (next.js 的功能)并进行行为切换。不过,请注意这是一个为了测试而敷衍实现的,真正想要进行 SSR 的人,请参考 https://github.com/zeit/next.js/tree/master/examples/with-apollo。
接下来,我们将实现一个React组件,它在被调用时返回该“/”路径。
import React from "react";
import { Query } from "react-apollo";
import gql from "graphql-tag";
const GET_USER = gql`
{
user {
id
name
}
}
`;
export default () => {
return (
<Query query={GET_USER} ssr={true}>
{props => {
if (props.loading) {
return "Loading...";
}
return `Hello, ${props.data.user.name} - ${props.data.user.id}`;
}}
</Query>
);
};
如果使用ReactApollo的组件,查询结果会以renderProp样式传递过来,这样我们就可以用它来渲染页面。虽然这是最近添加的功能,但非常方便。
完成到这一步后,使用命令 yarn next 在本地的localhost:3000启动客户端。在浏览器中打开 http://localhost:3000 进行确认。
请在中国本土实现模拟服务器。
yarn add apollo-cache-inmemory apollo-link-schema
使用 addMockFunctionsToSchema 方法将 GraphQL 服务器切换到模拟实现的行为。
// ...
const mocks = {
ID: () => "<id>",
String: () => "<string>",
User: () => ({
id: () => Date.now().toString(),
name: () => "user_by_type"
}),
Query: () => ({
user: () => ({
id: () => Date.now().toString(),
name: () => "user_by_query"
})
})
};
const {
makeExecutableSchema,
addMockFunctionsToSchema
} = require("graphql-tools");
const schema = makeExecutableSchema({
typeDefs: fs
.readFileSync(path.join(__dirname, "../schema.graphql"))
.toString()
});
addMockFunctionsToSchema({ schema, mocks });
// ...
你可以看看 mocks 这个地方。 如果你写了与类型匹配的模拟数据并返回它,可以试试删除查询。 测试一下,应该返回 User 的模拟数据,而如果将其删除,则应修改模拟的 ID、字符串和整数类型的数据。
查询和变更是特殊的类型,在这里编写的内容将成为未指定命名空间的查询名称。
当使用这个命令 `node server.js` 重新启动GraphQL服务器时,应该切换到模拟数据的处理部分。
只在客户端中插入模拟数据
当我们达到这个阶段时,就希望能够在像 Storybook 这样的组件目录和测试环境中,不依赖于服务器的存在,只借用定义和模拟实现,在客户端上完成。让我们做吧。
首先,在客户端上创建一个Mock用的提供者。
import React from "react";
import { makeExecutableSchema, addMockFunctionsToSchema } from "graphql-tools";
import { ApolloClient } from "apollo-client";
import { ApolloProvider, Query } from "react-apollo";
import { InMemoryCache } from "apollo-cache-inmemory";
import { SchemaLink } from "apollo-link-schema";
const typedefs = `
type User {
id: ID!
name: String!
}
type Query {
user: User!
}
`
const mocks = {
User: () => ({
id: () => Date.now().toString(),
name: () => "user_by_type"
}),
Query: () => ({
user: () => ({
id: () => Date.now().toString(),
name: () => "user_by_query"
})
})
}
const schema = makeExecutableSchema({ typeDefs });
addMockFunctionsToSchema({
schema,
mocks
});
const client = new ApolloClient({
ssrMode: true,
link: new SchemaLink({ schema }),
cache: new InMemoryCache()
});
export default ({ children }) => (
<ApolloProvider client={client}>{children}</ApolloProvider>
);
我现在正在客户端实现以前在服务器上实现的 GraphQL 的 makeExecutableSchema,并将 apolloClient 的 link 从 HttpLink 更改为 SchemaLink。通过这样做,客户端现在可以执行 GraphQL。
让我们使用这个,从storybook运行pages/index.js之后的内容。(略去storybook的介绍)
import React from "react";
import { storiesOf } from "@storybook/react";
import MockProvider from "../MockProvider";
import Index from "../pages/index";
storiesOf("Mock GraphQL example", module).add("Mocked Index", () => (
<MockProvider>
<Index />
</MockProvider>
));
只需按照上述模拟定义运行,无需启动服务器,即可正常工作。
在apollo-react/test-utils中有一个名为MockedProvider的模块,但根据我的试验,它并没有工作。
这次我们使用Node来实现服务器,但只要共享模式定义,就可以切换到其他语言实现,或者使用类似Graphcool的PaaS,或者使用类似Prisma的GraphQL实现也是可以的。
实际的开发流程
-
- graphql サーバーを mock モードでそれらしいデータを返却
-
- graphiql の localhost:3001/graphiql などで挙動を確認
-
- クライアントから ApolloProvider で任意の実装に接続
-
- GraphQLサーバーを任意の言語で実装
- 疎通確認
我认为会是这样的情况。如果没有副作用,应该一切都没问题。