nestjs-crud について
@nestjs-crud は、NestJS アプリケーション向けに CRUD エンドポイントを生成するライブラリです。TypeORM、Drizzle、MikroORM、Prisma に対応し、クエリ設定、ページネーション、ユーザーごとのデータ制御を備えています。
主な機能
- 01@Crud() による REST エンドポイント生成
- 024 種類の ORM アダプターに対応
- 03フィルター、ソート、関連検索を設定
- 04オフセット・カーソルページネーション
- 05ユーザー単位のデータ制御とキャッシュ設定
活用シーン
- 01NestJS エンティティの CRUD API 作成
- 02一覧ルートのフィルターとページネーション設定
- 03現在のユーザーに応じた読み書き制限
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ kodjunkie/ nestjs-crud --skill nestjs-crudインストール後、Claude Code で/nestjs-crudを入力して読み込まれたか確認します。
詳細
@nestjs-crud
@Crud() から RESTful CRUD エンドポイントを自動生成します。アダプターは TypeORM、Drizzle、MikroORM、Prisma の 4 種類です。Node 22.12 以降が必要です(NestJS 12 は ESM のみ対応しているため、Node 22.12 で利用可能になる Node の require(esm) が必要です)。NestJS 10~12、TypeORM 0.3.30 以降または 1.x に対応します(v2.3 以降は NestJS 12 + TypeORM 1 に対応)。CHANGELOG。
インストール
npm install @nestjs-crud/core @nestjs-crud/<adapter> # typeorm | drizzle | mikro-orm | prisma
npm install @nestjs-crud/request # optional frontend QB
# All four adapters declare the DB driver as an optional peer — install whichever your backend uses:
npm install pg # Postgres (typeorm/drizzle/mikro-orm)
npm install mysql2 # MySQL (typeorm/drizzle/mikro-orm)
# Prisma 7 needs a driver adapter:
npm install @prisma/adapter-pg pg # Postgres
npm install @prisma/adapter-mariadb mariadb # MySQL
npm install -D prisma # CLI
Swagger は任意です。メタデータを有効にするには @nestjs/swagger をインストールしてください。ライブラリは、Swagger が存在しない場合にすべての Swagger パスを null ガードします。
クイックスタート
@Crud() ブロックと @Controller() は、すべてのアダプターで同一です。異なるのはサービスのコンストラクターだけです。
@Crud({
model: { type: User },
query: {
limit: 25, maxLimit: 100,
join: { profile: { eager: true }, posts: { allow: ['id', 'title'] } },
},
})
@Controller('users')
export class UsersController implements CrudController<User> {
constructor(public service: UsersService) {}
}
TypeORM サービス
@Injectable()
export class UsersService extends TypeOrmCrudService<User> {
constructor(@InjectRepository(User) repo: Repository<User>) {
super(repo);
}
}
Drizzle サービス
@Injectable()
export class UsersService extends DrizzleCrudService<typeof users.$inferSelect> {
constructor(@Inject('DB') drizzleDb: typeof db) {
super(drizzleDb, users); // (db instance, table reference)
}
}
MikroORM サービス
import { InjectRepository } from '@mikro-orm/nestjs';
import { EntityRepository } from '@mikro-orm/core';
@Injectable()
export class UsersService extends MikroOrmCrudService<User> {
constructor(@InjectRepository(User) usersRepo: EntityRepository<User>) {
super(usersRepo, User);
}
}
コンストラクターは EntityManager | EntityRepository<T> を受け取ります(v2.2 以降)。super(usersRepo, User) は内部で repo.getEntityManager() を使って展開します。DI で注入される em プロキシは、RequestContext を介してリクエストごとに fork された em に解決されるため、特別な設定なしでリクエストスコープの ID マップ分離が維持されます。EntityManager を直接渡す場合は、constructor(em: EntityManager) { super(em, User); } とします。動作は同じです。
Prisma サービス(Prisma 7 対応の v2.1 以降)
import { PrismaCrudService, PrismaJoinResolver } from '@nestjs-crud/prisma';
const prisma = new PrismaClient({
adapter: new PrismaPg(new Pool({ connectionString: process.env.DATABASE_URL })),
});
@Injectable()
export class UsersService extends PrismaCrudService<User> {
constructor(@Inject('PRISMA') prisma: PrismaClient) {
super(prisma, 'user', {
entityColumns: ['id', 'email', 'isActive', 'companyId', 'deletedAt'],
entityPrimaryColumns: ['id'],
entityHasDeleteColumn: true,
softDeleteColumn: 'deletedAt',
onBadRequest: (msg) => { throw new BadRequestException(msg); },
joinResolver: new PrismaJoinResolver({
relationFields: ['company'],
allowedColumnsByRelation: { company: ['id', 'name'] },
}),
});
}
}
Prisma 7 では env URL のパスが削除されたため、ドライバーアダプターが必要です。スキーマの書き換えについては、v2.1 Migration wiki を参照してください。MySQL では @prisma/adapter-pg を @prisma/adapter-mariadb に置き換えてください。3 つ目のコンストラクター引数は PrismaCrudServiceConfig です。上記のすべてのフィールドが必須で、logger と cacheStrategy はその任意フィールドです。
生成されるエンドポイント
| メソッド | パス | ハンドラー |
|---|---|---|
| GET | /users | getManyBase |
| GET | /users/:id | getOneBase |
| POST | /users | createOneBase |
| POST | /users/bulk | createManyBase |
| PATCH | /users/:id | updateOneBase |
| PUT | /users/:id | replaceOneBase |
| DELETE | /users/:id | deleteOneBase |
| PATCH | /users/:id/recover | recoverOneBase(query.softDelete: true が必要) |
@Crud() の主なオプション
@Crud({
model: { type: Entity }, // required
dto: { create, update, replace }, // optional — see DTOs
serialize: { getMany, get }, // per-route response DTOs
query: {
limit: 25, maxLimit: 100,
pagination: 'offset' | 'cursor', // v2.2+ — default 'offset'
cache: 2000, // ms
alwaysPaginate: false,
softDelete: false, // enables recoverOneBase
relationLoadStrategy: 'join' | 'query', // TypeORM only
join: { rel: { eager, allow, exclude, required } },
filter: [{ field, operator, value }],
sort: [{ field, order }],
exclude: ['password'],
},
routes: { exclude: ['createManyBase'], updateOneBase: { allowParamsOverride: false } },
params: { id: { field: 'id', type: 'uuid', primary: true } },
serviceProperty: 'usersService', // v2.2+ — defaults 'service'; see §IntelliSense for typing
})
全リファレンスは、Controllers wiki を参照してください。
ネストされた join(v2.3 以降)。 ?join=profile.licenses を使うには、すべての祖先を ?join= でリクエストするか、eager にする必要があります。順序や深さは問いません。そうでない場合、4 つのアダプターすべてが 400 Invalid join: 'profile.licenses' を返します。祖先が eager ではない eager なネストキーがある場合、起動時に @Crud() が例外をスローします。ネストされたリレーションを読み込むのは TypeORM と Drizzle のみです。MikroORM と Prisma はトップレベルのリレーションのみ join または include します。query.join にない ?join= リレーションは拒否されず、無視されます。
カーソルページネーション(v2.2 以降、オプトイン)
@Crud({ query: { pagination: 'cursor', limit: 25 } })
getManyBase をキーベースのカーソルモードに切り替えます。レスポンスは { data, count, cursor: { next, prev } } です。デフォルトの 'offset' は維持されます(後方互換性あり)。
- カーソルは、不透明な base64url JSON
{sortField, sortValue, id, dir}です。署名はされません。認可は@CrudAuthで引き続き行います。カーソルが不透明であることは、アクセス境界を意味しません。 @CrudAuthフィルターはカーソルの適用前に適用されます(インターセプターで認証フィルターがparsed.searchにマージされ、サービスでカーソルのWHEREがその上にANDされます)。カーソルページは@CrudAuthでフィルターされた結果セットのサブセットとなり、offsetモードと同じ分離保証が得られます。- ソートは
?sort=、またはルートのquery.sortのデフォルト値を使います(v2.2.6 以降はデフォルト値が適用されます)。単一フィールドに加えて自動的に主キーが末尾に追加されます。複数ソート、またはソート指定がない場合は 400 になります。limitまたはmaxLimitが必要です(指定がない場合は 400)。改ざんされたカーソル、またはソートが一致しないカーソルは 400 になります。 - カーソルモードではキャッシュのラップを回避します(カーソルキーごとのカーディナリティが無制限のため)。アクセス頻度の高いエンドポイントでは、
@nestjs/throttlerと組み合わせてください。 - 4 つのアダプターすべてで、アダプターごとの
QueryComposer.applyCursorを介して適用されます。Prisma の組み込みcursor:引数は意図的に使用しません(単一カラムの一意キーのみ対応するため)。
クエリパラメーター(フロントエンド → バックエンド)
RequestQueryBuilder.create()
.setFilter({ field: 'isActive', operator: CondOperator.EQUALS, value: true })
.setOr({ field: 'role', operator: CondOperator.IN, value: ['admin'] })
.setJoin({ field: 'profile' })
.sortBy({ field: 'createdAt', order: 'DESC' })
.setLimit(20).setPage(2)
.query();
23 個の演算子(CondOperator): $eq $ne $gt $gte $lt $lte $between $isnull $notnull $in $notin $cont $excl $starts $ends + 大文字と小文字を区別しない *L バリアント 8 個。
生の形式:?filter=name||$cont||john&sort=createdAt,DESC&join=profile&limit=25&page=2&cursor=<opaque>
グローバルなデフォルト設定
プロジェクト全体のデフォルト設定は、NestFactory.create の前に main.ts で設定します。各 @Crud() は設定をディープマージします。コントローラーのスカラー値が優先され、配列は置き換えられます。
CrudConfigService.load({
query: { limit: 25, maxLimit: 100, alwaysPaginate: true, cache: 2000, softDelete: false },
routes: { updateOneBase: { allowParamsOverride: false, returnShallow: false }, deleteOneBase: { returnDeleted: false } },
params: { id: { field: 'id', type: 'number', primary: true } },
serialize: { getMany: false }, // disable serialize globally for list responses
queryParser: { delimiter: ',' }, // passed to RequestQueryBuilder.setOptions
auth: { property: 'user' }, // default user property for @CrudAuth
});
@CrudAuth — ユーザーごとのスコープ設定
@Crud({ model: { type: Post } })
@CrudAuth({
property: 'user',
filter: (u: User) => ({ authorId: { $eq: u.id } }), // appended to every read
persist: (u: User) => ({ authorId: u.id }), // set on every write
// or: (u: User) => ({ isPublic: { $eq: true } }), // alternative to filter: rows match `or` OR the request search; filter skipped when set
})
@UseGuards(JwtAuthGuard)
@Controller('posts')
export class PostsController { constructor(public service: PostsService) {} }
persist のキーは実行時にエンティティのカラムと照合して検証されます。誤記があると 400 エラーになります:@CrudAuth persist: invalid key(s) "X" — not columns on the target entity。
ガードの実行順序: CrudRequestInterceptor が req.user を読み取る前に実行されるよう、@UseGuards() はコントローラークラスに指定する必要があります。
ACL / RBAC との統合
生成されたルートハンドラーには @Action(CrudActions.<X>) が自動で適用されます。値は Read-All、Read-One、Create-One、Create-Many、Update-One、Replace-One、Delete-One、Recover-One です。コントローラーに @Feature(name) を指定し、ガードから getFeature(ctx.getClass()) と getAction(ctx.getHandler()) で両方を取得してから、ACL バックエンド(nest-access-control、CASL など)に処理を振り分けます。ACLGuard の完全な例については、Controllers wiki §Additional decorators を参照してください。
@Override() — カスタムハンドラー
@Override('getOneBase')
getUser(@ParsedRequest() req: CrudRequest, @Param('id') id: string) {
return this.service.getOne(req);
}
@Override('createOneBase')
createUser(@ParsedRequest() req: CrudRequest, @ParsedBody() dto: User) {
return this.service.createOne(req, dto);
}
@Override が受け付ける値:'getManyBase'、'getOneBase'、'createOneBase'、'createManyBase'、'updateOneBase'、'replaceOneBase'、'deleteOneBase'、'recoverOneBase'。
書き込み用のオーバーライドでは、@Body() ではなく @ParsedBody() を使用してください。@Body() は class-validator のグループ選択を迂回するため、CREATE と UPDATE のバリデーションが同一になります。
DTO — 2 つのパターン
パターン A(デフォルト):エンティティを DTO として使用し、class-validator のグループを適用します。
const { CREATE, UPDATE } = CrudValidationGroups;
@IsOptional({ groups: [UPDATE] })
@IsNotEmpty({ groups: [CREATE] })
@IsString({ always: true })
name: string;
CREATE → POST および PUT、UPDATE → PATCH、{ always: true } → 両方。
パターン B:専用 DTO。@Crud({ dto: { create, update, replace } }) で指定します。dto を指定すると、CrudValidationGroups は使用されず、コントローラーは DTO クラスに対して直接バリデーションを行います。エンティティを入力(A)に、DTO を出力(@Crud({ serialize }))に、自由に組み合わせられます。
バリデーショングループ:ファクトリーと手書きコントローラー
CrudValidationGroups.CREATE / UPDATE が適用されるのは、バリデーターが対応する groups を指定して実行された場合のみです。@Crud() ファクトリーは、生成された各ルートに new ValidationPipe({ groups: [CREATE | UPDATE], ... }) を自動でバインドします。@Crud() を使わずに作成した手書きのコントローラーにはデフォルトのパイプ(groups: undefined)が適用されます。グループを指定したデコレーターは黙ってスキップされるため、必須フィールドのバリデーターが実行されません。
// ✗ Hand-rolled — group-scoped decorators silently skip
@Controller('special')
export class SpecialController {
@Post('/users')
createSpecial(@Body() user: User) { ... } // CREATE-group validators DO NOT run
}
// ✓ Hand-rolled — wire the group pipe explicitly
@Post('/users')
createSpecial(
@Body(new ValidationPipe({ groups: [CrudValidationGroups.CREATE], whitelist: true })) user: User,
) { ... }
このようなルートが多数ある場合は、ヘルパーにまとめてください。代わりに、グループデコレーターを使わずに専用 DTO(パターン B)へ切り替える方法もあります。専用 DTO なら、どこでも同じようにバリデーションされます。
フィールドの厳密な許可リスト(v2 の破壊的変更)
?sort/?filter/?search/?fields 内のすべてのフィールドは、エンティティのカラムまたは許可リストに登録されたリレーションである必要があります。そうでない場合は 400 エラーになります。エラーメッセージはアダプターやブランチによって異なります:Field "X" is not allowed、Invalid field: 'X'、Invalid sort field: 'X'、Unknown column: X。無効にする方法はありません。よくある問題:TypeORM の @VirtualColumn / @Formula(metadata.columns に含まれません。許可するにはサブクラスで protected entityColumnsHash をオーバーライドしてください)、クライアント側のエイリアス、JOIN されていないリレーションに対するドット区切りのパス。
キャッシュ
@Crud({ query: { cache: <ttl-ms> } }) は、CacheStrategy が設定されている場合、4つのアダプターすべてで有効になります(v2.2より前はTypeORMのみ)。TTLの単位はミリ秒です。
import { createClient } from 'redis';
import { CrudConfigService } from '@nestjs-crud/core';
import { TypeOrmCacheStrategy } from '@nestjs-crud/typeorm';
const redis = createClient({ url: 'redis://localhost:6379' });
CrudConfigService.load({
query: { cache: 5000, cacheStrategy: new TypeOrmCacheStrategy(redis) },
});
コンストラクターでCrudServiceごとに上書きできます。@Crud({ query: { cache } }) でルートごとに上書きできます。?cache=0 でリクエストごとにキャッシュを無効化できます。カーソルルートはキャッシュされません(§カーソルページネーション)。
ストラテジー:TypeOrm | MikroOrm | Drizzle | PrismaRedis | PrismaAccelerate。redis(node-redis v5、またはv2.3以降ではv6)またはioredisクライアントを受け付けます。初回のみ遅延自動接続するため、明示的なconnect()は不要です。カスタムバックエンドを使う場合は、@nestjs-crud/core/cacheのRedisLike(set / get / del / scanPrefix)を実装してください。書き込み時にはエンティティのプレフィックスに基づいて自動的に無効化されます。
ストラテジーなしで@Crud cacheを設定した場合(かつTypeORMのDataSource.cacheによるフォールバックもない場合)、次回のキャッシュ読み取り時にCrudCacheNotConfiguredErrorが発生します。セットアップ、セキュリティ、チューニングについては、キャッシュWikiを参照してください。
書き込みオーバーライド時のトランザクション
updateOne/replaceOne/deleteOneでは、READ COMMITTEDで読み取り・変更・書き込みをトランザクションで囲み、更新の取りこぼし競合を防ぎます。recoverOneは事前の読み取りがないため対象外です。独自の外側のトランザクションで@Override()を囲む場合、アダプターの内側のトランザクションはその内側のセーブポイントになります。外側の分離レベルが引き下げられることはなく、ロールバックは上位にも波及します。
TypeORMの分割クエリによるリレーション読み込み
@Crud({ query: { relationLoadStrategy: 'query' } }) — setFindOptionsを介してリレーションごとに個別のクエリを実行します。注意点: JoinOption.allowは無視され、リレーションの全カラムが読み込まれます。機密カラムを非表示にするためにallowを使用している場合は、この設定を有効にしないでください。ネストされた結合は、完全なドット区切りパスがquery.joinのキーになっている場合にのみ読み込まれます(v2.3以降)。Drizzle、MikroORM、Prismaではこのオプションは無視されます。
| ストラテジー | 動作 | 選択する場面 |
|---|---|---|
'join' | leftJoin/innerJoinによる単一のSQL | 浅い結合、またはJoinOption.allowに依存する場合 |
'query' | setFindOptionsを介してリレーションごとに個別のクエリを実行 | 深い複数リレーションの読み取りで、JOINによる直積がペイロードを膨らませる場合 |
Swaggerのカスタマイズ
@Crud({ swagger: {...} }) で操作テキスト、タグ、例、エラーレスポンス、バージョンに応じたグループ化を制御できます。デフォルトでは、命令形のサマリー、複数行の説明、400/認証がある場合は401/404の出力、自動的な@ApiTags、@ApiPropertyに基づくリクエストボディの例の自動生成が行われます。
制約: operationIdは上書きできません。401のデフォルト出力は@CrudAuthに基づきます(APP_GUARDを使う構成ではswagger.errorResponses.unauthorized: trueを設定してください)。synthExampleのコンシューマー関数はメタデータより優先されます。@Override()と手動の@ApiOperation()はswagger.operations[...]より優先されます。詳細なリファレンスはSwagger Wikiを参照してください。
ロガー
4つのアダプターすべてで、デフォルトはnew Logger(<ServiceName>)です。TypeORM/Drizzle/MikroORMでは、位置引数としてlogger?: LoggerServiceをcacheStrategy?の前に指定します — (repo, logger?, cacheStrategy?)、(db, table, relationsConfig?, logger?, cacheStrategy?)、(emOrRepo, entityClass, logger?, cacheStrategy?)。PrismaのロガーはserviceConfig内の{ error, warn?, debug? }として指定します。
出力の規約: クエリのトレースにはdebug、SQLインジェクションの拒否とトランザクションのロールバックにはwarn、捕捉されなかったDBエラーにはerrorを使います。err.messageを絶対に埋め込まないでください — DBドライバーはエラーメッセージにSQLのパラメーター値を漏えいさせる場合があります。メッセージにはerr.nameを出力し、第2引数としてerr.stackを渡してください。
IntelliSense
@Crud() はハンドラーを実行時に組み込みますが、TypeScript からは認識されません。implements CrudController<T> を指定すると、service の型付けが復元されます。@Override() の内部では、this.service.getMany(req) が完全に型付けされ、慣用的な書き方になります。1 つのオーバーライド内で生成されたハンドラーを 2 つ組み合わせる場合:get base(): CrudController<User> { return this; }。
serviceProperty を使用する場合(v2.2.5+): implements CrudController<T> は失敗します — TS2559 has no properties in common。CrudController<T> はすべてオプショナルなプロパティで構成される(弱い型)ため、名前を変更したフィールドだけを宣言するクラスとは、共有するプロパティがありません。代わりに CrudControllerFor<T, P> を使用してください:
@Crud({ model: { type: Contact }, serviceProperty: 'contactService' })
@Controller('contacts')
export class ContactsController implements CrudControllerFor<Contact, 'contactService'> {
constructor(public contactService: ContactsCrudService) {}
}
デフォルトの CrudControllerFor<T> は CrudController<T> と同等です。別の方法として、implements 自体を省略できます — ルーティングには必要ありません。
ベストプラクティス
- 生の文字列ではなく
CondOperator列挙型を使用してください('$cont'は誤記しても警告なしで動作します) query設定では、機密フィールドを必ずexcludeしてください(?fields=passwordを指定すると、除外しない限り返されます)- 機密性の高い関連では、join フィールドを必ず許可リストに登録してください
- AND/OR の組み合わせには
searchを使用してください。filterは AND のみです - サーバー側で
maxLimitを設定してください — クライアントのlimitを決して信用しないでください
よくある問題
| 症状 | 解決方法 |
|---|---|
| リレーションが読み込まれない | query.join に追加してください。追加しない場合、クライアントからの結合リクエストは無視されます。 |
maxLimit 超過 → 400 | maxLimit を引き上げるか、alwaysPaginate: false を設定してください。 |
| 更新時にバリデーションが常に失敗する | フィールドに @IsOptional({ groups: [UPDATE] }) が必要です。 |
Cannot find module 'typeorm' / Cannot find module 'pg' / Cannot find module 'mysql2' / repo.createQueryBuilder is not a function | アダプターの ORM または DB ドライバーのピア依存関係がインストールされていません。npm install typeorm pg(Postgres)または npm install typeorm mysql2(MySQL)を実行してください。4 つすべてのアダプターで DB ドライバーはオプションのピア依存関係として宣言されています。バックエンドで使用するものを選んでください。 |
| Swagger のメタデータが空 | @nestjs/swagger がインストールされていません。このライブラリは Swagger のセットアップをスキップします。インストールして再起動してください。 |
@CrudAuth のフィルターが適用されない | @UseGuards() はコントローラークラスに付ける必要があります(インターセプターより先に実行されます)。 |
{ data, count, total, page } ではなくフラットな配列が返される | query: 内に alwaysPaginate: true を設定してください(トップレベルではありません)。 |
400 Field "X" is not allowed / Invalid field: 'X' / Invalid sort field: 'X' / Unknown column: X | エンティティのカラム、または query.join の許可リストに追加してください。 |
400 @CrudAuth persist: invalid key(s) "X" | @CrudAuth({ persist }) で指定したキーがエンティティのカラム名と一致していません。 |
TS2559 has no properties in common with type 'CrudController<T>' | serviceProperty によってフィールド名が変更され、弱い型のチェックに失敗しています。implements CrudControllerFor<Entity, 'fieldName'>(v2.2.5 以降)を指定するか、implements を削除してください。 |
npm install 時の EBADENGINE | Node <22.12 です。アップグレードするか、^1.0.2 に固定してください。 |
CrudCacheNotConfiguredError | @Crud cache が設定されていますが、CacheStrategy が接続されておらず、TypeORM の DataSource.cache によるフォールバックもありません。 |
カーソル:Cursor pagination supports a single sort field → 400 | 複数フィールドでのソートはサポートされていません。フィールドを 1 つだけ使用してください。ソートしない場合は ?sort= を渡すか、query.sort を設定してください(v2.2.6 以降はデフォルトが適用されます。それ以前のバージョンでは明示的な ?sort= が必要です)。 |
カーソル:Cursor pagination requires a limit → 400 | カーソルモードでは query.limit または maxLimit が必要です。 |
カーソル:Invalid cursor / Cursor token exceeds maximum length → 400 | カーソルが改ざんまたは不正な形式であるか、1024 文字を超えています。最初のページを再取得してください。 |
カーソル:Cursor sort field mismatch → 400 | カーソルが別の ?sort= フィールドで発行されています。同じソートフィールドを指定してください。 |
400 Invalid join: 'a.b' | 親の a が結合されていません。?join= に a を追加するか、eager に設定してください(v2.3 以降)。 |
起動時:@Crud: eager join 'a.b' on X needs 'a' to be eager too | a を eager に設定するか、a.b から eager を削除してください。 |
@nestjs-crud/typeorm のインストール時に npm の ERESOLVE が発生する | TypeORM 1.x のピア依存関係は ioredis@^5 ですが、NestJS 12 では 6 が解決されます。npm install ioredis@^5 を実行するか、"overrides": { "ioredis": "^5.0.4" } を追加してください。 |
MikroORM:エンティティが古い/em.flush() で永続化されない | コンストラクターで em をサブクラスにキャッシュしています。getEm() のサンクを使用し、em をフィールドとして保存しないでください。 |
Jest:Cannot use 'import.meta' outside a module | MikroORM 7 と NestJS 12 は純粋な ESM です。NODE_OPTIONS=--experimental-vm-modules と ts-jest の ESM プリセットを使って Jest を実行してください。 |
Prisma:include で Unknown argument 'where' | Prisma では、to-one の include に where を指定できません。親の where でフィルターしてください。アダプターは SCondition のドット区切りパスを自動的に処理します。 |
外側のトランザクション + @Override() による書き込み:SERIALIZABLE で永続化され、ロールバックが連鎖する | アダプターの内部トランザクションは、外側のトランザクション内のセーブポイントです。ラップを削除するか、セーブポイントのネストを受け入れるかを決めてください。 |
分割クエリ:allow を指定してもリレーションの全カラムが読み込まれる | setFindOptions ではエイリアスレベルの select を指定できません。機密性の高いリレーションでは 'join' を使い続けてください。 |