Project Icon

remix-graphql

Remix 框架的 GraphQL 集成工具包

remix-graphql 是一个为 Remix 框架设计的 GraphQL 工具包。它提供了处理 loader 和 action 请求、设置本地 schema 和解析器、执行远程 API 查询,以及在资源路由中创建 GraphQL API 等功能。该工具简化了 GraphQL 在 Remix 中的集成过程,有助于提升开发效率。

remix-graphql

RemixGraphQL可以和谐共存❤️ 本包含有基本的实用函数,可以帮助你实现这一点。

更具体地说,最新版本的remix-graphql可以帮助你完成以下任务:

  • 使用GraphQL查询和变更处理loader和action请求
    • 你可以定义本地schema和解析器来处理请求
    • 你也可以对远程API执行GraphQL请求
  • 将GraphQL API设置为资源路由

以下是一些未来可能实现的酷点子:

  • 将多个loader的查询批处理为单个API请求

目录

安装

你可以使用你喜欢的包管理器安装remix-graphql。它依赖于graphql包,所以请确保也安装了该包。

# 使用`npm`
npm install graphql remix-graphql
# 或使用`yarn`
yarn add graphql remix-graphql

它还列出了一些Remix包作为对等依赖。(如果你使用Remix CLI设置项目,你很可能已经安装了它们。)如果遇到意外错误,请再次检查是否安装了以下包:

  • @remix-run/dev
  • @remix-run/react
  • @remix-run/serve
  • remix

如何从remix-graphql导入

此模块不适用于浏览器环境,它仅在服务器上工作。你可以通过从扩展名为.server.js(或.server.ts)的文件中导入,强制Remix编译器永远不会在客户端包中包含remix-graphql的内容。

// 这样做不行,实际上会抛出错误:
import { anything } from "remix-graphql";

// 应该这样做:
import { anything } from "remix-graphql/index.server";

定义你的schema

remix-graphql保持简单,让你自己决定定义GraphQL schema的最佳方式。在所有需要"将schema传递给remix-graphql"的地方,相应的函数都期望一个GraphQLSchema对象。

这意味着以下所有方法都可以用来定义schema:

  • 使用graphql包中的GraphQLSchema类(显然...)
  • 使用SDL定义schema,在对象中定义解析器函数,并使用makeExecutableSchema(来自@graphql-tools/schema)合并两者
  • 使用nexusmakeSchema

我们建议从一个文件中导出schema,例如app/graphql/schema.server.ts。通过使用.server.ts扩展名,你可以确保这些代码不会被发送到浏览器。(这是对Remix编译器的提示,在构建浏览器包时应忽略此模块。)

使用GraphQL处理loader和action请求

loadersactions都只是简单的函数,给定一个Request返回一个Response。使用remix-graphql,你可以使用GraphQL来处理这个请求!以下是一个完整且可工作的示例,展示了它是如何工作的:

// app/routes/index.tsx
import type { GraphQLError } from "graphql";
import { Form } from "remix";
import type { ActionFunction, LoaderFunction } from "@remix-run/node";;
import { processRequestWithGraphQL } from "remix-graphql/index.server";

// 从你导出schema的地方导入
import { schema } from "~/graphql/schema";

const ALL_POSTS_QUERY = /* GraphQL */ `
  query Posts($limit: Int) {
    posts(limit: $limit) {
      id
      title
      likes
      author {
        name
      }
    }
  }
`;

export const loader: LoaderFunction = (args) =>
  processRequestWithGraphQL({
    // 传递Remix传给loader函数的参数。
    args,
    // 提供你的schema。
    schema,
    // 提供应该执行的GraphQL操作。这也可以是一个mutation,
    // 它被命名为`query`是为了与通过HTTP发送GraphQL请求的常见命名保持一致。
    query: ALL_POSTS_QUERY,
    // 可选地提供执行操作时应使用的变量。如果不传递,`remix-graphql`将从以下位置派生变量:
    // - ...路由参数。
    // - ...提交的`formData`(如果存在)。
    variables: { limit: 10 },
    // 可选地传递一个对象,其属性应包含在执行上下文中。
    context: {},
    // 可选地传递一个函数,为成功执行的操作派生自定义HTTP状态码。
    deriveStatusCode(
      // 执行的结果。
      executionResult: ExecutionResult,
      // 默认情况下会返回的状态码,即如果不传递`deriveStatusCode`函数。
      defaultStatusCode: number
    ) {
      return defaultStatusCode;
    },
  });

const LIKE_POST_MUTATION = /* GraphQL */ `
  mutation LikePost($id: ID!) {
    likePost(id: $id) {
      id
      likes
    }
  }
`;

// `processRequestWithGraphQL`函数可以用于loader和action!
export const action: ActionFunction = (args) =>
  processRequestWithGraphQL({ args, schema, query: LIKE_POST_MUTATION });

export default function IndexRoute() {
  const { data } = useLoaderData<LoaderData>();
  if (!data) {
    return "哎呀,出了点问题 :(";
  }

  return (
    <main>
      <h1>博客文章</h1>
      <ul>
        {data.posts.map((post) => (
          <li key={post.id}>
            {post.title}(作者:{post.author.name})
            <br />
            {post.likes} 个赞
            <Form method="post">
              {/* `remix-graphql`会自动将所有提交的表单数据
                  转换为GraphQL操作的同名变量 */}
              <input hidden name="id" value={post.id} />
              <button type="submit">点赞</button>
            </Form>
          </li>
        ))}
      </ul>
    </main>
  );
}

type LoaderData = {
  data?: {
    posts: {
      id: string;
      title: string;
      likes: number;
      author: { name: string };
    }[];
  };
  errors?: GraphQLError[];
};

自动类型生成

在上面示例的末尾,你可以看到从loader函数返回的数据类型必须手动定义。由于GraphQL是强类型的,如果你想的话,可以自动化这个过程!

首先,你需要从你的schema生成内省数据作为JSON,并将其存储在本地文件中。为此,你可以创建一个简单的脚本,如下所示:

// app/graphql/introspection.{js,ts}
import fs from "fs";
import { introspectionFromSchema } from "graphql";
import path from "path";
import { schema } from "./schema";

fs.writeFileSync(
  path.join(__dirname, "introspection.json"),
  JSON.stringify(introspectionFromSchema(schema))
);

通常,你不希望将生成的JSON文件提交到版本控制中,所以我们建议将其添加到你的.gitignore文件中。

为了更方便地运行这个脚本,在你的package.json中创建一个简单的NPM脚本:

{
  "scripts": {
    // 如果你用JavaScript创建了脚本
    "introspection": "node app/graphql/introspection.js",
    // 如果你用TypeScript创建了脚本(确保在这种情况下安装
    // `esbuild-register`作为开发依赖)
    "introspection": "node --require esbuild-register app/graphql/introspection.ts"
  }
}

要实际从你的查询和变更生成类型,我们推荐使用GraphQL Code Generator。为此,你需要安装几个依赖:

# 使用`npm`
npm install --save-dev @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations
# 或使用`yarn`
yarn add -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations

快完成了!现在在你项目的根目录创建一个名为codegen.yml的配置文件,内容如下:

overwrite: true
# 之前生成的内省数据存储的路径
schema: "app/graphql/introspection.json"
# 匹配所有包含操作定义的文件的glob
documents: "app/routes/**/*.{ts,tsx}"
generates:
  # 这是生成的类型将被存储的路径
  app/graphql/types.ts:
    plugins:
      - "typescript"
      - "typescript-operations"
    config:
      skipTypename: true

现在你终于可以生成类型了!为了方便起见,再添加一个NPM脚本:

{
  "scripts": {
    "introspection": "node --require esbuild-register app/graphql/introspection.ts",
    "codegen": "npm run introspection && graphql-codegen --config codegen.yml"
  }
}

运行npm run codegen(或yarn codegen)现在将自动为所有查询和变更创建返回数据的类型。(附注:这也是验证所有操作是否对你的schema有效的好方法!)

**还有一件事:**注意到我们在上面的示例中包含查询和变更的字符串之前的/* GraphQL */注释了吗?这很重要!它是对@graphql-codegen的提示,表明这个字符串应该被解析为GraphQL。没有它,你将无法获得字符串中定义的操作的任何类型。

现在可以像这样修改上面的示例:

// 添加这个导入...
import type { PostsQuery } from "~/graphql/types";

// ...并像这样更改`LoaderData`类型
type LoaderData = { data?: PostsQuery; errors?: GraphQLError[] };

向远程GraphQL API发送请求

也许你不想把GraphQL API写在Remix应用中,或者你想使用第三方GraphQL API,比如GitHub的公共API。在这两种情况下,remix-graphql都可以帮到你!

// app/routes/$username.tsx
import type { GraphQLError } from "graphql";
import type { LoaderFunction } from "@remix-run/node";
import { sendGraphQLRequest } from "remix-graphql/index.server";

const LOAD_USER_QUERY = /* GraphQL */ `
  query LoadUser($username: String!) {
    user(login: $username) {
      name
    }
  }
`;

export const loader: LoaderFunction = (args) =>
  sendGraphQLRequest({
    // 传递Remix传给loader函数的参数。
    args,
    // 提供远程GraphQL API的端点。
    endpoint: "https://api.github.com/graphql",
    // 可选地为请求添加头部。
    headers: { authorization: `Bearer ${process.env.GITHUB_TOKEN}` },
    // 提供要发送到远程API的GraphQL操作。
    query: LOAD_USER_QUERY,
    // 可选地提供执行操作时应使用的变量。如果不传递,`remix-graphql`将从以下位置派生变量:
    // - ...路由参数。
    // - ...提交的`formData`(如果存在)。
    // 这意味着以下是默认值,也可以省略。
    variables: args.params,
  });

export default function UserRoute() {
  const { data } = useLoaderData<LoaderData>();
  if (!data) {
    return "哎呀,出了点问题 :(";
  }
  if (!data.user) {
    return "找不到用户 :(";
  }
  return <h1>{data.user.name}</h1>;
}

type LoaderData = {
  data?: {
    user: {
      name: string | null;
    } | null;
  };
  errors?: GraphQLError[];
};

如果你想在加载器中执行比单个 GraphQL 查询更多的操作,完全可以做到!函数 sendGraphQLRequest 会返回对远程 API 进行获取请求的 Response 对象,因此你可以在加载器中根据需要对其进行任何操作。

import { json } from "remix";
import type { LoaderFunction } from "@remix-run/node";
import { sendGraphQLRequest } from "remix-graphql/index.server";

const LOAD_USER_QUERY = /* GraphQL */ `
  query LoadUser($username: String!) {
    user(login: $username) {
      name
    }
  }
`;

export const loader: LoaderFunction = (args) => {
  try {
    const loadUserRes = await sendGraphQLRequest({
      args,
      endpoint: "https://api.github.com/graphql",
      headers: { authorization: `Bearer ${process.env.GITHUB_TOKEN}` },
      query: LOAD_USER_QUERY,
    }).then((res) => res.json());

    /* 你可以在这里执行任何额外的操作...  */
    const otherStuff = 42;

    return json({ username: loadUserRes.data.user.name, otherStuff });
  } catch {
    throw new Response("加载数据时出现问题 :(");
  }
};

在 Remix 应用中设置 GraphQL API

你可以使用 Remix 的资源路由为你的 GraphQL API 创建一个专用端点。你只需创建一个路由(例如 app/routes/graphql.ts)并粘贴以下代码。通过同时使用加载器和操作,你的端点可以支持 GET 和 POST 请求!

// app/routes/graphql.ts
import {
  getActionFunction,
  getLoaderFunction,
} from "remix-graphql/index.server";
import type { DeriveStatusCodeFunction } from "remix-graphql/index.server";

// 从你导出的地方导入 schema
import { schema } from "~/graphql/schema";

// 处理 GET 请求
export const loader = getLoaderFunction({
  // 提供你的 schema
  schema,
  // 可选:传递一个对象,其属性应包含在执行上下文中
  context: {},
  // 可选:传递一个函数,为成功执行的操作派生自定义 HTTP 状态码
  deriveStatusCode,
});

// 处理 POST 请求
export const action = getActionFunction({
  // 提供你的 schema
  schema,
  // 可选:传递一个对象,其属性应包含在执行上下文中
  context: {},
  // 可选:传递一个函数,为成功执行的操作派生自定义 HTTP 状态码
  deriveStatusCode,
});

// 此函数等同于默认行为
const deriveStatusCode: DeriveStatusCodeFunction = (
  // 执行的结果
  executionResult,
  // 默认情况下返回的状态码,即不传递 `deriveStatusCode` 函数时的状态码
  defaultStatusCode
) => defaultStatusCode;

上下文

在定义 schema 和编写解析器时,通常会提供一个上下文对象。remix-graphql 导出的所有函数都接受一个可选的 context 属性作为参数对象。如果传递,它必须是一个对象。它的所有属性都将包含在传递给解析器的上下文对象中。

remix-graphql 还导出了一个 Context 类型,包含了添加到执行上下文对象的所有属性。这个类型接受一个可选的泛型,你可以通过它向上下文对象添加任何自定义属性。

import type { PrismaClient } from "@prisma/client";
import type { Context } from "remix-graphql/index.server";

type ContextWithDatabase = Context<{ db: PrismaClient }>;

以下小节突出显示了 remix-graphql 添加到上下文对象的所有属性。

request

这是传递给 Remix 中加载器或操作函数的 Request 对象。它始终是上下文对象的一部分。

redirect

在处理 UI 路由的加载器或操作时,Remix 中的一个常见模式是重定向。(Remix 甚至提供了一个 redirect 实用函数,可以从任何加载器或操作函数返回。)在 remix-graphql 中,你可以通过使用上下文对象中提供的 redirect 函数来实现这一点。

这个函数的签名如下:

function redirect(
  // 重定向的 URL
  url: string,
  // 可选:包含在 HTTP 响应中的头部值
  headers?: HeadersInit
): void;

注意,这个函数只在处理 UI 路由中的 GraphQL 请求时才是上下文对象的一部分,即使用 processRequestWithGraphQL 时。在处理资源路由中的 GraphQL 请求时,即使用 getActionFunctiongetLoaderFunction 时,它不是上下文对象的一部分。

项目侧边栏1项目侧边栏2
推荐项目
Project Cover

豆包MarsCode

豆包 MarsCode 是一款革命性的编程助手,通过AI技术提供代码补全、单测生成、代码解释和智能问答等功能,支持100+编程语言,与主流编辑器无缝集成,显著提升开发效率和代码质量。

Project Cover

AI写歌

Suno AI是一个革命性的AI音乐创作平台,能在短短30秒内帮助用户创作出一首完整的歌曲。无论是寻找创作灵感还是需要快速制作音乐,Suno AI都是音乐爱好者和专业人士的理想选择。

Project Cover

有言AI

有言平台提供一站式AIGC视频创作解决方案,通过智能技术简化视频制作流程。无论是企业宣传还是个人分享,有言都能帮助用户快速、轻松地制作出专业级别的视频内容。

Project Cover

Kimi

Kimi AI助手提供多语言对话支持,能够阅读和理解用户上传的文件内容,解析网页信息,并结合搜索结果为用户提供详尽的答案。无论是日常咨询还是专业问题,Kimi都能以友好、专业的方式提供帮助。

Project Cover

阿里绘蛙

绘蛙是阿里巴巴集团推出的革命性AI电商营销平台。利用尖端人工智能技术,为商家提供一键生成商品图和营销文案的服务,显著提升内容创作效率和营销效果。适用于淘宝、天猫等电商平台,让商品第一时间被种草。

Project Cover

吐司

探索Tensor.Art平台的独特AI模型,免费访问各种图像生成与AI训练工具,从Stable Diffusion等基础模型开始,轻松实现创新图像生成。体验前沿的AI技术,推动个人和企业的创新发展。

Project Cover

SubCat字幕猫

SubCat字幕猫APP是一款创新的视频播放器,它将改变您观看视频的方式!SubCat结合了先进的人工智能技术,为您提供即时视频字幕翻译,无论是本地视频还是网络流媒体,让您轻松享受各种语言的内容。

Project Cover

美间AI

美间AI创意设计平台,利用前沿AI技术,为设计师和营销人员提供一站式设计解决方案。从智能海报到3D效果图,再到文案生成,美间让创意设计更简单、更高效。

Project Cover

AIWritePaper论文写作

AIWritePaper论文写作是一站式AI论文写作辅助工具,简化了选题、文献检索至论文撰写的整个过程。通过简单设定,平台可快速生成高质量论文大纲和全文,配合图表、参考文献等一应俱全,同时提供开题报告和答辩PPT等增值服务,保障数据安全,有效提升写作效率和论文质量。

投诉举报邮箱: service@vectorlightyear.com
@2024 懂AI·鲁ICP备2024100362号-6·鲁公网安备37021002001498号