House pattern for building complete CRUD domains in NestJS with Prisma — controller/service/repository with dependency inversion, validated DTOs and multi-tenant isolation. DEPRECATED — no active Wibi backend uses NestJS today; use only if a project explicitly picks NestJS. For the real production stack (Express), use express-prisma-pattern instead.
Scanned 9/2/2026
Install to Claude Code
npx -y skills add JoaoEquer/Oficina --skill nestjs-crud-pattern --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Nestjs Crud Pattern?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/joaoequer-nestjs-crud-pattern)More formats (shields.io, HTML) on the badges page.
---
name: nestjs-crud-pattern
description: House pattern for building complete CRUD domains in NestJS with Prisma — controller/service/repository with dependency inversion, validated DTOs and multi-tenant isolation. DEPRECATED — no active Wibi backend uses NestJS today; use only if a project explicitly picks NestJS. For the real production stack (Express), use express-prisma-pattern instead.
---
> ⚠️ **Deprecated**: no active Wibi backend uses NestJS — the 3 real backends (`dream-book-api`, `dream-book-api-agent`, `simple-management-api`) are Express + Prisma in a manual Clean Architecture shape. Kept for historical reference, or in case a future project explicitly picks NestJS. For the real production stack, use [`express-prisma-pattern`](../express-prisma-pattern/SKILL.md).
# NestJS CRUD — house pattern
Every CRUD domain follows exactly the same shape. The project's first module is the mold; the rest copy its form. Do not invent variations.
## Per-domain structure
```
src/modules/<domain>/
├── <domain>.controller.ts # HTTP only: receives request, returns response. Zero business logic.
├── <domain>.service.ts # Business logic. Depends on the repository ABSTRACTION, never on Prisma directly.
├── <domain>.repository.ts # Abstract class (contract) + Prisma implementation in the same file.
├── dto/
│ ├── create-<domain>.dto.ts
│ └── update-<domain>.dto.ts
└── <domain>.module.ts # Wiring: binds the contract to the implementation via provider token.
```
Cross-cutting concerns (PrismaService, guards, context decorators) live in `src/shared/`.
## The repository contract (DIP in practice)
```typescript
// <domain>.repository.ts
export abstract class TaskRepository {
abstract create(workspaceId: string, data: CreateTaskDto): Promise<Task>;
abstract findAll(workspaceId: string): Promise<Task[]>;
abstract findById(workspaceId: string, id: string): Promise<Task | null>;
abstract update(workspaceId: string, id: string, data: UpdateTaskDto): Promise<Task>;
abstract softDelete(workspaceId: string, id: string): Promise<void>;
}
@Injectable()
export class PrismaTaskRepository extends TaskRepository {
constructor(private readonly prisma: PrismaService) { super(); }
// ... implementation: EVERY query filters by workspaceId
}
```
```typescript
// <domain>.module.ts
@Module({
controllers: [TaskController],
providers: [
TaskService,
{ provide: TaskRepository, useClass: PrismaTaskRepository },
],
})
export class TaskModule {}
```
The service injects `TaskRepository` (the contract). Swapping the implementation or mocking in tests becomes trivial.
## Non-negotiable rules
1. **`workspaceId` always comes from the authenticated context (JWT), never from the request body.** If Auth doesn't exist yet, use a marker constant (`FAKE_WORKSPACE_ID`) with a `// TODO: extract from JWT` comment — and register it as a visible pending item.
2. **Tenant isolation enforced at the data layer**: every repository query filters by `workspaceId`. No exceptions, not even for "internal" queries.
3. **Permissions validated on the server** (guard/decorator), never trusted to the frontend.
4. **Dates always in UTC.**
5. **DTOs with `class-validator`** and a global `ValidationPipe` enabled in `main.ts` (`whitelist: true`).
6. **Delete is soft delete** by default (`deletedAt`), unless an explicit decision says otherwise.
## SOLID, feet on the ground
- **SRP**: one responsibility per layer. The controller doesn't validate business rules; the service doesn't build SQL; the repository doesn't decide policy.
- **DIP**: service ↔ repository contract, as above.
- **OCP/ISP without excess**: do NOT create a generic `BaseRepository<T>`, do NOT build hexagonal ports/adapters, do NOT abstract what isn't needed yet. Abstraction only where the cost pays off.
## Source of truth for the model
The exact fields of each entity come from the project's data model document (UML/diagram in `docs/`). If the document doesn't exist or the entity isn't in it, **stop and ask** before inventing fields. Never run a production migration without checking the schema against the source of truth.
## Definition of done (per domain)
- [ ] Model in Prisma + migration applied (table actually created)
- [ ] Complete module: POST, GET list, GET by id, PATCH, DELETE responding
- [ ] `workspaceId` from context, filtered queries, DTOs validating
- [ ] Lint and build clean, project boots locally
- [ ] Registered in `AppModule`
- [ ] Documented: the work goes into the day's delivery log (see `git-workflow` rule)
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!