mattiavalerio/mvskills

nestjs-architecture

Architectural rules for NestJS server code — Clean Architecture organised as vertical slices (bounded-context modules containing one folder per use case), with Kysely, nestjs-zod, neverthrow and Vitest.

查看源码

使用方法

  1. 复制安装命令,在自己的智能体环境安装前先检查源码包。
  2. 从原始文档核对所需工具、凭据和支持的宿主。
  3. 说明输入材料与期望交付物,并对照来源检查输出。
npx skills add https://github.com/mattiavalerio/mvskills --skill nestjs-architecture

开始前需了解

  • 安装不包含模型订阅、API 额度或访问文件的权限。
  • 仓库星数和安装量反映活跃情况,不等于独立质量或安全测试。
仓库原始内容

按源仓库内容呈现,保留标题、案例、代码、表格、链接以及原文引用的演示图片。

Profile scope and official documentation

Read docs/agents/stack.md when present. Apply this custom stack only to apps selecting the default NestJS profile or explicitly adopting it. In existing apps with another stack preserve conventions; this skill does not authorize migration. For new unconfigured apps agree on the profile using setup-mvskills.

For API/CLI facts start with https://docs.nestjs.com/llms.txt and fetch relevant linked official pages. Use https://docs.nestjs.com/llms-full.txt as a fallback, searching relevant sections rather than loading it wholesale. Match docs to installed versions and command --help; the selected profile owns architecture choices.

NestJS — Clean Architecture with Vertical Slices

This skill tells you what goes where and who may depend on whom. How to generate files with the CLI lives in the nestjs-cli skill; the step-by-step workflow for adding a feature lives in the nestjs-feature skill.

The mental model

Load strict-typescript for authored code, scripts and review. For public HTTP DTOs, responses and frontend contracts load api-contracts when selected by the profile. Use the host's skill tool for these loads, or read the installed entrypoints when it is unavailable. Repository standards and ADRs remain authoritative for the selected app; pass their paths along with this profile to Standards review.

A backend is a set of bounded contexts (orders, billing, catalog…). Each context is one Nest module. Inside a context, every use case is a vertical slice: one folder that holds everything specific to that use case (HTTP entry point, input schema, application logic, tests). What is shared inside a context — the domain model and the persistence contracts — sits next to the slices, not inside them.

src/
├── main.ts
├── app.module.ts                  # imports context modules + shared infrastructure only
├── modules/
│   └── <context>/
│       ├── <context>.module.ts    # composition root of the context (wiring only)
│       ├── <context>.http-errors.ts  # domain error type → HTTP status table
│       ├── domain/                # entities, value objects, domain errors — pure TS
│       ├── ports/                 # abstract classes the use cases depend on
│       ├── infrastructure/        # adapters implementing ports (Kysely, HTTP clients…)
│       ├── public/                # (optional) what other contexts may import
│       └── features/
│           └── <feature>/         # one folder per use case, kebab-case verb-noun
│               ├── <feature>.controller.ts
│               ├── <feature>.dto.ts
│               ├── <feature>.use-case.ts
│               └── <feature>.use-case.spec.ts
└── shared/
    ├── kernel/                    # cross-context pure code: base error types, Result helpers
    ├── http/                      # Result → HttpException mapping, global pipes/filters
    └── infrastructure/            # DatabaseModule (Kysely), config, logger

Feature names are use cases, not CRUD verbs on tables: place-order, cancel-order, get-order-summary — not orders-crud. If you cannot name the slice as an action a user or system performs, the slice is probably wrong.

Dependency rules (the part that matters most)

Dependencies point inwards. These rules are enforced by dependency-cruiser (config in assets/dependency-cruiser.cjs), so breaking them fails validation.

  1. `domain/` is pure. No imports from @nestjs/*, kysely, nestjs-zod, other

layers of the context, or other contexts. It may import neverthrow and shared/kernel. Why: domain logic must be testable and portable without booting Nest.

  1. `ports/` depend only on `domain/` and `shared/kernel`. Ports are abstract classes,

which double as Nest injection tokens — no @Inject() strings or symbols needed.

  1. A slice never imports another slice. If two slices need the same logic, it belongs

in domain/ (business rule) or in a port (data access). Duplicating a small mapping between two slices is acceptable; coupling them is not. Why: slices must be deletable and developable in parallel without touching each other.

  1. Slices never import `infrastructure/`. They depend on ports; the module binds the

port to an adapter.

  1. Contexts talk through `public/` only (exported types, a facade port, or domain

events). Never import another context's domain/, features/ or infrastructure/.

  1. `<context>.module.ts` is the only file that knows everything. It contains wiring

(controllers, providers, { provide: Port, useClass: Adapter }) and nothing else.

Layer responsibilities — short version

LayerContainsNever contains
Controllerroute decorators, DTO in, call use case, map Result → HTTPbusiness rules, DB access, try/catch for flow control
DTOZod schema + createZodDtologic, entity types
Use caseorchestration, returns ResultAsync<Output, Error>HTTP concepts, Kysely, throw for expected failures
Domainentities, invariants, factory functions returning Resultframework imports, I/O
Portabstract class with method signatures returning ResultAsyncimplementation
AdapterKysely queries, row ↔ entity mapping, infra errors → domain errorsbusiness decisions

Conventions you must follow

  • TypeScript strict, zero `any`. Use unknown + Zod parsing at boundaries.
  • Expected failures are values, not exceptions. Use cases and ports return

ResultAsync<T, E> from neverthrow. Only truly unexpected failures (bugs, DB down) may throw. Details: references/errors-and-results.md.

  • Validation happens once, at the edge, with Zod via nestjs-zod. Inside the slice,

data is already typed and trusted.

  • Kysely only for persistence (never Prisma, Drizzle or TypeORM, even though the Nest

docs suggest them). Details: references/persistence-kysely.md.

  • No premature abstraction. Don't add a port for something with one trivial

implementation that will never change (e.g. a clock in a CRUD app). Add it when you need to test against it or swap it.

  • One use case = one class with one public `execute()` method.
  • In the selected mvskills profile use extensionless @/ imports rooted at app src,

e.g. @/shared/kernel/errors. Follow strict-typescript's alias reference for build, watch, test and runtime resolution. Preserve existing import styles until migrated.

When to read the references

  • references/slice-anatomy.md — full annotated example of one slice, plus the module file.

Read it the first time you create a slice in a session.

  • references/errors-and-results.md — domain error shapes, neverthrow patterns,

Result → HTTP mapping. Read it when writing a use case, port or controller.

  • references/persistence-kysely.md — repository adapters, mapping, transactions.

Read it when touching infrastructure/.

  • references/testing-vitest.md — what to test at which level, fakes vs mocks, e2e.

Read it before writing tests.

  • references/decision-guide.md — "where does this go?" for ambiguous cases (shared

logic, cross-context calls, queries vs commands, background jobs).

Bootstrapping a new project

If src/shared/ does not exist yet:

  1. Copy assets/shared/ into src/shared/.
  2. Copy assets/dependency-cruiser.cjs to the project root as .dependency-cruiser.cjs.
  3. Use assets/app.module.example.ts as the reference for app.module.ts (global

ZodValidationPipe + ZodSerializerInterceptor, config, database).

  1. Install: pnpm add neverthrow zod nestjs-zod kysely pg @nestjs/config and

pnpm add -D dependency-cruiser kysely-codegen @types/pg. Verify dependency-cruiser supports the installed TypeScript version and actually analyzes sources. The validation helper fails on zero analyzed modules or a missing TypeScript transpiler; resolve compatibility using current tool documentation. Prove the rules bite: check:arch passes on the clean tree; a temporary import from a slice into infrastructure/ fails with slices-do-not-import-infrastructure; after reverting it passes again. If the planted import does not fail, fix the config or alias resolution before continuing. Leave no planted file behind.

  1. Generate DB types with pnpm kysely-codegen --out-file src/shared/infrastructure/database/db.generated.ts.

按任务相关推荐

来自同一仓库

更多 Skills

全部 Skills