TypeORM
このページではPrisma ORMとTypeORMを比較します。TypeORMからPrisma ORMへ移行する方法を知りたい場合は、このガイドをご覧ください。
TypeORM vs Prisma ORM
Prisma ORMとTypeORMは類似の問題を解決しますが、その動作方法は大きく異なります。
TypeORMは、テーブルをモデルクラスにマッピングする従来のORMです。これらのモデルクラスは、SQLマイグレーションを生成するために使用できます。モデルクラスのインスタンスは、実行時にアプリケーションへのCRUDクエリのためのインターフェースを提供します。
Prisma ORMは、肥大化したモデルインスタンス、ビジネスロジックとストレージロジックの混在、型安全性の欠如、遅延ロードなどによって引き起こされる予測不能なクエリといった、従来のORMの多くの問題を軽減する新しい種類のORMです。
Prismaスキーマを使用して、アプリケーションモデルを宣言的に定義します。Prisma Migrateは、PrismaスキーマからSQLマイグレーションを生成し、それをデータベースに対して実行することを可能にします。CRUDクエリは、Node.jsおよびTypeScript用の軽量で完全に型安全なデータベースクライアントであるPrisma Clientによって提供されます。
API設計と抽象度
TypeORMとPrisma ORMは異なる抽象度で動作します。TypeORMはそのAPIにおいてSQLをミラーリングすることに近く、一方でPrisma Clientは、アプリケーション開発者の一般的なタスクを念頭に置いて慎重に設計された、より高レベルの抽象化を提供します。Prisma ORMのAPI設計は、「正しいことを簡単にする」という考え方に大きく傾倒しています。
Prisma Clientはより高レベルの抽象度で動作しますが、基盤となるデータベースの全機能を公開するように努めており、ユースケースで必要に応じていつでも生のSQLにドロップダウンできます。
以下のセクションでは、Prisma ORMとTypeORMのAPIが特定のシナリオでどのように異なるか、そしてこれらのケースにおけるPrisma ORMのAPI設計の根拠が何かをいくつかの例で検証します。
フィルタリング
TypeORMは主に、find
メソッドなどを使用して、リストやレコードのフィルタリングにSQL演算子に依存しています。一方、Prisma ORMは、より汎用的な演算子セットを提供し、直感的に使用できます。また、後述の型安全性のセクション以下で説明するように、TypeORMは多くのシナリオでフィルタークエリの型安全性を失うことにも注意が必要です。
TypeORMとPrisma ORMの両方のフィルタリングAPIがどのように異なるかを示す良い例は、string
フィルターを見ることです。TypeORMは主にSQLから直接来るILike
演算子に基づくフィルターを提供しますが、Prisma ORMは開発者が使用できるより具体的な演算子を提供します。例えば、contains
、startsWith
、endsWith
などです。
const posts = await prisma.post.findMany({
where: {
title: 'Hello World',
},
})
const posts = await postRepository.find({
where: {
title: ILike('Hello World'),
},
})
const posts = await prisma.post.findMany({
where: {
title: { contains: 'Hello World' },
},
})
const posts = await postRepository.find({
where: {
title: ILike('%Hello World%'),
},
})
const posts = await prisma.post.findMany({
where: {
title: { startsWith: 'Hello World' },
},
})
const posts = await postRepository.find({
where: {
title: ILike('Hello World%'),
},
})
const posts = await prisma.post.findMany({
where: {
title: { endsWith: 'Hello World' },
},
})
const posts = await postRepository.find({
where: {
title: ILike('%Hello World'),
},
})
ページネーション
TypeORMはリミット・オフセットによるページネーションのみを提供しますが、Prisma ORMはリミット・オフセットだけでなく、カーソルベースのページネーションにも便利な専用APIを提供します。両方のアプローチについては、ドキュメントのページネーションセクションまたは以下のAPI比較で詳しく学ぶことができます。
リレーション
外部キーで接続されたレコードの操作は、SQLでは非常に複雑になることがあります。Prisma ORMの仮想リレーションフィールドの概念は、アプリケーション開発者が関連データと直感的かつ便利に作業できる方法を提供します。Prisma ORMのアプローチの利点には以下のようなものがあります。
- fluent APIによるリレーションシップのトラバース(ドキュメント)
- 接続されたレコードの更新/作成を可能にするネストされた書き込み(ドキュメント)
- 関連レコードへのフィルターの適用(ドキュメント)
- JOINを気にせずにネストされたデータを簡単かつ型安全にクエリ(ドキュメント)
- モデルとそのリレーションに基づいたネストされたTypeScript型定義の作成(ドキュメント)
- リレーションフィールドを介したデータモデルにおけるリレーションの直感的なモデリング(ドキュメント)
- リレーションテーブル(JOIN、リンク、ピボット、結合テーブルとも呼ばれる)の暗黙的な処理(ドキュメント)
データモデリングとマイグレーション
PrismaモデルはPrismaスキーマで定義されますが、TypeORMはモデル定義にクラスと実験的なTypeScriptデコレータを使用します。Active Record ORMパターンでは、TypeORMのアプローチは、アプリケーションが成長するにつれて保守が困難になる複雑なモデルインスタンスにつながることがよくあります。
一方、Prisma ORMは、Active RecordパターンではなくDataMapper ORMパターンに従い、Prismaスキーマで定義されたモデルのデータを読み書きするための、調整され完全に型安全なAPIを公開する軽量なデータベースクライアントを生成します。
Prisma ORMのデータモデリング用DSLは、シンプルで直感的に使用できます。VS Codeでデータをモデリングする際、オートコンプリート、クイックフィックス、定義へのジャンプなどの機能を持つPrisma ORMの強力なVS Code拡張機能をさらに活用でき、開発者の生産性を向上させます。
model User {
id Int @id @default(autoincrement())
name String?
email String @unique
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
authorId Int?
author User? @relation(fields: [authorId], references: [id])
}
import {
Entity,
PrimaryGeneratedColumn,
Column,
OneToMany,
ManyToOne,
} from 'typeorm'
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column({ nullable: true })
name: string
@Column({ unique: true })
email: string
@OneToMany((type) => Post, (post) => post.author)
posts: Post[]
}
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number
@Column()
title: string
@Column({ nullable: true })
content: string
@Column({ default: false })
published: boolean
@ManyToOne((type) => User, (user) => user.posts)
author: User
}
マイグレーションはTypeORMとPrisma ORMで同様の方法で動作します。どちらのツールも、提供されたモデル定義に基づいてSQLファイルを生成し、それらをデータベースに対して実行するためのCLIを提供します。SQLファイルは、マイグレーションが実行される前に変更できるため、どちらのマイグレーションシステムでもカスタムデータベース操作を実行できます。
型安全
TypeORMは、Node.jsエコシステムでTypeScriptを完全に採用した最初のORMの1つであり、開発者がデータベースクエリにある程度の型安全性を得る上で素晴らしい仕事をしました。
しかし、TypeORMの型安全性の保証が不十分な状況が数多く存在します。以下のセクションでは、Prisma ORMがクエリ結果の型に対してより強力な保証を提供できるシナリオを説明します。
フィールドの選択
このセクションでは、クエリでモデルのフィールドのサブセットを選択する際の型安全性の違いについて説明します。
TypeORM
TypeORMは、find
メソッド(例: find
, findByIds
, findOne
, ...)に対してselect
オプションを提供します。例えば、
- select付きfind
- モデル
const postRepository = getManager().getRepository(Post)
const publishedPosts: Post[] = await postRepository.find({
where: { published: true },
select: ['id', 'title'],
})
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number
@Column()
title: string
@Column({ nullable: true })
content: string
@Column({ default: false })
published: boolean
@ManyToOne((type) => User, (user) => user.posts)
author: User
}
返されるpublishedPosts
配列の各オブジェクトは、実行時に選択されたid
とtitle
プロパティのみを保持しますが、TypeScriptコンパイラはこのことを認識していません。そのため、クエリ後もPost
エンティティに定義されている他のプロパティにアクセスできてしまいます。例えば、
const post = publishedPosts[0]
// The TypeScript compiler has no issue with this
if (post.content.length > 0) {
console.log(`This post has some content.`)
}
このコードは実行時にエラーになります。
TypeError: Cannot read property 'length' of undefined
TypeScriptコンパイラは返されたオブジェクトのPost
型しか認識せず、それらのオブジェクトが実行時に実際に保持するフィールドについては知りません。そのため、データベースクエリで取得されていないフィールドにアクセスすることを防ぐことができず、実行時エラーが発生します。
Prisma ORM
Prisma Clientは、同じ状況で完全な型安全性を保証し、データベースから取得されていないフィールドへのアクセスからあなたを保護します。
Prisma Clientクエリを使った同じ例を考えてみましょう。
- select付きfindMany
- モデル
const publishedPosts = await prisma.post.findMany({
where: { published: true },
select: {
id: true,
title: true,
},
})
const post = publishedPosts[0]
// The TypeScript compiler will not allow this
if (post.content.length > 0) {
console.log(`This post has some content.`)
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
authorId Int?
author User? @relation(fields: [authorId], references: [id])
}
この場合、TypeScriptコンパイラはコンパイル時にすでに次のエラーをスローします。
[ERROR] 14:03:39 ⨯ Unable to compile TypeScript:
src/index.ts:36:12 - error TS2339: Property 'content' does not exist on type '{ id: number; title: string; }'.
42 if (post.content.length > 0) {
これは、Prisma Clientがクエリの戻り値をその場で生成するためです。この場合、publishedPosts
は次のように型付けされます。
const publishedPosts: {
id: number
title: string
}[]
したがって、クエリで取得されなかったモデルのプロパティに誤ってアクセスすることは不可能です。
リレーションの読み込み
このセクションでは、クエリでモデルのリレーションを読み込む際の型安全性の違いについて説明します。従来のORMでは、これはEager Loadingと呼ばれることがあります。
TypeORM
TypeORMは、find
メソッドに渡すことができるrelations
オプションを介して、データベースからリレーションをEager Loadすることを可能にします。
この例を考えてみましょう
- relations付きfind
- モデル
const postRepository = getManager().getRepository(Post)
const publishedPosts: Post[] = await postRepository.find({
where: { published: true },
relations: ['author'],
})
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number
@Column()
title: string
@Column({ nullable: true })
content: string
@Column({ default: false })
published: boolean
@ManyToOne((type) => User, (user) => user.posts)
author: User
}
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column({ nullable: true })
name: string
@Column({ unique: true })
email: string
@OneToMany((type) => Post, (post) => post.author)
posts: Post[]
}
select
とは異なり、TypeORMはrelations
オプションに渡される文字列に対して、オートコンプリートも型安全性も提供しません。これは、これらのリレーションをクエリする際に発生するタイポをTypeScriptコンパイラが捕捉できないことを意味します。例えば、以下のクエリは許可されてしまいます。
const publishedPosts: Post[] = await postRepository.find({
where: { published: true },
// this query would lead to a runtime error because of a typo
relations: ['authors'],
})
この微妙なタイポが、以下の実行時エラーにつながります。
UnhandledPromiseRejectionWarning: Error: Relation "authors" was not found; please check if it is correct and really exists in your entity.
Prisma ORM
Prisma ORMは、このような間違いからあなたを保護し、アプリケーションの実行時に発生しうるエラーの全クラスを排除します。Prisma Clientクエリでリレーションを読み込むためにinclude
を使用する場合、クエリを指定するためにオートコンプリートを活用できるだけでなく、クエリの結果も適切に型付けされます。
- relations付きfind
- モデル
const publishedPosts = await prisma.post.findMany({
where: { published: true },
include: { author: true },
})
model User {
id Int @id @default(autoincrement())
name String?
email String @unique
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
authorId Int?
author User? @relation(fields: [authorId], references: [id])
}
繰り返しますが、publishedPosts
の型はオンザフライで生成され、次のようになります。
const publishedPosts: (Post & {
author: User
})[]
参考までに、Prisma ClientがあなたのPrismaモデル用に生成するUser
とPost
の型は以下のようになります。
- User
- Post
// Generated by Prisma ORM
export type User = {
id: number
name: string | null
email: string
}
// Generated by Prisma ORM
export type Post = {
id: number
title: string
content: string | null
published: boolean
authorId: number | null
}
フィルタリング
このセクションでは、where
を使用してレコードのリストをフィルタリングする際の型安全性の違いについて説明します。
TypeORM
TypeORMでは、find
メソッドにwhere
オプションを渡すことで、特定の条件に従って返されるレコードのリストをフィルタリングできます。これらの条件は、モデルのプロパティに関して定義できます。
演算子を使用した型安全性の喪失
この例を考えてみましょう
- select付きfind
- モデル
const postRepository = getManager().getRepository(Post)
const publishedPosts: Post[] = await postRepository.find({
where: {
published: true,
title: ILike('Hello World'),
views: MoreThan(0),
},
})
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number
@Column()
title: string
@Column({ nullable: true })
content: string
@Column({ nullable: true })
views: number
@Column({ default: false })
published: boolean
@ManyToOne((type) => User, (user) => user.posts)
author: User
}
このコードは適切に実行され、実行時に有効なクエリを生成します。しかし、where
オプションはさまざまなシナリオで実際には型安全ではありません。特定の型に対してのみ機能するFindOperator
(例: ILike
は文字列に、MoreThan
は数値に機能)を使用する場合、モデルのフィールドに正しい型を提供するという保証を失います。
例えば、MoreThan
演算子に文字列を提供することができます。TypeScriptコンパイラは文句を言わず、アプリケーションは実行時のみに失敗します。
const postRepository = getManager().getRepository(Post)
const publishedPosts: Post[] = await postRepository.find({
where: {
published: true,
title: ILike('Hello World'),
views: MoreThan('test'),
},
})
上記のコードは、TypeScriptコンパイラがあなたのために捕捉しない実行時エラーになります。
error: error: invalid input syntax for type integer: "test"
存在しないプロパティの指定
また、TypeScriptコンパイラは、where
オプションにモデルに存在しないプロパティを指定することを許可することにも注意してください。これもまた実行時エラーにつながります。
const publishedPosts: Post[] = await postRepository.find({
where: {
published: true,
title: ILike('Hello World'),
viewCount: 1,
},
})
この場合、アプリケーションは再び実行時に以下のエラーで失敗します。
EntityColumnNotFound: No entity column "viewCount" was found.
Prisma ORM
TypeORMで型安全性の観点から問題となる両方のフィルタリングシナリオは、Prisma ORMによって完全に型安全な方法でカバーされています。
演算子の型安全な使用法
Prisma ORMでは、TypeScriptコンパイラがフィールドごとに演算子の正しい使用法を強制します。
const publishedPosts = await prisma.post.findMany({
where: {
published: true,
title: { contains: 'Hello World' },
views: { gt: 0 },
},
})
Prisma Clientで上記のような問題のあるクエリを指定することは許可されません。
const publishedPosts = await prisma.post.findMany({
where: {
published: true,
title: { contains: 'Hello World' },
views: { gt: 'test' }, // Caught by the TypeScript compiler
},
})
TypeScriptコンパイラがこれを検出し、アプリの実行時エラーからあなたを保護するために、次のエラーをスローします。
[ERROR] 16:13:50 ⨯ Unable to compile TypeScript:
src/index.ts:39:5 - error TS2322: Type '{ gt: string; }' is not assignable to type 'number | IntNullableFilter'.
Type '{ gt: string; }' is not assignable to type 'IntNullableFilter'.
Types of property 'gt' are incompatible.
Type 'string' is not assignable to type 'number'.
42 views: { gt: "test" }
モデルプロパティとしてのフィルターの型安全な定義
TypeORMでは、モデルのフィールドにマッピングされないプロパティをwhere
オプションに指定できます。上記の例では、viewCount
でフィルタリングすると、フィールドの実際の名前がviews
であるため、実行時エラーが発生しました。
Prisma ORMでは、TypeScriptコンパイラはモデルに存在しないwhere
内のプロパティを参照することを許可しません。
const publishedPosts = await prisma.post.findMany({
where: {
published: true,
title: { contains: 'Hello World' },
viewCount: { gt: 0 }, // Caught by the TypeScript compiler
},
})
繰り返しますが、TypeScriptコンパイラはあなたの間違いからあなたを保護するために、以下のメッセージで警告します。
[ERROR] 16:16:16 ⨯ Unable to compile TypeScript:
src/index.ts:39:5 - error TS2322: Type '{ published: boolean; title: { contains: string; }; viewCount: { gt: number; }; }' is not assignable to type 'PostWhereInput'.
Object literal may only specify known properties, and 'viewCount' does not exist in type 'PostWhereInput'.
42 viewCount: { gt: 0 }
新規レコードの作成
このセクションでは、新規レコードを作成する際の型安全性の違いについて説明します。
TypeORM
TypeORMでは、データベースに新しいレコードを作成する方法が主に2つあります。insert
とsave
です。どちらのメソッドも、必須フィールドが提供されていない場合に実行時エラーにつながる可能性のあるデータを開発者が送信することを許可します。
この例を考えてみましょう
- saveによる作成
- insertによる作成
- モデル
const userRepository = getManager().getRepository(User)
const newUser = new User()
newUser.name = 'Alice'
userRepository.save(newUser)
const userRepository = getManager().getRepository(User)
userRepository.insert({
name: 'Alice',
})
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column({ nullable: true })
name: string
@Column({ unique: true })
email: string
@OneToMany((type) => Post, (post) => post.author)
posts: Post[]
}
TypeORMでレコード作成にsave
またはinsert
を使用しても、必須フィールドの値の提供を忘れると、次の実行時エラーが発生します。
QueryFailedError: null value in column "email" of relation "user" violates not-null constraint
email
フィールドはUser
エンティティ上で必須として定義されています(これはデータベースのNOT NULL
制約によって強制されます)。
Prisma ORM
Prisma ORMは、モデルのすべての必須フィールドの値を送信することを強制することで、このような間違いからあなたを保護します。
例えば、必須のemail
フィールドが欠落している新しいUser
を作成しようとすると、TypeScriptコンパイラによって捕捉されます。
- createによる作成
- モデル
const newUser = await prisma.user.create({
data: {
name: 'Alice',
},
})
model User {
id Int @id @default(autoincrement())
name String?
email String @unique
}
これにより、次のコンパイル時エラーが発生します。
[ERROR] 10:39:07 ⨯ Unable to compile TypeScript:
src/index.ts:39:5 - error TS2741: Property 'email' is missing in type '{ name: string; }' but required in type 'UserCreateInput'.
API比較
単一オブジェクトの取得
Prisma ORM
const user = await prisma.user.findUnique({
where: {
id: 1,
},
})
TypeORM
const userRepository = getRepository(User)
const user = await userRepository.findOne(id)
単一オブジェクトの選択されたスカラー値の取得
Prisma ORM
const user = await prisma.user.findUnique({
where: {
id: 1,
},
select: {
name: true,
},
})
TypeORM
const userRepository = getRepository(User)
const user = await userRepository.findOne(id, {
select: ['id', 'email'],
})
リレーションの取得
Prisma ORM
- includeの使用
- Fluent API
const posts = await prisma.user.findUnique({
where: {
id: 2,
},
include: {
post: true,
},
})
const posts = await prisma.user
.findUnique({
where: {
id: 2,
},
})
.post()
注:
select
はpost
配列を含むuser
オブジェクトを返しますが、fluent APIはpost
配列のみを返します。
TypeORM
- relationsの使用
- JOINの使用
- Eager relationsの使用
const userRepository = getRepository(User)
const user = await userRepository.findOne(id, {
relations: ['posts'],
})
const userRepository = getRepository(User)
const user = await userRepository.findOne(id, {
join: {
alias: 'user',
leftJoinAndSelect: {
posts: 'user.posts',
},
},
})
const userRepository = getRepository(User)
const user = await userRepository.findOne(id)
具体的な値によるフィルタリング
Prisma ORM
const posts = await prisma.post.findMany({
where: {
title: {
contains: 'Hello',
},
},
})
TypeORM
const userRepository = getRepository(User)
const users = await userRepository.find({
where: {
name: 'Alice',
},
})
その他のフィルター条件
Prisma ORM
Prisma ORMは、現代のアプリケーション開発で一般的に使用される多くの追加フィルターを生成します。
TypeORM
TypeORMは、より複雑な比較を作成するために使用できる組み込みの演算子を提供します。
リレーションフィルター
Prisma ORM
Prisma ORMを使用すると、取得されるリストのモデルだけでなく、そのモデルのリレーションに適用される条件に基づいてリストをフィルタリングできます。
例えば、次のクエリはタイトルに「Hello」を含む1つ以上の投稿を持つユーザーを返します。
const posts = await prisma.user.findMany({
where: {
Post: {
some: {
title: {
contains: 'Hello',
},
},
},
},
})
TypeORM
TypeORMはリレーションフィルター専用のAPIを提供していません。QueryBuilder
を使用するか、クエリを手動で記述することで、同様の機能を得ることができます。
ページネーション
Prisma ORM
カーソル方式ページネーション
const page = await prisma.post.findMany({
before: {
id: 242,
},
last: 20,
})
オフセットページネーション
const cc = await prisma.post.findMany({
skip: 200,
first: 20,
})
TypeORM
const postRepository = getRepository(Post)
const posts = await postRepository.find({
skip: 5,
take: 10,
})
オブジェクトの作成
Prisma ORM
const user = await prisma.user.create({
data: {
email: 'alice@prisma.io',
},
})
TypeORM
- saveの使用
- createの使用
- insertの使用
const user = new User()
user.name = 'Alice'
user.email = 'alice@prisma.io'
await user.save()
const userRepository = getRepository(User)
const user = await userRepository.create({
name: 'Alice',
email: 'alice@prisma.io',
})
await user.save()
const userRepository = getRepository(User)
await userRepository.insert({
name: 'Alice',
email: 'alice@prisma.io',
})
オブジェクトの更新
Prisma ORM
const user = await prisma.user.update({
data: {
name: 'Alicia',
},
where: {
id: 2,
},
})
TypeORM
const userRepository = getRepository(User)
const updatedUser = await userRepository.update(id, {
name: 'James',
email: 'james@prisma.io',
})
オブジェクトの削除
Prisma ORM
const deletedUser = await prisma.user.delete({
where: {
id: 10,
},
})
TypeORM
- deleteの使用
- removeの使用
const userRepository = getRepository(User)
await userRepository.delete(id)
const userRepository = getRepository(User)
const deletedUser = await userRepository.remove(user)
一括更新
Prisma ORM
const user = await prisma.user.updateMany({
data: {
name: 'Published author!',
},
where: {
Post: {
some: {
published: true,
},
},
},
})
TypeORM
クエリビルダーを使用してデータベースのエンティティを更新できます。
一括削除
Prisma ORM
const users = await prisma.user.deleteMany({
where: {
id: {
in: [1, 2, 6, 6, 22, 21, 25],
},
},
})
TypeORM
- deleteの使用
- removeの使用
const userRepository = getRepository(User)
await userRepository.delete([id1, id2, id3])
const userRepository = getRepository(User)
const deleteUsers = await userRepository.remove([user1, user2, user3])
トランザクション
Prisma ORM
const user = await prisma.user.create({
data: {
email: 'bob.rufus@prisma.io',
name: 'Bob Rufus',
Post: {
create: [
{ title: 'Working at Prisma' },
{ title: 'All about databases' },
],
},
},
})
TypeORM
await getConnection().$transaction(async (transactionalEntityManager) => {
const user = getRepository(User).create({
name: 'Bob',
email: 'bob@prisma.io',
})
const post1 = getRepository(Post).create({
title: 'Join us for GraphQL Conf in 2019',
})
const post2 = getRepository(Post).create({
title: 'Subscribe to GraphQL Weekly for GraphQL news',
})
user.posts = [post1, post2]
await transactionalEntityManager.save(post1)
await transactionalEntityManager.save(post2)
await transactionalEntityManager.save(user)
})
Prismaとつながりましょう
以下のコミュニティと連携して、Prismaの旅を続けましょう。 私たちの活発なコミュニティに参加しましょう。情報収集、イベント参加、他の開発者との協力ができます。
- Xでフォローしてください お知らせ、ライブイベント、役立つヒントをお届けします。
- Discordに参加する 質問したり、コミュニティと話したり、会話を通じてアクティブなサポートを受けたりできます。
- YouTubeを購読する チュートリアル、デモ、ストリームを視聴できます。
- GitHubで関わる リポジトリにスターを付けたり、問題を報告したり、問題に貢献したりしてください。