舊版 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 schema
- 內省您的資料庫並解決潛在的schema 不相容性
- 安裝並產生 Prisma Client
本指南進一步假設您具有類似以下的檔案設定
.
├── 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 schema - 一個名為
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 模型,您可以使用新增至傳遞到 Nexus
definition
函數中的t
引數的t.model
屬性。t.model
可讓您存取 Prisma 模型的屬性並公開它們。 - 透過 Nexus 公開 Prisma 模型的 CRUD 運算遵循類似的方法。這些會透過
queryType
和mutationType
類型的definition
函數中的t.crud
公開。
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 schema 中的模型進行比對。在此案例中,它會與 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 schema,一旦這些類型也成為 GraphQL schema 的一部分,錯誤就會消失!
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 schema 中的模型進行比對。在此案例中,它會與 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 schema 中的模型進行比對。在此案例中,它會與 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 schema 中的模型進行比對。在此案例中,它會與 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 查詢
在本節中,您將把所有 GraphQL 查詢從先前版本的 nexus
和 nexus-prisma
遷移到最新版本的 @nexus/schema
和 nexus-plugin-prisma
。
4.1.1. 遷移 users
查詢
在我們的範例 API 中,範例 GraphQL schema 中的 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,
})
},
})
回想一下,crud
屬性是由 nexus-plugin-prisma
新增至 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 Client 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 } },
],
},
})
},
})
},
})
請注意,db
物件會由 nexus-plugin-prisma
自動附加到 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 Client 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 變異
在本節中,您將把範例 schema 中的 GraphQL 變異遷移到最新版本的 @nexus/schema
和 nexus-plugin-prisma
。
4.2.1. 遷移 createUser
變異
在我們的範例 API 中,範例 GraphQL schema 中的 createUser
變異實作如下。
const Mutation = prismaObjectType({
name: 'Mutation',
definition(t) {
t.prismaFields(['createUser'])
},
})
若要使用最新版本的 @nexus/schema
和 nexus-plugin-prisma
取得相同的行為,您需要在 t.crud
上呼叫 createOneUser
函數,並傳遞 alias
以重新命名 GraphQL schema 中的欄位為 createUser
(否則它會以使用的函數命名為 createOneUser
)
const Query = queryType({
definition(t) {
t.crud.createOneUser({
alias: 'createUser',
})
},
})
回想一下,crud
屬性是由 nexus-plugin-prisma
新增至 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 Client 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 Client 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 Client 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 伺服器。