跳到主要內容

舊版 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 外掛程式的使用方式在最新版本中也發生了變更。

以下是此類配置的範例

./src/index.ts
 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 物件,您需要像這樣調整類型

./src/types.ts
import { Prisma } from './generated/prisma-client'
import { PrismaClient } from '@prisma/client'

export interface Context {
prisma: Prisma
prisma: PrismaClient
}

3. 遷移您的 GraphQL 類型

以下快速概述使用最新版本的 @nexus/schemanexus-plugin-prisma 建立 GraphQL 類型之兩種方法之間的主要差異。

  • prismaObjectType 函數不再可用,所有類型都使用 Nexus 的 objectType 函數建立。
  • 若要透過 Nexus 公開 Prisma 模型,您可以使用新增至傳遞到 Nexus definition 函數中的 t 引數的 t.model 屬性。t.model 可讓您存取 Prisma 模型的屬性並公開它們。
  • 透過 Nexus 公開 Prisma 模型的 CRUD 運算遵循類似的方法。這些會透過 queryTypemutationType 類型的 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/schemanexus-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 模型的欄位命名的函數。

此時,您可能會在關聯欄位 postsprofile 上看到錯誤,例如

//delete-next-line
Missing type Post, did you forget to import a type to the root query?

這是因為您尚未將 PostProfile 類型新增至 GraphQL schema,一旦這些類型也成為 GraphQL schema 的一部分,錯誤就會消失!

3.2. 遷移 Post 類型

使用先前的 nexus-prisma 套件的類型定義

在範例應用程式中,Post 類型定義如下

const Post = prismaObjectType({
name: 'Post',
definition(t) {
t.prismaFields(['*'])
},
})

prismaFields 中的星號表示公開所有 Prisma 欄位。

使用最新版本的 @nexus/schemanexus-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/schemanexus-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/schemanexus-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 查詢從先前版本的 nexusnexus-prisma 遷移到最新版本的 @nexus/schemanexus-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/schemanexus-plugin-prisma

4.2.1. 遷移 createUser 變異

在我們的範例 API 中,範例 GraphQL schema 中的 createUser 變異實作如下。

const Mutation = prismaObjectType({
name: 'Mutation',
definition(t) {
t.prismaFields(['createUser'])
},
})

若要使用最新版本的 @nexus/schemanexus-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 伺服器。