使用 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サーバーを任意の言語で実装

 

    疎通確認

我认为会是这样的情况。如果没有副作用,应该一切都没问题。

广告
将在 10 秒后关闭
bannerAds