旧 Nexus 到新 Nexus
概述
注意:本指南并非完全最新,因为它目前使用的是 已弃用 版本的
nexus-plugin-prisma
。虽然这仍然可以使用,但建议使用新的nexus-prisma
库或其他代码优先 GraphQL 库,例如 Pothos。如果您有任何问题,请随时在我们的 Discord 上分享。
本升级指南介绍如何升级基于 Prisma 1 并使用 nexus
(< v0.12.0) 或 @nexus/schema
以及 nexus-prisma
(< v4.0.0) 来实现 GraphQL 服务器的项目。
代码将升级到 @nexus/schema
的最新版本。此外,nexus-prisma
包将替换为新的 nexus-plugin-prisma
。
本指南假设您已经完成了 升级 Prisma ORM 层的指南。这意味着您已经
- 安装了 Prisma ORM 2 CLI
- 创建了 Prisma ORM 2 架构
- 内省了您的数据库并解决了潜在的 架构不兼容性
- 安装并生成了 Prisma 客户端
本指南还假设您拥有类似于以下内容的文件设置
.
├── README.md
├── package.json
├── prisma
│ └── schema.prisma
├── prisma1
│ ├── datamodel.prisma
│ └── prisma.yml
└── src
├── generated
│ ├── nexus-prisma
│ ├── nexus.ts
│ ├── prisma-client
│ └── schema.graphql
├── types.ts
└── index.ts
重要的部分是
- 一个名为
prisma
的文件夹,其中包含您的 Prisma ORM 2 架构 - 一个名为
src
的文件夹,其中包含您的应用程序代码
如果您的项目结构与此不同,则需要调整指南中的说明以匹配您的设置。
1. 升级 Nexus 依赖项
首先,您可以删除旧的 Nexus 和 Prisma 1 依赖项
npm uninstall nexus nexus-prisma prisma-client-lib prisma1
然后,您可以在项目中安装最新的 @nexus/schema
依赖项
npm install @nexus/schema
接下来,安装 Nexus 的 Prisma ORM 插件,这将允许您在 GraphQL API 中公开 Prisma ORM 模型(这是以前 nexus-prisma
包的新等效项)
npm install nexus-plugin-prisma
nexus-plugin-prisma
依赖项捆绑了所有必需的 Prisma ORM 依赖项。因此,您应该删除在升级应用程序的 Prisma ORM 层时添加安装的依赖项
npm uninstall @prisma/cli @prisma/client
但是请注意,您仍然可以使用熟悉的命令调用 Prisma ORM 2 CLI
npx prisma -v
注意:如果您在运行
npx prisma -v
时看到 Prisma 1 CLI 的输出,请确保删除您的node_modules
文件夹并重新运行npm install
。
2. 更新 Nexus 和 Prisma ORM 的配置
首先,您可以删除不再需要的旧导入
import { makePrismaSchema, prismaObjectType } from 'nexus-prisma'
import datamodelInfo from './generated/nexus-prisma'
import { prisma } from './generated/prisma-client'
相反,您现在将以下内容导入到您的应用程序中
import { nexusSchemaPrisma } from 'nexus-plugin-prisma/schema'
import { objectType, makeSchema, queryType, mutationType } from '@nexus/schema'
import { PrismaClient } from '@prisma/client'
接下来,您需要调整当前创建 GraphQLSchema
的代码,这很可能目前是通过代码中的 makePrismaSchema
函数完成的。由于此函数是从已删除的 nexus-prisma
包中导入的,因此您需要将其替换为 @nexus/schema
包中的 makeSchema
函数。Nexus 的 Prisma ORM 插件的使用方式在最新版本中也有所改变。
以下是这种配置的示例
const schema = makePrismaSchema({
const schema = makeSchema({
// Provide all the GraphQL types we've implemented
types: [Query, Mutation, UserUniqueInput, User, Post, Category, Profile],
// Configure the interface to Prisma
prisma: {
datamodelInfo,
client: prisma,
},
plugins: [nexusSchemaPrisma({
experimentalCRUD: true,
})],
// Specify where Nexus should put the generated files
outputs: {
schema: path.join(__dirname, './generated/schema.graphql'),
typegen: path.join(__dirname, './generated/nexus.ts'),
},
// Configure nullability of input arguments: All arguments are non-nullable by default
nonNullDefaults: {
input: false,
output: false,
},
// Configure automatic type resolution for the TS representations of the associated types
typegenAutoConfig: {
sources: [
{
source: path.join(__dirname, './types.ts'),
alias: 'types',
},
],
contextType: 'types.Context',
},
})
如果您以前键入了通过解析器链传递的 GraphQL context
对象,则需要像这样调整类型
import { Prisma } from './generated/prisma-client'
import { PrismaClient } from '@prisma/client'
export interface Context {
prisma: Prisma
prisma: PrismaClient
}
3. 迁移 GraphQL 类型
以下是对使用 @nexus/schema
和 nexus-plugin-prisma
的最新版本创建 GraphQL 类型两种方法的主要差异的简要概述。
prismaObjectType
函数不再可用,所有类型都使用 Nexus 的objectType
函数创建。- 要通过 Nexus 公开 Prisma 模型,您可以使用
t.model
属性,该属性已添加到传递给 Nexusdefinition
函数的t
参数中。t.model
使您能够访问 Prisma 模型的属性并让您公开它们。 - 通过 Nexus 公开 Prisma 模型的 CRUD 操作遵循类似的方法。这些是通过
t.crud
在queryType
和mutationType
类型的definition
函数中公开的。
3.1. 迁移 Post
类型
使用之前的 nexus-prisma
包的类型定义
在示例应用程序中,User
类型定义如下
const User = prismaObjectType({
name: 'User',
definition(t) {
t.prismaFields([
'id',
'name',
'email',
'jsonData',
'role'
{
name: 'posts',
args: [], // remove the arguments from the `posts` field of the `User` type in the Prisma schema
},
])
},
})
使用 @nexus/schema
和 nexus-plugin-prisma
的最新版本的类型定义
使用 @nexus/schema
的最新版本,您现在可以访问主 schema
实例上的 objectType
函数,并像这样公开 Prisma 模型中的所有字段
const User = objectType({
name: 'User',
definition(t) {
t.model.id()
t.model.name()
t.model.email()
t.model.jsonData()
t.model.role()
t.model.posts({
pagination: false,
ordering: false,
filtering: false,
})
t.model.profile()
},
})
请注意,t.model
会查看传递给 objectType
函数的参数中的对象的 name
属性,并将其与 Prisma 架构中的模型进行匹配。在这种情况下,它会与 User
模型进行匹配。因此,t.model
公开了以 User
模型的字段命名的函数。
此时,您可能会在关系字段 posts
和 profile
上看到错误,例如
//delete-next-line
Missing type Post, did you forget to import a type to the root query?
这是因为您尚未将 Post
和 Profile
类型添加到 GraphQL 架构中,这些错误将在这些类型也成为 GraphQL 架构的一部分后消失!
3.2. 迁移 Post
类型
使用之前的 nexus-prisma
包的类型定义
在示例应用程序中,Post
类型定义如下
const Post = prismaObjectType({
name: 'Post',
definition(t) {
t.prismaFields(['*'])
},
})
prismaFields
中的星号表示公开所有 Prisma 字段。
使用最新版本的 @nexus/schema
和 nexus-plugin-prisma
定义类型
使用最新版本的 @nexus/schema
,您需要显式地公开所有字段,没有选项可以只公开 Prisma 模型中的所有内容。
因此,Post
的新定义必须显式列出其所有字段
const Post = objectType({
name: 'Post',
definition(t) {
t.model.id()
t.model.title()
t.model.content()
t.model.published()
t.model.author()
t.model.categories()
},
})
请注意,t.model
会查看 name
属性,并将其与 Prisma 模式中的模型进行匹配。在本例中,它与 Post
模型匹配。因此,t.model
公开以 Post
模型的字段命名的函数。
3.3. 迁移 Profile
类型
使用之前的 nexus-prisma
包定义类型
在示例应用程序中,Profile
类型定义如下
const Profile = prismaObjectType({
name: 'Profile',
definition(t) {
t.prismaFields(['*'])
},
})
prismaFields
中的星号表示公开所有 Prisma 字段。
使用最新版本的 @nexus/schema
和 nexus-plugin-prisma
定义类型
使用最新版本的 @nexus/schema
,您需要显式地公开所有字段,没有选项可以只公开 Prisma 模型中的所有内容。
因此,Profile
的新定义必须显式列出其所有字段
const Profile = objectType({
name: 'Profile',
definition(t) {
t.model.id()
t.model.bio()
t.model.user()
t.model.userId()
},
})
请注意,t.model
会查看 name
属性,并将其与 Prisma 模式中的模型进行匹配。在本例中,它与 Profile
模型匹配。因此,t.model
公开以 Profile
模型的字段命名的函数。
3.4. 迁移 Category
类型
使用之前的 nexus-prisma
包定义类型
在示例应用程序中,Category
类型定义如下
const Category = prismaObjectType({
name: 'Category',
definition(t) {
t.prismaFields(['*'])
},
})
prismaFields
中的星号表示公开所有 Prisma ORM 字段。
使用最新版本的 @nexus/schema
和 nexus-plugin-prisma
定义类型
使用最新版本的 @nexus/schema
,您需要显式地公开所有字段,没有选项可以只公开 Prisma 模型中的所有内容。
因此,Category
的新定义必须显式列出其所有字段
const Category = objectType({
name: 'Category',
definition(t) {
t.model.id()
t.model.name()
t.model.posts({
pagination: true,
ordering: true,
filtering: true,
})
},
})
请注意,t.model
会查看 name
属性,并将其与 Prisma 模式中的模型进行匹配。在本例中,它与 Category
模型匹配。因此,t.model
公开以 Category
模型的字段命名的函数。
4. 迁移 GraphQL 操作
下一步,您可以开始将所有 GraphQL 查询和突变从“以前”的 GraphQL API 迁移到新的 API。
对于本指南,将使用以下示例 GraphQL 操作
input UserUniqueInput {
id: String
email: String
}
type Query {
posts(searchString: String): [Post!]!
user(userUniqueInput: UserUniqueInput!): User
users(where: UserWhereInput, orderBy: Enumerable<UserOrderByInput>, skip: Int, after: String, before: String, first: Int, last: Int): [User]!
}
type Mutation {
createUser(data: UserCreateInput!): User!
createDraft(title: String!, content: String, authorId: ID!): Post
updateBio(userUniqueInput: UserUniqueInput!, bio: String!): User
addPostToCategories(postId: String!, categoryIds: [String!]!): Post
}
4.1. 迁移 GraphQL 查询
在本节中,您将从 nexus
和 nexus-prisma
的先前版本迁移所有 GraphQL 查询到 @nexus/schema
和 nexus-plugin-prisma
的最新版本。
4.1.1. 迁移 users
查询
在我们的示例 API 中,示例 GraphQL 模式中的 users
查询实现如下。
const Query = prismaObjectType({
name: 'Query',
definition(t) {
t.prismaFields(['users'])
},
})
要使用新的 Nexus 获取相同的行为,您需要在 t.crud
上调用 users
函数
schema.queryType({
definition(t) {
t.crud.users({
filtering: true,
ordering: true,
pagination: true,
})
},
})
回想一下,nexus-plugin-prisma
将 crud
属性添加到 t
(使用与 t.model
相同的机制)。
4.1.2. 迁移 posts(searchString: String): [Post!]!
查询
在示例 API 中,posts
查询实现如下
queryType({
definition(t) {
t.list.field('posts', {
type: 'Post',
args: {
searchString: stringArg({ nullable: true }),
},
resolve: (parent, { searchString }, context) => {
return context.prisma.posts({
where: {
OR: [
{ title_contains: searchString },
{ content_contains: searchString },
],
},
})
},
})
},
})
此查询唯一需要更新的是对 Prisma ORM 的调用,因为新的 Prisma 客户端 API 与 Prisma 1 中使用的 API 略有不同。
queryType({
definition(t) {
t.list.field('posts', {
type: 'Post',
args: {
searchString: stringArg({ nullable: true }),
},
resolve: (parent, { searchString }, context) => {
return context.prisma.post.findMany({
where: {
OR: [
{ title: { contains: searchString } },
{ content: { contains: searchString } },
],
},
})
},
})
},
})
请注意,nexus-plugin-prisma
会自动将 db
对象附加到 context
。它代表您的 PrismaClient
的一个实例,它使您能够在解析器中向数据库发送查询。
4.1.3. 迁移 user(uniqueInput: UserUniqueInput): User
查询
在示例 API 中,user
查询实现如下
inputObjectType({
name: 'UserUniqueInput',
definition(t) {
t.string('id')
t.string('email')
},
})
queryType({
definition(t) {
t.field('user', {
type: 'User',
args: {
userUniqueInput: schema.arg({
type: 'UserUniqueInput',
nullable: false,
}),
},
resolve: (_, args, context) => {
return context.prisma.user({
id: args.userUniqueInput?.id,
email: args.userUniqueInput?.email,
})
},
})
},
})
您现在需要调整对 prisma
实例的调用,因为新的 Prisma 客户端 API 与 Prisma 1 中使用的 API 略有不同。
const Query = queryType({
definition(t) {
t.field('user', {
type: 'User',
args: {
userUniqueInput: arg({
type: 'UserUniqueInput',
nullable: false,
}),
},
resolve: (_, args, context) => {
return context.prisma.user.findUnique({
where: {
id: args.userUniqueInput?.id,
email: args.userUniqueInput?.email,
},
})
},
})
},
})
4.2. 迁移 GraphQL 突变
在本节中,您将从示例模式迁移 GraphQL 突变到 @nexus/schema
和 nexus-plugin-prisma
的最新版本。
4.2.1. 迁移 createUser
突变
在我们的示例 API 中,示例 GraphQL 模式中的 createUser
突变实现如下。
const Mutation = prismaObjectType({
name: 'Mutation',
definition(t) {
t.prismaFields(['createUser'])
},
})
要使用 @nexus/schema
和 nexus-plugin-prisma
的最新版本获取相同的行为,您需要在 t.crud
上调用 createOneUser
函数,并传递一个 alias
以将 GraphQL 模式中的字段重命名为 createUser
(否则它将被称为 createOneUser
,因为它是使用的函数的名称)。
const Query = queryType({
definition(t) {
t.crud.createOneUser({
alias: 'createUser',
})
},
})
回想一下,nexus-plugin-prisma
将 crud
属性添加到 t
(使用与 t.model
相同的机制)。
4.2.2. 迁移 createDraft(title: String!, content: String, authorId: String!): Post!
查询
在示例应用程序中,createDraft
突变实现如下。
mutationType({
definition(t) {
t.field('createDraft', {
type: 'Post',
args: {
title: stringArg({ nullable: false }),
content: stringArg(),
authorId: stringArg({ nullable: false }),
},
resolve: (_, args, context) => {
return context.prisma.createPost({
title: args.title,
content: args.content,
author: {
connect: { id: args.authorId },
},
})
},
})
},
})
您现在需要调整对 prisma
实例的调用,因为新的 Prisma 客户端 API 与 Prisma 1 中使用的 API 略有不同。
const Mutation = mutationType({
definition(t) {
t.field('createDraft', {
type: 'Post',
args: {
title: stringArg({ nullable: false }),
content: stringArg(),
authorId: stringArg({ nullable: false }),
},
resolve: (_, args, context) => {
return context.prisma.post.create({
data: {
title: args.title,
content: args.content,
author: {
connect: { id: args.authorId },
},
},
})
},
})
},
})
4.2.3. 迁移 updateBio(bio: String, userUniqueInput: UserUniqueInput!): User
突变
在示例 API 中,updateBio
突变定义和实现如下。
mutationType({
definition(t) {
t.field('updateBio', {
type: 'User',
args: {
userUniqueInput: arg({
type: 'UserUniqueInput',
nullable: false,
}),
bio: stringArg(),
},
resolve: (_, args, context) => {
return context.prisma.updateUser({
where: {
id: args.userUniqueInput?.id,
email: args.userUniqueInput?.email,
},
data: {
profile: {
create: { bio: args.bio },
},
},
})
},
})
},
})
您现在需要调整对 prisma
实例的调用,因为新的 Prisma 客户端 API 与 Prisma 1 中使用的 API 略有不同。
const Mutation = mutationType({
definition(t) {
t.field('updateBio', {
type: 'User',
args: {
userUniqueInput: arg({
type: 'UserUniqueInput',
nullable: false,
}),
bio: stringArg(),
},
resolve: (_, args, context) => {
return context.prisma.user.update({
where: {
id: args.userUniqueInput?.id,
email: args.userUniqueInput?.email,
},
data: {
profile: {
create: { bio: args.bio },
},
},
})
},
})
},
})
4.2.4. 迁移 addPostToCategories(postId: String!, categoryIds: [String!]!): Post
突变
在示例 API 中,addPostToCategories
突变定义和实现如下。
mutationType({
definition(t) {
t.field('addPostToCategories', {
type: 'Post',
args: {
postId: stringArg({ nullable: false }),
categoryIds: stringArg({
list: true,
nullable: false,
}),
},
resolve: (_, args, context) => {
const ids = args.categoryIds.map((id) => ({ id }))
return context.prisma.updatePost({
where: {
id: args.postId,
},
data: {
categories: { connect: ids },
},
})
},
})
},
})
您现在需要调整对 prisma
实例的调用,因为新的 Prisma 客户端 API 与 Prisma 1 中使用的 API 略有不同。
const Mutation = mutationType({
definition(t) {
t.field('addPostToCategories', {
type: 'Post',
args: {
postId: stringArg({ nullable: false }),
categoryIds: stringArg({
list: true,
nullable: false,
}),
},
resolve: (_, args, context) => {
const ids = args.categoryIds.map((id) => ({ id }))
return context.prisma.post.update({
where: {
id: args.postId,
},
data: {
categories: { connect: ids },
},
})
},
})
},
})
5. 清理
5.1. 清理 npm 依赖项
如果您还没有,现在可以卸载与 Prisma 1 设置相关的依赖项
npm uninstall prisma1 prisma-client-lib
5.2. 删除未使用的文件
接下来,删除 Prisma 1 设置的文件
rm -rf src/generated
rm -rf prisma1
5.3. 停止 Prisma ORM 服务器
最后,您可以停止运行 Prisma ORM 服务器。