全栈式、类型安全的 GraphQL 完整介绍(功能包括 Next.js、Nexus、Prisma) 全栈式、类型安全的 GraphQL 完整介绍(功能包括 Next.js、Nexus、Prisma)

2025-05-26

全栈、类型安全 GraphQL 的完整介绍(功能包括 Next.js、Nexus、Prisma)

全栈、类型安全 GraphQL 的完整介绍(功能包括 Next.js、Nexus、Prisma)

全栈、类型安全 GraphQL 的完整介绍(功能包括 Next.js、Nexus、Prisma)

在本文中,您将学习如何使用带有数据库的 GraphQL 从头开始​​构建一个完全类型安全的全栈 Web 应用程序!

要跟踪源代码,请克隆此 repo

🗓 2020 年 11 月 19 日更新:由于 Nexus 框架已停止使用,本教程已更新为使用 Nexus Schema

我们的技术栈

首先,让我们看看我们选择的工具:

开始吧!🚀

步骤 0:安装 VS Code 扩展

在开始之前,请确保您已经安装了这些用于语法高亮和自动格式化的 VS Code 扩展 - PrismaGraphQL

VS Code 的 Prisma 扩展
VS Code 的 Prisma 扩展

VS Code 的 GraphQL 扩展
VS Code 的 GraphQL 扩展

步骤 1:启动 PostgreSQL 数据库

您首先需要的是一个 PostgreSQL 数据库实例,以便在开发过程中进行交互。

有很多方法可以实现这一点,但 Heroku 允许我们免费托管 PostgreSQL 数据库,并且只需极少的设置。查看Nikolas Burk这篇文章,它会指导你完成整个过程!

如果您已经安装了 Docker 并且希望将开发数据库保留在本地,您还可以查看我制作的有关如何使用 Docker Compose 执行此操作的视频。

您将能够获得以下格式的 PostgreSQL URI:

postgresql://<USER>:<PASSWORD>@<HOST_NAME>:<PORT>/<DB_NAME>
Enter fullscreen mode Exit fullscreen mode

postgres注意:如果您使用的是 Heroku,您将获得一个以 而不是作为协议的URI postgresql。两种格式都可以使用,但我们更喜欢使用postgresql

当一切设置正确后,您就可以继续下一步了!😃

步骤 2:创建 Next.js 项目

现在,创建一个 Next.js 项目并create-next-app进入目录:

npx create-next-app my-awesome-app --use-npm -e with-typescript
cd my-awesome-app
Enter fullscreen mode Exit fullscreen mode

Git 应该由 自动初始化create-next-app,并且您的项目结构应该如下所示:

项目结构由原始的“create-next-app” endraw 引导
项目结构由create-next-app

可能create-next-app没有预装最新的 TypeScript 包。如果您想使用最新的 TypeScript 功能,请npm install -D typescript@latest在项目启动后运行。

步骤3:使用 Prisma 安装 Nexus

准备好 Next.js 项目后,在应用程序的根目录打开一个终端窗口并安装 Nexus Schema 和 Prisma。

对于 Prisma,我们需要@prisma/client@nexus/schemanexus-plugin-prisma作为常规依赖项和@prisma/cli开发依赖项。

常规依赖项:

npm i @prisma/client @nexus/schema nexus-plugin-prisma
Enter fullscreen mode Exit fullscreen mode

开发依赖项:

npm i @prisma/cli
Enter fullscreen mode Exit fullscreen mode

安装依赖项后,在项目中初始化 Prisma。

npx prisma init
Enter fullscreen mode Exit fullscreen mode

此命令将创建一个prisma目录。如果你查看目录,你会看到一个.env文件和一个schema.prisma文件。该schema.prisma文件将保存数据库模型,而.env文件将保存数据库连接字符串。

由于数据库连接字符串包含敏感信息,因此最好不要使用 Git提交此.env文件,因此请确保也将其添加到.gitignore文件中。

调整schema.prisma文件以包含User模型:

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model User {
  id   String @default(cuid()) @id
  name String
}
Enter fullscreen mode Exit fullscreen mode

模式文件告诉 Prisma 使用 PostgreSQL 作为数据库类型,并将数据库连接 URL 定义为环境变量。它还定义了一个User带有idname字段的简单数据模型。

您的项目目前看起来应该是这样的:

创建原始“prisma” endraw 目录后的项目结构
创建prisma目录 后的项目结构

步骤 4:将 Nexus Schema 与 Next.js 连接起来

Nexus Schema 是一个允许我们构建代码优先的 GraphQL API 的库。我们负责提供一个服务器来服务该 API。为了达到我们的目的,我们将使用Apollo Server

Apollo Server 有多种版本,可用于各种用途。对于我们的项目,我们选择它apollo-server-mirco,因为它非常适合无服务器部署。

npm install apollo-server-micro
Enter fullscreen mode Exit fullscreen mode

要创建 GraphQL 端点,请在项目中创建一个新文件/pages/api/graphql.ts。得益于 Next.js 中强大的API 路由http://our-app-domain/api/graphql,在Next.js 服务器启动时即可访问 GraphQL 服务器。

在该/pages/api/graphql.ts文件中,写入以下样板代码:

import { ApolloServer } from 'apollo-server-micro';

// we'll create these in a second!
import { schema } from '../../graphql/schema';
import { createContext } from './../../graphql/context';

const apolloServer = new ApolloServer({
  context: createContext,
  schema,
  tracing: process.env.NODE_ENV === 'development'
});

export const config = {
  api: {
    bodyParser: false
  }
};

export default apolloServer.createHandler({
  path: '/api/graphql'
});
Enter fullscreen mode Exit fullscreen mode

由于/pages/api/目录内的所有内容都被视为 API 路由,因此最好在此目录之外实现实际的模式和解析器。

现在,在项目根目录中创建一个名为 的新目录/graphql/,并在其中包含两个文件:/graphql/schema.ts/graphql/context.ts

在 中/graphql/schema.ts,首先使用makeSchema函数通过 Nexus 构建 GraphQL 模式。我们还希望nexus-plugin-prisma启用CRUD功能:

// graphql/schema.ts

import { objectType, queryType, mutationType, makeSchema } from '@nexus/schema';
import { nexusPrisma } from 'nexus-plugin-prisma';
import path from 'path';

const Query = queryType({
  definition(t) {
    t.string('hello', { resolve: () => 'hello world' });
  }
});

export const schema = makeSchema({
  types: [Query],
  plugins: [nexusPrisma({ experimentalCRUD: true })],
  outputs: {
    typegen: path.join(process.cwd(), 'generated', 'nexus-typegen.ts'),
    schema: path.join(process.cwd(), 'generated', 'schema.graphql')
  },
  typegenAutoConfig: {
    contextType: 'Context.Context',
    sources: [
      {
        source: '@prisma/client',
        alias: 'prisma'
      },
      {
        source: path.join(process.cwd(), 'graphql', 'context.ts'),
        alias: 'Context'
      }
    ]
  }
});
Enter fullscreen mode Exit fullscreen mode

对 的调用makeSchema包含一个名为 的属性plugins。这是一个包含我们想要与 Nexus Schema 一起使用的所有插件的数组,在本例中,我们想要使用nexus-plugin-prisma。此处的配置指示插件使用 CRUD 功能,这使得我们能够为 API 自动生成 CRUD 解析器。您可以阅读更多关于 Nexus Schema 提供的 CRUD 功能的信息。

接下来,初始化PrismaClient内部/graphql/context.ts并导出一个函数来在 Apollo Server 中创建上下文。

// graphql/context.ts

import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

export interface Context {
  prisma: PrismaClient;
}

export function createContext(): Context {
  return { prisma };
}
Enter fullscreen mode Exit fullscreen mode

文件结构现在看起来应该是这样的:

my-awesome-app/
├─ components/
├─ graphql/
│  ├─ context.ts
│  ├─ schema.ts
├─ interfaces/
├─ pages/
├─ prisma/
│  ├─ .env
│  ├─ schema.prisma
├─ utils/
├─ next-env.d.ts
├─ package-lock.json
├─ package.json
├─ tsconfig.json
Enter fullscreen mode Exit fullscreen mode

有了这些文件,运行应用程序:

npx next dev
Enter fullscreen mode Exit fullscreen mode

如果你访问http://localhost:3000/api/graphql,你会看到 GraphQL Playground 已经启动并运行(使用我们的“hello world”模式)!😃

带有 Hello World 模式的 GraphQL Playground
带有 Hello World 模式的 GraphQL Playground

步骤 5:实现您的第一个 GraphQL API

随着 GraphQL 服务器在后台运行,并且 GraphQL Playground 在http://localhost:3000/api/graphql准备就绪,现在是时候开始实现 API 了!

步骤 5.1:定义对象类型

首先定义一个User对象类型来反映数据库模式。定义完成后,将其添加到types中的数组中makeSchema

// graphql/schema.ts

import { objectType, queryType, makeSchema } from '@nexus/schema';

const User = objectType({
  name: 'User',
  definition(t) {
    t.model.id();
    t.model.name();
  }
});

// ...

export const schema = makeSchema({
  types: [User, Query]
  // ...
});
Enter fullscreen mode Exit fullscreen mode

如果您键入上述代码而不是复制粘贴,您会注意到 VS Code 将自动完成先前定义的数据模型上可用的字段( id, ) nameUser/prisma/schema.prisma

提示:您也可以随时使用 调用智能感知CTRL + SPACE,以防它有时不会自动显示,这非常有用!

现在,返回 GraphQL Playground 并切换Schema侧面板 - 您将看到User从刚刚在文件中编写的代码生成了一个 GraphQL 对象类型/graphql/schema.ts

type User {
  id: String!
  name: String!
}
Enter fullscreen mode Exit fullscreen mode

步骤 5.2:定义查询类型

对于根Query类型,Nexus 提供了一个queryType函数。

要查询数据库中现有用户的列表,可以编写字段解析器,allUsers如下所示:

const Query = queryType({
  definition(t) {
    t.list.field('allUsers', {
      type: 'User',
      resolve(_parent, _args, ctx) {
        return ctx.prisma.user.findMany({});
      }
    });
  }
});
Enter fullscreen mode Exit fullscreen mode

您可以在函数中执行任何您想做的事情resolve。数据库的 Prisma 客户端可以直接作为db对象的属性进行访问。您可以在 Prisma 客户端的官方文档ctx中阅读有关其 API 的更多信息

提示:您随时可以使用 VS Code 中的智能感知功能来探索 Nexus 和 Prisma Client 的 API!

除了手动编写解析器之外,Nexus-Prisma 插件还方便地公开了对数据库的基本“读取”操作t.crud。以下代码可让您直接从数据库中查找 (或sUser列表)。User

const Query = queryType({
  definition(t) {
    t.list.field('allUsers', {
      type: 'User',
      resolve(_parent, _args, ctx) {
        return ctx.prisma.user.findMany({});
      }
    });
    t.crud.user();
    t.crud.users();
  }
});
Enter fullscreen mode Exit fullscreen mode

上面的代码将生成一个 GraphQL 根Query类型:

type Query {
  allUsers: [User!]
  user(where: UserWhereUniqueInput!): User
  users(
    skip: Int
    after: UserWhereUniqueInput
    before: UserWhereUniqueInput
    first: Int
    last: Int
  ): [User!]!
}

input UserWhereUniqueInput {
  id: String
}
Enter fullscreen mode Exit fullscreen mode

请注意,所有相关Input类型也都是免费为我们生成的!💯

原始“Query” endraw 类型由 Nexus 生成
Query类型由 Nexus 生成

步骤 5.3:定义突变类型

与类型类似QueryMutation类型可以用mutationType函数来定义。

😈 让我们玩得开心一点,创建一个bigRedButton突变来销毁数据库中的所有用户数据。

我们还可以访问t.crud这里的辅助函数,它公开了数据库的基本“创建”、“更新”和“删除”操作。然后我们必须将数据添加Mutationtypes数组中makeSchema

import { objectType, queryType, mutationType, makeSchema } from '@nexus/schema';

// ...

const Mutation = mutationType({
  definition(t) {
    t.field('bigRedButton', {
      type: 'String',
      async resolve(_parent, _args, ctx) {
        const { count } = await ctx.prisma.user.deleteMany({});
        return `${count} user(s) destroyed. Thanos will be proud.`;
      }
    });

    t.crud.createOneUser();
    t.crud.deleteOneUser();
    t.crud.deleteManyUser();
    t.crud.updateOneUser();
    t.crud.updateManyUser();
  }
});

// ...

export const schema = makeSchema({
  types: [User, Query, Mutation]
  // ...
});
Enter fullscreen mode Exit fullscreen mode

这将生成如下所示的 GraphQL 模式:

type Mutation {
  bigRedButton: String
  createOneUser(data: UserCreateInput!): User!
  deleteOneUser(where: UserWhereUniqueInput!): User
  deleteManyUser(where: UserWhereInput): BatchPayload!
  updateOneUser(data: UserUpdateInput!, where: UserWhereUniqueInput!): User
  updateManyUser(
    data: UserUpdateManyMutationInput!
    where: UserWhereInput
  ): BatchPayload!
}
Enter fullscreen mode Exit fullscreen mode

原始的“Mutation” endraw 类型由 Nexus 生成
Mutation类型由 Nexus 生成

现在,我们简单但功能齐全的 GraphQL API 已经准备好了!🥳

步骤6:初始化数据库

在使用 GraphQL API 执行任何操作之前,您需要在数据库中创建与 Prisma 模式文件相对应的表。

这可以通过手动连接到数据库并运行 SQL 命令来完成,但我将向您展示如何使用prisma db push命令(Prisma 2 的一部分的数据库工具)来执行此操作。

首先,使用以下命令保存文件的初始更改schema.prisma。目前,该prisma db push命令仍处于预览状态,因此--preview-feature需要额外的 flag 标志。

npx prisma db push --preview-feature
Enter fullscreen mode Exit fullscreen mode

太棒了!数据库准备好后,就可以回到http://localhost:3000/api/graphql,体验一下你的第一个 Nexus GraphQL API 了。我给你举个例子,方便你操作!

mutation {
  createOneUser(data: { name: "Alice" }) {
    id
  }
}
Enter fullscreen mode Exit fullscreen mode

测试用户创建😎
测试用户创建😎

步骤 7:使用 Next.js 设置 Urql GraphQL 客户端

我们将使用Urql作为前端的 GraphQL 客户端,但您可以使用任何您喜欢的库。

首先,安装依赖项:

npm install graphql-tag next-urql react-is urql isomorphic-unfetch
Enter fullscreen mode Exit fullscreen mode

然后,在 处创建一个新文件/pages/_app.tsx。这是一个特殊的 Next.js 组件,将用于初始化所有页面。

import React from 'react';
import { withUrqlClient, NextUrqlAppContext } from 'next-urql';
import NextApp, { AppProps } from 'next/app';
import fetch from 'isomorphic-unfetch';

// the URL to /api/graphql
const GRAPHQL_ENDPOINT = `http://localhost:3000/api/graphql`;

const App = ({ Component, pageProps }: AppProps) => {
  return <Component {...pageProps} />;
};

App.getInitialProps = async (ctx: NextUrqlAppContext) => {
  const appProps = await NextApp.getInitialProps(ctx);
  return { ...appProps };
};

export default withUrqlClient((_ssrExchange, _ctx) => ({
  url: GRAPHQL_ENDPOINT,
  fetch
}))(
  // @ts-ignore
  App
);
Enter fullscreen mode Exit fullscreen mode

就这样!现在,您可以在 Next.js 应用的任何页面中使用 GraphQL 客户端。

步骤 8:使用 GraphQL 客户端

首先,在 创建一个 TSX 文件/components/AllUsers.tsx。该文件将包含一个组件,用于执行allUsersGraphQL 查询并将结果渲染为列表。这样,我们就可以使用该组件从 PostgreSQL 数据库中获取所有用户信息。

您可以先创建查询,例如,使用以下代码。通过使用gql,GraphQL VS Code 扩展将能够将模板字符串识别为 GraphQL 查询,并对其应用良好的语法高亮显示。

import React from 'react';
import gql from 'graphql-tag';
import { useQuery } from 'urql';

const AllUsersQuery = gql`
  query {
    allUsers {
      id
      name
    }
  }
`;
Enter fullscreen mode Exit fullscreen mode

因为已知您将要获取的数据是一个User对象数组(感谢 GraphQL 模式!),所以您还可以定义一个新类型:

type AllUsersData = {
  allUsers: {
    id: string;
    name: string;
  }[];
};
Enter fullscreen mode Exit fullscreen mode

接下来,创建将使用查询的 React 组件。

该组件封装了以下逻辑:

  • 如果查询仍处于获取状态,则会返回文本“正在加载...”
  • 如果过程中出现错误,我们将显示错误
  • 如果查询不再获取并且没有错误,则数据将用于呈现用户列表
const AllUsers: React.FC = () => {
  const [result] = useQuery<AllUsersData>({
    query: AllUsersQuery
  });
  const { data, fetching, error } = result;

  if (fetching) return <p>Loading...</p>;
  if (error) return <p>Oh no... {error.message}</p>;

  return (
    <div>
      <p>There are {data?.allUsers.length} user(s) in the database:</p>
      <ul>
        {data?.allUsers.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
};

export default AllUsers;
Enter fullscreen mode Exit fullscreen mode

现在,保存 TSX 文件,并将其安装到主页上/pages/index.tsx

import Link from 'next/link';
import Layout from '../components/Layout';
import AllUsers from '../components/AllUsers';

const IndexPage = () => (
  <Layout title="Home | Next.js + TypeScript Example">
    <h1>Hello Next.js 👋</h1>
    <p>
      <Link href="/about">
        <a>About</a>
      </Link>
    </p>
    {/* === Tada! === */}
    <AllUsers />
  </Layout>
);

export default IndexPage;
Enter fullscreen mode Exit fullscreen mode

是时候启动 Next.js 开发服务器了!

npm run dev
Enter fullscreen mode Exit fullscreen mode

瞧!用户列表渲染出来了!🥳

用户列表呈现在我们的主页上
我们的主页上会显示一个用户列表

步骤 9:自动生成useQuery钩子和类型

我们不必手动定义所有希望通过 GraphQL 接收的类型,而是可以使用一个非常酷的包GraphQL Code Generator,直接从 Nexus GraphQL 端点生成类型。这样,你基本上只需要在schema.prisma文件中将类型定义一次作为单一真实来源,然后应用程序中使用的所有类型都可以轻松地从该模式派生出来!🎉

首先,将 TSX 文件中的 GraphQL 查询复制并重构到graphql目录中。以步骤 8 中的示例为例,在 处创建一个新文件/graphql/queries.graphql.ts,并从 处复制查询/components/AllUsers.tsx

import gql from 'graphql-tag';

export const AllUsersQuery = gql`
  query AllUsers {
    allUsers {
      id
      name
    }
  }
`;
Enter fullscreen mode Exit fullscreen mode

将 GraphQL 操作与组件分离使得导航代码库变得更加容易。

接下来,安装作为开发依赖项所需的软件包graphql-code-generator

npm install -D \
    @graphql-codegen/cli \
    @graphql-codegen/typescript \
    @graphql-codegen/typescript-operations \
    @graphql-codegen/typescript-urql
Enter fullscreen mode Exit fullscreen mode

codegen.yml然后,在项目根目录中创建一个包含以下内容的文件:

overwrite: true
schema: 'http://localhost:3000/api/graphql' # GraphQL endpoint via the nexus dev server
documents: 'graphql/**/*.graphql.ts' # parse graphql operations in matching files
generates:
  generated/graphql.tsx: # location for generated types, hooks and components
    plugins:
      - 'typescript'
      - 'typescript-operations'
      - 'typescript-urql'
    config:
      withComponent: false # we'll use Urql client with hooks instead
      withHooks: true
Enter fullscreen mode Exit fullscreen mode

上述配置将告诉graphql-code-generator从 中提取 GraphQL 模式http://localhost:3000/api/graphql,然后生成类型、UrqluseQuery挂钩到位于 的文件中/generated/graphql.tsx

太棒了,让我们开始代码生成(在监视模式下)!

npx graphql-codegen --watch
Enter fullscreen mode Exit fullscreen mode

您将在中看到一些由机器人编写的漂亮代码/generated/graphql.tsx。多么简洁啊!

原始的 `useAllUsersQuery` endraw 钩子和所有类型均由 GraphQL 代码生成器生成
一个useAllUsersQuery钩子和所有类型均由 GraphQL 代码生成器生成

现在,您可以返回components/AllUsers.tsx,并使用文件中的内容替换手动编写的AllUsersData类型、GraphQL 查询和钩子useQuery/generated/graphql.tsx

import React from 'react';
import { useAllUsersQuery } from '../generated/graphql';

const AllUsers: React.FC = () => {
  const [result] = useAllUsersQuery();
  const { data, fetching, error } = result;

  if (fetching) return <p>Loading...</p>;
  if (error) return <p>Oh no... {error.message}</p>;

  return (
    <div>
      <p>There are {data?.allUsers?.length} user(s) in the database:</p>
      <ul>
        {data?.allUsers?.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
};

export default AllUsers;
Enter fullscreen mode Exit fullscreen mode

重新访问应用程序的索引页http://localhost:3000,一切正常!🙌

为了让开发体验更加美好,我们对项目的NPM脚本进行优化。

首先,安装Concurrently NPM 模块,它是同时运行多个 CLI 观察器的绝佳工具:

npm install -D concurrently
Enter fullscreen mode Exit fullscreen mode

然后,用以下内容替换文件dev中的脚本:package.json

{
  // ...
  "scripts": {
    // ...
    "dev": "concurrently -r \"npx nexus dev\" \"npx next\" \"npx graphql-codegen --watch\""
    // ...
  }
  // ...
}
Enter fullscreen mode Exit fullscreen mode

现在,我们可以使用单个npm run dev命令同时启动 Nexus、Next.js 和 GraphQL 代码生成器!

结论

希望你喜欢本教程并学到一些有用的东西!你可以在这个 GitHub 仓库中找到源代码。

另外,请查看Awesome Prisma 列表 ,了解 Prisma 生态系统中的更多教程和入门项目!

文章来源:https://dev.to/prisma/complete-introduction-to-fullstack-type-safe-graphql-feat-next-js-nexus-prisma-c5
PREV
如何在 Heroku 上设置免费的 PostgreSQL 数据库
NEXT
我如何为我的大学网站开发验证码破解器