写点什么

使用 Next.js , Nexus, Prisma 构建全栈项目

用户头像
夏木
关注
发布于: 2020 年 09 月 02 日
使用 Next.js , Nexus, Prisma 构建全栈项目



此教程翻译自《Complete Intruduction to Fullstack,Type-Safe GraphQL(feat.Next.js,Nexus,Prisma)



我们将要从模板中构建全栈项目,会使用 Next.js ,Nexus,Prisma ,来实现 React 的后端渲染,GraphQL 接口。

技术栈浅析

首先,我们浅析一下我们选择的技术栈。



  • TypeScript - 前后端使用统一的编程语言

  • React 和 Next.js - 前端开发框架 React ,以及React 的服务端渲染库

  • Urql GraphQL client - GraphQL 的客户端

  • PostgreSQL - 数据库

  • Nexus - 一个 code-first 的 GraphQL 服务端

  • Prisma Client 和 Prisma Migrate - 用于数据库 ORM 操作的工具库(注意:Prisma Migrate 仍然在实验阶段)



现在,我们开始吧!

配置开发环境

在我们开始编写代码前,首先要配置开发环境所需要的软件,以便我们能够使用轻松的方式编写代码。我们使用的软件编辑器是 VS Code , 它的插件库里提供了 Prisma 和 GraphQL 的代码高亮和自动格式化工具,我们先在 VS Code 中安装下面两个插件。

截屏2020-08-30 上午10.02.16.png接下来,我们要安装 [docker](https://www.docker.com/get-started) (我们的 PostgreSQL 数据库将在 docker 容器中运行,当然其他的云数据库或本地安装的数据库亦可),具体安装教程请前往 [docker 官网](https://www.docker.com/get-started)

注意事项

我们的开发工具是 mac pro 笔记本电脑,在 window 系统下的 pc 电脑运行结果如有不同,请先自行查找原因,然后在评论区留言讨论。

在命令行下运行指令,如无特别说明,一律是在项目根目录下运行。

新建一个 Next.js 项目

我们可以使用 ceate-next-app 工具包新建一个 Next.js 项目。在命令行工具输入下面的指令:

npx create-next-app prisma-next-nexus --use-yarn -e with-typescript

create-next-app 会使用 Git 自动下载项目初始化代码,并自动安装所有的依赖。指令运行完成以后,在 VS Code 打开项目目录,可以看到下图所示的项目结构。

<a name="yeBUt"></a>

安装 Nexus 和 Prisma

现在,我们安装 Nexus 框架和 nexus-plugin-prisma 包。在命令行工具中运行下面的指令:

yarn add nexus @prisma/client && yarn add @prisma/cli -D

Nexus 包含了提供 Typescript 语言类型编译支持的插件 Nexus TypeScript Language Service Plugin ,我们只需要在 Typescript 配置文件 tsconfig.json 中配置即可,修改代码如下:

{
"compilerOptions": {
// ...
"noEmit": true,
"rootDir": ".",
"typeRoots": ["node_modules/@types", "types"],
"plugins": [{ "name": "nexus/typescript-language-service" }] // 新增这一行
},
// ...
"include": ["**/*.ts", "**/*.tsx", ".", "types.d.ts"]
}



使用 Docker 启动数据库

我们使用 docker-compose 保存 postgresql 容器的设置,在项目根目录下添加 docker-compose-yml 文件,文件内容如下:

version: '3.1'
services:
db:
image: postgres:11.7
container_name: prisma-next-nexus-postgre
restart: always
environment:
POSTGRES_USER: prismaNextNexus
POSTGRES_PASSWORD: ${POSTGRESPWD}
ports:
- 54333:5432
volumes:
- ./db/postgresql:/var/lib/postgresql/data

其中 ${POSTGRESPWD} 是保存在 .env 文件中的数据库密码变量。同样的,在根目录下新建 .env 文件,并添加下面的代码:

POSTGRESPWD=xxxxxx // 密码

然后在命令行中输入如下指令:

docker-compose up -d

启动过程没有报错,即成功,命令会自动退出。 -d 时表示docker 容器在后台运行。

Prisma 连接数据库

我们要使用 Prisma 连接到上一步启动的 postgresql 数据库容器。首先,我们在根目录下新建 prisma 文件夹,然后在其中添加 .env 文件,然后在 .env 文件中添加环境变量 DATABASE_URL ,代码如下“

DATABASE_URL="postgresql://prismaNextNexus:xxx@localhost:54333/postgres"

别忘了替换数据库的密码    



因为 .env 文件里包含了密码这样的敏感信息,不应该提交到 git 仓库中,所以在 .gitignore 中添加省略 .env 。

最后,我们需要在 /prisma 创建 schema 文件,用于设置数据库配置,和数据库 model 描述。具体代码如下:

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model Hello {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
}



使用 Prisma 初始化数据库表结构

schema.prisma 写入的 model 描述就是数据库表的描述。我们接下来就要使用 @prisma/cli 工具在 数据库中自动创建,更新数据表。

在命令行工具中输入下面的指令:

yarn prisma migrate save --name init --experimental

运行结果如下图:

此时,Prisma 已经在 prisma 目录下创建了连接文件 migration 。截屏2020-08-30 下午3.37.04.png

最后,将连接的信息推送到数据中使用下面的指令:

yarn run prisma migrate up --experimental

运行完成以后,可以在数据库中看到我们在 prisma.schema 定义的 Hello 。<br />

我们使用的数据库 UI 工具是 TablePlus。



连接 Nexus 和 Next.js

Next.js 的 API routes 功能非常方便,我们只需要添加 /pages/api/graphql.ts 文件,然后启动 next.js 服务,就可以在 http://app-domain/api/graphql 访问 GraphqQL 服务。<br />在 graphql.ts 文件中编写下面的代码:

import app, { server } from "nexus";
import "../../graphql/schema"; // we'll create this file in a second!
app.assemble();
export default server.handlers.graphql;

下面,我们在根目录创建 graphql 目录,并且在这个目录下新建 schema.ts 文件。在 schema.ts 文件中,我们首先初始化 nexus-plugin-prisma 插件,设置 CRUD 功能,并通过 plugin 和 nexus 连接。具体代码如下:

import { use } from "nexus";
import { prisma } from "nexus-plugin-prisma";
use(prisma({ features: { crud: true } }));

最后,在命令行中启动 nexus 服务

yarn run nexus dev

<a name="Kq7dR"></a>



此时,graphql 已经启动,访问 localhost:4000/graphql 可以看到 GraphQL Playground 已经运行(目前只有空的 schema)。<br />



编写 GrahphQL API

现在,我们开始写一个 API。

<a name="EpFFM"></a>

定义对象类型

首先,我们在 graphql/shema.ts 中定义一个 User 对象类型,这里使用的 nexus 定义对象的语法:

schema.objectType({
name: "User",
definition(t) {
t.model.id();
t.model.name();
},
})

当你在 VS Code 中编写上面代码时,VS Code 将会自动完成这些字段( id , name )。这是因为我们已经在 prisma/schema.prisma 中定义好了 User model。<br />现在,打开 Grapqhl Playground 并切换到 Schema 标签页。你会看到已经添加了 Grqphql 对象类型 User 。<br />



定义 Query 类型

Nexus 使用 schema.queryType 函数来定义 root Query 。<br />我们写一个查询全部 User 的 query - allUsers 。<br />具体代码如下:

schema.queryType({
definition(t) {
t.list.field("allUsers", {
type: "User",
resolve(_parent, _args, ctx) {
return ctx.db.user.findMany();
},
});
},
});

resolve 函数中,可以添加实际的逻辑代码。Prisma 客户端里可以操作 数据的实例 db 包含在上下文对象 ctx 中。 更多关于 Prisma 客户端 API 的信息可以在官网文档中了解。<br />Nexus-Prisma plugin 也包含了从数据库读取数据的方法,下面的代码可以直接定义读取 user users 的 query。

schema.queryType({
definition(t) {
t.list.field("allUsers", {
type: "User",
resolve(_parent, _args, ctx) {
return ctx.db.user.findMany();
},
});
t.crud.user(); // nexus-prisma 定义的
t.crud.users(); // nexus-prisma 定义的
},
});

别忘了,查询单个 user 时传入参数的 Input  类型描述也会自动生成。

input UserWhereUniqueInput {
id: String
}

<a name="Evowg"></a>

定义 Mutation 类型

举一反三,和 Query type 类似, Mutation  类型使用 schema.mutationType 类定义。<br />接下来,让我们来创建一个 bigRedButton mutation 用来删除所有的 user 数据。代码如下:

schema.mutationType({
definition(t) {
t.field("bigRedButton", {
type: "String",
async resolve(_parent, _args, ctx) {
const { count } = await ctx.db.user.deleteMany({});
return `${count} user(s) destroyed.`;
},
});
},
});

同样的,nexus-prisma plutin 也为 mutation 提供了很多 CURD 函数。

schema.mutationType({
definition(t) {
t.field("bigRedButton", {
type: "String",
async resolve(_parent, _args, ctx) {
const { count } = await ctx.db.user.deleteMany({});
return `${count} user(s) destroyed.`;
},
});
t.crud.createOneUser(); // 创建一个用户
t.crud.deleteOneUser(); // 删除一个用户
t.crud.updateOneUser(); // 更新用户信息
t.crud.updateManyUser(); // 更新多个用户信息
},
});

自动生成的 GraphQL schema 如下:

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



现在,我们完整的 GraphqlQL API 已经完成了!接下来,我们尝试在 React 中使用 GraphQL 发送请求进行增删改查。

在 React 中使用 GraphQL

我们将会在前端代码中使用 Urql 。当然,也可以使用其他你熟悉的 GrphQL 客户端,比如 Apollo client 等。

设置 Urql GraphQL 客户端

首先,在命令行工具运行下面的安装指令:

yarn add graphql-tag next-urql react-is urql isomorphic-unfetch

然后,在 /pages 目录下新增 _app.tsx 文件。这个文件是特殊的 Next.js 组件,它会在初始化每一个页面时都会被执行。<br />我们在 /pages/_app.tsx 文件中添加下面的代码:

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
);

现在,在前端所有页面都可以使用 GrphQL 客户端来发送网络请求了!



使用 GraphQL client 查询所有用户

首先,在根目录下创建 components 文件夹,用来保存所有的组件文件。在 components 文件夹下创建 AllUsers.tsx 。 AllUsers.tsx 文件里定义 React 函数组件,组件内部将会请求 allUsers GraphQL query 并把查询返回的结果显示出来。<br />我们现在 AllUser.tsx 中定义 gql 查询语句,如下代码:

import gql from "graphql-tag";
const AllUsersQuery = gql`
query AllUsers {
allUsers {
id
name
}
}
`;

接下来,定义查询请求返回后的数据类型,我们知道返回数据是一个包好所有 User 对象的数组。所以,我们定义如下类型:

type AllUsersData = {
allUsers: {
id: string;
name: string;
}[];
}

最后,我们完成 React 函数组件,并发送请求获取所有的 User 数据。

import React from "react";
import {useQuery} from 'urql';
...
export default function AllUsers() {
const [result] = useQuery<AllUsersData>({ // 发送请求,更多 API 信息请查看 urql 官网
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>
);
}

完整版的函数组件代码如下:

import React from "react";
import {useQuery} from 'urql';
import gql from "graphql-tag";
const AllUsersQuery = gql`
query AllUsers {
allUsers {
id
name
}
}
`;
type AllUsersData = {
allUsers: {
id: string;
name: string;
}[];
}
export default function AllUsers() {
const [result] = useQuery<AllUsersData>({ // 发送请求,更多 API 信息请查看 urql 官网
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>
);
}

最后,我们在 /pages/index.tsx 主页文件中渲染 AllUsers 组件,代码如下:

import AllUsers from "../components/AllUsers";
const IndexPage = () => (
<div>
<h1>Hello Next.js 👋</h1>
{/* === Tada! === */}
<AllUsers />
</div>
);
export default IndexPage;

🥳 接下来,我们在命令行启动 next.js App ~

yarn dev

最后,访问 localhost:3000 。<br />

<a name="b3dVY"></a>

自动生成 `useQuery` hooks 和 types

如果我们想要自动生成 GraphQL API 的类型描述而不是手动定义所有的,我们可以使用一个非常酷的工具 GraphQL Code Generator ,它可以从 Nexus GraphQL 入口 schema 描述中直接生成客户端所需的类型定义代码。这样,我们就只需在 schema.prisma 文件定义一次类型,然后,客户端所使用的类型都会自动通过 GraphQL Code Generator 自动生成。<br />首先,在命令行工具运行下面的代码来安装所需的包:

yarn add -D @graphql-codegen/cli @graphql-codegen/typescript \
@graphql-codegen/typescript-operations \
@graphql-codegen/typescript-urql

然后,我们在根目录下添加 codegen.yml 配置文件来告诉 GraphQL Code Generator 的 GraphQL schema 访问地址等信息,我们项目的配置信息如下:

overwrite: true
schema: "http://localhost:4000/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 目录下新建 queries.graphql.ts 文件,并将 components/AllUsers.tsx 中的 gql 标签对象剪切到 queries.graphql.ts 中。具体代码如下图:

import gql from "graphql-tag";
export const AllUsersQuery = gql`
query AllUsers {
allUsers {
id
name
}
}
`;

最后,别忘了启动 nexus ,之后我们就可以在命令行运行如下指令,来自动生成类型定义了:

yarn graphql-codegen



<br />如果希望开发过程中监听文件改变,自动生成类型定义的话,可以添加 --watch 参数。<br />指令运行成功以后,我们可以在产出目录 generated 中查看 graphql.tsx 文件内容,部分代码如下:

...
export function useAllUsersQuery(options: Omit<Urql.UseQueryArgs<AllUsersQueryVariables>, 'query'> = {}) {
return Urql.useQuery<AllUsersQuery>({ query: AllUsersDocument, ...options });
};
...

最后,我们在 components/AllUsers.tsx 中使用生成 useAllUsersQuery 函数来发送请求获取用户数据。代码如下:

import React, { useEffect } from "react";
import {
useAllUsersQuery,
} from "../generated/graphql";
export default function AllUsers() {
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>
);
}

<a name="MiabK"></a>

结尾

我们希望你能喜欢这篇实践教程,并学习到如何配置 prisma,nexus,nextjs 来完成全栈的开发!<br />你可以访问此 Github repo 下的 prisma-next-nexus 目录来查看完整代码。<br />



发布于: 2020 年 09 月 02 日阅读数: 89
用户头像

夏木

关注

还未添加个人签名 2019.01.16 加入

还未添加个人简介

评论

发布
暂无评论
使用 Next.js , Nexus, Prisma 构建全栈项目