ServicePrisma - muzyk0/crud GitHub Wiki

Description

@nestjsx/crud-prisma is the Prisma service adapter for @nestjsx/crud.

It is not a reflection-based drop-in replacement for @nestjsx/crud-typeorm. The adapter keeps controllers, request parsing, route generation, query parsing, and CrudAuth() behavior in place while moving ORM-specific behavior into explicit Prisma model config.

Install

npm i @nestjsx/crud-prisma @prisma/client prisma

Required model config

Every PrismaCrudService needs:

  • modelName
  • scalarFields
  • stringFields when filter or or query params should stay string-typed for Prisma string operators
  • primaryKeys
  • whereUnique(params, entity)

Optional config:

  • relationMap for join, nested join, eager relations, required joins, and nested sorting
  • softDelete for deleteOne() and recoverOne()
  • write.normalizeCreate, write.normalizeUpdate, write.normalizeReplace, write.normalizeDelete, and write.normalizeRecover for explicit write shaping

Usage

import { Injectable } from '@nestjs/common';
import { PrismaCrudService, definePrismaCrudModelConfig } from '@nestjsx/crud-prisma';

import { PrismaService } from '../prisma/prisma.service';
import { Company } from './company.model';

const companyModel = definePrismaCrudModelConfig<Company>({
  modelName: 'Company',
  scalarFields: ['id', 'name', 'domain', 'description', 'deletedAt'],
  stringFields: ['name', 'domain', 'description'],
  primaryKeys: ['id'],
  softDelete: {
    field: 'deletedAt',
    deletedValue: () => new Date(),
    notDeletedValue: null,
  },
  whereUnique: (params, entity) => ({
    id: Number(entity && entity.id ? entity.id : params.id),
  }),
});

@Injectable()
export class CompaniesService extends PrismaCrudService<Company> {
  constructor(prisma: PrismaService) {
    super(prisma.company, {
      model: companyModel,
      query: {
        softDelete: true,
      },
    });
  }
}
import { Controller } from '@nestjs/common';
import { Crud } from '@nestjsx/crud';

import { Company } from './company.model';
import { CompaniesService } from './companies.service';

@Crud({
  model: {
    type: Company,
  },
  query: {
    softDelete: true,
  },
})
@Controller('companies')
export class CompaniesController {
  constructor(public service: CompaniesService) {}
}

Relation and route example

The integration fixture keeps a nested controller route and Prisma-backed service:

@Crud({
  model: {
    type: User,
  },
  params: {
    companyId: {
      field: 'companyId',
      type: 'number',
    },
    id: {
      field: 'id',
      type: 'number',
      primary: true,
    },
  },
  routes: {
    deleteOneBase: {
      returnDeleted: true,
    },
  },
  query: {
    softDelete: true,
    join: {
      company: {
        exclude: ['description'],
      },
      'company.projects': {
        exclude: ['description'],
      },
      profile: {
        eager: true,
      },
    },
  },
})
@Controller('/companies/:companyId/users')
export class UsersController {
  constructor(public service: UsersService) {}
}

The corresponding Prisma model config uses relationMap, stringFields, softDelete, and whereUnique to make the service behavior explicit. allowParamsOverride stays false unless a route opts in, so route params continue to win over payload values on endpoints such as POST /companies/:companyId/users and PATCH /companies/:companyId/users/:id.

Migration from TypeORM

  1. Keep the existing @Crud() controller.
  2. Replace TypeOrmCrudService<Entity> with PrismaCrudService<Model>.
  3. Convert entity metadata into definePrismaCrudModelConfig().
  4. Add softDelete and whereUnique explicitly instead of relying on decorators or repository metadata.
  5. Add write.normalize* hooks only where nested writes or DTO normalization need to be explicit.

This migration strategy lets you move one controller/service pair at a time.

Verified fixture behavior

The Prisma integration fixtures and tests currently verify:

  • GET /companies?include_deleted=1
  • GET /users/1?join=company&join=company.projects
  • POST /companies/:companyId/users
  • PATCH /me
  • POST /projects

Those fixtures show that include_deleted, eager joins, required joins, returnDeleted, and CrudAuth.persist() continue to work through the Prisma adapter when the model config declares the needed metadata.

Compatibility notes

Supported:

  • Scalar field selection through scalarFields.
  • Relation traversal through relationMap, including nested relation metadata.
  • Compound primary keys and soft delete through explicit model configuration.
  • Write normalization through optional create, update, replace, delete, and recover hooks.

Known non-goals:

  • TypeORM runtime metadata parity or inferred relation discovery.
  • Implicit nested writes or cascade behavior.
  • Transparent query-cache parity without an explicit extension hook.

Additional notes:

  • Request-level cache settings are a no-op by default and only become active when PrismaCrudOptions.cache is provided.
  • When PrismaCrudOptions.cache is enabled, provide get(key) and/or set(key, value, ttl) so the adapter can wrap normalized Prisma args with request-scoped cache reads and writes.
  • Mutation lookups and post-write refetches bypass that cache so write paths do not reuse stale read results.
  • Declare stringFields on root and relation config anywhere legacy filter or or values such as 5 must remain "5" for Prisma string comparisons, ranges, membership checks, or case-insensitive operators.
  • Join aliases remain compatibility metadata only and do not drive Prisma query generation.
  • Soft delete behavior must be declared through softDelete config rather than ORM-specific decorators.
  • returnShallow is honored for create, update, and replace routes, returnDeleted is honored for delete routes, and returnRecovered is honored for recover routes. When returnShallow is false, create, update, and replace refetch the entity inside the current request scope when they can resolve a primary-key lookup. Delete and recover return a body only when their route flag is enabled.
⚠️ **GitHub.com Fallback** ⚠️