全栈、类型安全 GraphQL 的完整介绍(功能包括 Next.js、Nexus、Prisma)
全栈、类型安全 GraphQL 的完整介绍(功能包括 Next.js、Nexus、Prisma)
全栈、类型安全 GraphQL 的完整介绍(功能包括 Next.js、Nexus、Prisma)
在本文中,您将学习如何使用带有数据库的 GraphQL 从头开始构建一个完全类型安全的全栈 Web 应用程序!
要跟踪源代码,请克隆此 repo。
🗓 2020 年 11 月 19 日更新:由于 Nexus 框架已停止使用,本教程已更新为使用 Nexus Schema
我们的技术栈
首先,让我们看看我们选择的工具:
- TypeScript - 后端和前端的编程语言
- React和Next.js - 作为前端框架和中端
- Urql GraphQL 客户端- 前端的 GraphQL 客户端
- PostgreSQL - 应用程序的数据库
- Apollo Server——我们将用来服务 GraphQL API 的服务器框架
- Nexus Schema - 用于构建代码优先GraphQL API 的库
- Prisma Client和
prisma db push
- 用于更改数据库模式、访问和查询数据库的工具包(注意:prisma db push
目前仍处于预览状态)
开始吧!🚀
步骤 0:安装 VS Code 扩展
在开始之前,请确保您已经安装了这些用于语法高亮和自动格式化的 VS Code 扩展 - Prisma和GraphQL。


步骤 1:启动 PostgreSQL 数据库
您首先需要的是一个 PostgreSQL 数据库实例,以便在开发过程中进行交互。
有很多方法可以实现这一点,但 Heroku 允许我们免费托管 PostgreSQL 数据库,并且只需极少的设置。查看Nikolas Burk的这篇文章,它会指导你完成整个过程!
如果您已经安装了 Docker 并且希望将开发数据库保留在本地,您还可以查看我制作的有关如何使用 Docker Compose 执行此操作的视频。
您将能够获得以下格式的 PostgreSQL URI:
postgresql://<USER>:<PASSWORD>@<HOST_NAME>:<PORT>/<DB_NAME>
postgres
注意:如果您使用的是 Heroku,您将获得一个以 而不是作为协议的URIpostgresql
。两种格式都可以使用,但我们更喜欢使用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
Git 应该由 自动初始化create-next-app
,并且您的项目结构应该如下所示:

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/schema
和nexus-plugin-prisma
作为常规依赖项和@prisma/cli
开发依赖项。
常规依赖项:
npm i @prisma/client @nexus/schema nexus-plugin-prisma
开发依赖项:
npm i @prisma/cli
安装依赖项后,在项目中初始化 Prisma。
npx prisma init
此命令将创建一个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
}
模式文件告诉 Prisma 使用 PostgreSQL 作为数据库类型,并将数据库连接 URL 定义为环境变量。它还定义了一个User
带有id
和name
字段的简单数据模型。
您的项目目前看起来应该是这样的:

prisma
目录 后的项目结构
步骤 4:将 Nexus Schema 与 Next.js 连接起来
Nexus Schema 是一个允许我们构建代码优先的 GraphQL API 的库。我们负责提供一个服务器来服务该 API。为了达到我们的目的,我们将使用Apollo Server。
Apollo Server 有多种版本,可用于各种用途。对于我们的项目,我们选择它apollo-server-mirco
,因为它非常适合无服务器部署。
npm install apollo-server-micro
要创建 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'
});
由于/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'
}
]
}
});
对 的调用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 };
}
文件结构现在看起来应该是这样的:
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
有了这些文件,运行应用程序:
npx next dev
如果你访问http://localhost:3000/api/graphql,你会看到 GraphQL Playground 已经启动并运行(使用我们的“hello world”模式)!😃

步骤 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]
// ...
});
如果您键入上述代码而不是复制粘贴,您会注意到 VS Code 将自动完成先前定义的数据模型上可用的字段( id
, ) 。name
User
/prisma/schema.prisma
提示:您也可以随时使用 调用智能感知CTRL + SPACE,以防它有时不会自动显示,这非常有用!
现在,返回 GraphQL Playground 并切换Schema侧面板 - 您将看到User
从刚刚在文件中编写的代码生成了一个 GraphQL 对象类型/graphql/schema.ts
。
type User {
id: String!
name: String!
}
步骤 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({});
}
});
}
});
您可以在函数中执行任何您想做的事情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();
}
});
上面的代码将生成一个 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
}
请注意,所有相关Input
类型也都是免费为我们生成的!💯

Query
类型由 Nexus 生成
步骤 5.3:定义突变类型
与类型类似Query
,Mutation
类型可以用mutationType
函数来定义。
😈 让我们玩得开心一点,创建一个bigRedButton
突变来销毁数据库中的所有用户数据。
我们还可以访问t.crud
这里的辅助函数,它公开了数据库的基本“创建”、“更新”和“删除”操作。然后我们必须将数据添加Mutation
到types
数组中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]
// ...
});
这将生成如下所示的 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!
}

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
太棒了!数据库准备好后,就可以回到http://localhost:3000/api/graphql,体验一下你的第一个 Nexus GraphQL API 了。我给你举个例子,方便你操作!
mutation {
createOneUser(data: { name: "Alice" }) {
id
}
}

步骤 7:使用 Next.js 设置 Urql GraphQL 客户端
我们将使用Urql作为前端的 GraphQL 客户端,但您可以使用任何您喜欢的库。
首先,安装依赖项:
npm install graphql-tag next-urql react-is urql isomorphic-unfetch
然后,在 处创建一个新文件/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
);
就这样!现在,您可以在 Next.js 应用的任何页面中使用 GraphQL 客户端。
步骤 8:使用 GraphQL 客户端
首先,在 创建一个 TSX 文件/components/AllUsers.tsx
。该文件将包含一个组件,用于执行allUsers
GraphQL 查询并将结果渲染为列表。这样,我们就可以使用该组件从 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
}
}
`;
因为已知您将要获取的数据是一个User
对象数组(感谢 GraphQL 模式!),所以您还可以定义一个新类型:
type AllUsersData = {
allUsers: {
id: string;
name: string;
}[];
};
接下来,创建将使用查询的 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;
现在,保存 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;
是时候启动 Next.js 开发服务器了!
npm run dev
瞧!用户列表渲染出来了!🥳

步骤 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
}
}
`;
将 GraphQL 操作与组件分离使得导航代码库变得更加容易。
接下来,安装作为开发依赖项所需的软件包graphql-code-generator
:
npm install -D \
@graphql-codegen/cli \
@graphql-codegen/typescript \
@graphql-codegen/typescript-operations \
@graphql-codegen/typescript-urql
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
上述配置将告诉graphql-code-generator
从 中提取 GraphQL 模式http://localhost:3000/api/graphql
,然后生成类型、UrqluseQuery
挂钩到位于 的文件中/generated/graphql.tsx
。
太棒了,让我们开始代码生成(在监视模式下)!
npx graphql-codegen --watch
您将在中看到一些由机器人编写的漂亮代码/generated/graphql.tsx
。多么简洁啊!

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;
重新访问应用程序的索引页http://localhost:3000,一切正常!🙌
为了让开发体验更加美好,我们对项目的NPM脚本进行优化。
首先,安装Concurrently NPM 模块,它是同时运行多个 CLI 观察器的绝佳工具:
npm install -D concurrently
然后,用以下内容替换文件dev
中的脚本:package.json
{
// ...
"scripts": {
// ...
"dev": "concurrently -r \"npx nexus dev\" \"npx next\" \"npx graphql-codegen --watch\""
// ...
}
// ...
}
现在,我们可以使用单个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