# Application

> Use when creating application layer artifacts for a NestJS bounded context — use cases, CQRS command/query handlers, DTOs, ports (cross-module interfaces), application services, or read model projections. Supports three patterns: plain UseCase (A), CQRS native (B), and Handler-as-Orchestrator (C).

- **Type:** Skill
- **Install:** `agentstack add skill-softtor-nestjs-hexagonal-application`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [Softtor](https://agentstack.voostack.com/s/softtor)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [Softtor](https://github.com/Softtor)
- **Source:** https://github.com/Softtor/nestjs-hexagonal/tree/main/skills/application
- **Website:** https://github.com/Softtor/nestjs-hexagonal#readme

## Install

```sh
agentstack add skill-softtor-nestjs-hexagonal-application
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

> **Note:** Examples use `companyId` as the multi-tenant identifier. Replace with your project's term (e.g., `organizationId`, `tenantId`).

# Application Layer

This skill covers everything inside a bounded context's `application/` directory:
use cases, CQRS handlers, DTOs, ports, application services, and Redis read models.

---

## 1. Pattern Selection

Work through this flowchart before writing a single line of code.

```
Does the module already use CQRS (CommandBus / QueryBus)?
├── No  → Pattern A (plain UseCase + TOKEN)
└── Yes →
        Is this a simple read (findById) with no RBAC or transformation?
        ├── Yes → Skip use case — inject repository directly in controller
        └── No  →
                Does orchestration span multiple services (QueryBus calls,
                multiple ports, complex enrichment)?
                ├── Yes → Pattern C (Handler as Orchestrator)
                └── No  → Pattern B (CQRS Command/Query)

Need read model / projections?
└── CQRS R/W separation: write side → Pattern B/C, read side → Redis query handler
    (see references/read-model-patterns.md)

Reusable logic between 2+ handlers?
└── Extract into Application Service (@Injectable, in application/services/)

Shared domain logic (no I/O, no framework)?
└── Extract into Domain Service (plain class, in domain/services/)
```

---

## 2. Pattern A — Plain UseCase with TOKEN

Use when the context uses plain use cases without CQRS (e.g., `customers`, `contacts`).

### Directory layout

```
application/
├── dtos/
│   └── create-.dto.ts
└── usecases/
    ├── create-.usecase.ts
    └── __tests__/
        └── create-.usecase.spec.ts
```

### DTO

```typescript
// application/dtos/create-.dto.ts
export namespace CreateDto {
  export interface Input {
    companyId: string;
    name: string;
    // add domain-specific fields
  }

  export interface Output {
    id: string;
    companyId: string;
    name: string;
    createdAt: Date;
  }
}
```

### UseCase class

No `@Injectable`, no NestJS imports. Pure TypeScript class.

```typescript
// application/usecases/create-.usecase.ts
import type { Repository } from '../../domain/repositories/.repository';
import type { CreateDto } from '../dtos/create-.dto';
import { Entity } from '../../domain/entities/.entity';
import { OutputMapper } from '../dtos/-output.dto';

export const CREATE__USE_CASE_TOKEN = Symbol('CreateUseCase');

export namespace CreateUseCase {
  export type Input = CreateDto.Input;
  export type Output = CreateDto.Output;

  export class UseCase {
    constructor(
      private readonly repository: Repository.Repository,
    ) {}

    async execute(input: Input): Promise {
      const entity = Entity.create({
        companyId: input.companyId,
        name: input.name,
      });

      await this.repository.insert(entity);

      return OutputMapper.toOutput(entity);
    }
  }
}
```

### Module registration

```typescript
// infrastructure/.module.ts
import {
  CREATE__USE_CASE_TOKEN,
  CreateUseCase,
} from '../application/usecases/create-.usecase';
import { _REPOSITORY_TOKEN } from '../domain/repositories/.repository';

@Module({
  providers: [
    PrismaRepository,
    {
      provide: _REPOSITORY_TOKEN,
      useExisting: PrismaRepository,
    },
    {
      provide: CREATE__USE_CASE_TOKEN,
      useFactory: (repo: Repository.Repository) =>
        new CreateUseCase.UseCase(repo),
      inject: [_REPOSITORY_TOKEN],
    },
  ],
  exports: [_REPOSITORY_TOKEN], // NEVER export use cases
})
export class Module {}
```

### Controller injection

```typescript
constructor(
  @Inject(CREATE__USE_CASE_TOKEN)
  private readonly createUseCase: CreateUseCase.UseCase,
) {}
```

Full template: `references/usecase-plain.md`

---

## 3. Pattern B — CQRS Command/Query Handler

Use when the module already integrates `CqrsModule`. Handlers are self-registering via `@CommandHandler` / `@QueryHandler` — no token needed.

### Directory layout

```
application/
├── dtos/
│   ├── create-.dto.ts
│   └── get-.dto.ts
├── commands/
│   ├── create-.command.ts
│   └── create-.handler.ts
└── queries/
    ├── get-.query.ts
    └── get-.handler.ts
```

### Command

```typescript
// application/commands/create-.command.ts
import { Command } from '@nestjs/cqrs';
import type { CreateDto } from '../dtos/create-.dto';

export class CreateCommand extends CommandDto.Output> {
  constructor(
    public readonly companyId: string,
    public readonly name: string,
  ) {
    super();
  }
}
```

### Command Handler

```typescript
// application/commands/create-.handler.ts
import { Inject } from '@nestjs/common';
import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs';

import type { Repository } from '../../domain/repositories/.repository';
import { _REPOSITORY_TOKEN } from '../../domain/repositories/.repository';
import { Entity } from '../../domain/entities/.entity';
import type { CreateDto } from '../dtos/create-.dto';
import { CreateCommand } from './create-.command';

@CommandHandler(CreateCommand)
export class CreateHandler
  implements ICommandHandlerCommand, CreateDto.Output>
{
  constructor(
    @Inject(_REPOSITORY_TOKEN)
    private readonly repository: Repository.Repository,
    private readonly publisher: EventPublisher,
  ) {}

  async execute(command: CreateCommand): PromiseDto.Output> {
    // Create entity (entity.apply(event) happens inside Entity.create)
    const entity = Entity.create({
      companyId: command.companyId,
      name: command.name,
    });

    await this.repository.insert(entity);

    // mergeObjectContext and commit happen in the HANDLER — never in the use case
    this.publisher.mergeObjectContext(entity);
    entity.commit(); // dispatches domain events via EventBus

    return { id: entity.id };
  }
}
```

Key rules for command handlers:
- Write commands return `void` or `{ id: string }` — never the full aggregate.
- `EventPublisher` is injected ONLY in the handler, never in use cases.
- `mergeObjectContext(entity)` + `entity.commit()` happen in the handler after persisting.
- Entity calls `this.apply(event)` internally — that is framework-agnostic.

### Query

```typescript
// application/queries/get-.query.ts
import { Query } from '@nestjs/cqrs';
import type { GetDto } from '../dtos/get-.dto';

export class GetQuery extends QueryDto.Output> {
  constructor(
    public readonly id: string,
    public readonly companyId: string,
  ) {
    super();
  }
}
```

### Query Handler

```typescript
// application/queries/get-.handler.ts
import { Inject, NotFoundException } from '@nestjs/common';
import { IQueryHandler, QueryHandler } from '@nestjs/cqrs';

import type { Repository } from '../../domain/repositories/.repository';
import { _REPOSITORY_TOKEN } from '../../domain/repositories/.repository';
import type { GetDto } from '../dtos/get-.dto';
import { GetQuery } from './get-.query';
import { OutputMapper } from '../dtos/-output.dto';

@QueryHandler(GetQuery)
export class GetHandler
  implements IQueryHandlerQuery, GetDto.Output>
{
  constructor(
    @Inject(_REPOSITORY_TOKEN)
    private readonly repository: Repository.Repository,
  ) {}

  async execute(query: GetQuery): PromiseDto.Output> {
    const entity = await this.repository.findById(query.id);

    if (!entity || entity.companyId !== query.companyId) {
      throw new NotFoundException();
    }

    return OutputMapper.toOutput(entity);
  }
}
```

### Module registration

```typescript
// Handlers are self-registering — just add to providers array:
providers: [
  ...existingProviders,
  CreateHandler,
  GetHandler,
],
```

### Controller dispatch

```typescript
constructor(
  private readonly commandBus: CommandBus,
  private readonly queryBus: QueryBus,
) {}

// CommandBus.execute() return type inferred from Command
const result = await this.commandBus.execute(
  new CreateCommand(companyId, name),
);

const item = await this.queryBus.execute(
  new GetQuery(id, companyId),
);
```

Full template: `references/cqrs-command-query.md`

---

## 4. Pattern C — Handler as Orchestrator

Use when a handler must coordinate multiple dependencies: external port calls, QueryBus resolution, complex enrichment before delegating to a pure UseCase.

The handler receives NestJS DI and instantiates framework-agnostic use cases via `new`.
The use case returns the entity so the handler can commit domain events:

```typescript
// application/commands/create-invoice.handler.ts
@CommandHandler(CreateInvoiceCommand)
export class CreateInvoiceHandler
  implements ICommandHandler
{
  private readonly useCase: CreateInvoiceUseCase.UseCase;

  constructor(
    private readonly queryBus: QueryBus,
    @Inject(INVOICE_REPOSITORY_TOKEN)
    invoiceRepository: InvoiceRepository.Repository,
    @Inject(CUSTOMER_PORT_TOKEN)
    private readonly customerPort: CustomerPort,
    @Inject(LOGGER_PORT_TOKEN)
    logger: LoggerPort,
    private readonly publisher: EventPublisher,
  ) {
    // Instantiate framework-agnostic use case here
    this.useCase = new CreateInvoiceUseCase.UseCase(invoiceRepository, logger);
  }

  async execute({ input }: CreateInvoiceCommand): Promise {
    // 1. Resolve external dependencies (ports, QueryBus)
    const config = await this.queryBus.execute(
      new ResolveConfigQuery(input.companyId),
    );

    // 2. Validate / enrich
    const customer = await this.customerPort.findById(input.customerId);

    // 3. Delegate business logic to use case — useCase returns the entity
    const entity = await this.useCase.execute({
      ...input,
      config,
      customer,
    });

    // 4. Handler commits domain events (EventPublisher never enters the use case)
    this.publisher.mergeObjectContext(entity);
    entity.commit();

    return { id: entity.id };
  }
}

// application/usecases/create-invoice.usecase.ts — NO NestJS imports
export namespace CreateInvoiceUseCase {
  export class UseCase {
    constructor(
      private readonly repository: InvoiceRepository.Repository,
      private readonly logger: LoggerPort,
    ) {}

    // Returns the entity so the handler can commit domain events
    async execute(input: Input): Promise {
      const entity = InvoiceEntity.create({ ...input }); // entity.apply() happens here
      await this.repository.insert(entity);
      return entity;
    }
  }
}
```

Key rules for Pattern C:
- UseCase returns the entity — handler owns `mergeObjectContext` + `commit`.
- `EventPublisher` is injected ONLY in the handler.
- UseCase has zero NestJS imports (except entity base class from shared/domain).
- UseCase is testable without NestJS — just `new UseCase(mockRepo)`.

Benefits:
- UseCase is 100% framework-agnostic — testable without NestJS.
- Handler is a thin wiring layer — testable by mocking only external dependencies.
- UseCase can be reused in other handlers or frameworks.

Full template: `references/cqrs-orchestrator.md`

---

## 5. DTOs

### Namespace pattern (preferred for write operations)

```typescript
// application/dtos/create-.dto.ts
export namespace CreateDto {
  export interface Input {
    companyId: string;
    name: string;
  }

  export interface Output {
    id: string;
  }
}
```

### Inline in Command class (acceptable for simple commands)

```typescript
export class CreateCommand extends Command {
  constructor(
    public readonly companyId: string,
    public readonly data: { name: string; description?: string },
  ) {
    super();
  }
}
```

### Output mapper (always extract when mapping is non-trivial)

```typescript
// application/dtos/-output.dto.ts
export class OutputMapper {
  static toOutput(entity: Entity): Output {
    return {
      id: entity.id,
      companyId: entity.companyId,
      name: entity.name,
      createdAt: entity.createdAt,
      updatedAt: entity.updatedAt,
    };
  }
}
```

Full template: `references/dto-patterns.md`

---

## 6. Ports (Cross-Module Communication)

A port is an interface defined by the **consumer** module in `application/ports/`. The **provider** module implements it via an adapter in `infrastructure/adapters/`.

```typescript
// /application/ports/company.port.ts
export const COMPANY_PORT_TOKEN = Symbol('CompanyPort');

export interface CompanyPort {
  findById(id: string): Promise;
}
```

Module wiring in the provider module:

```typescript
// /infrastructure/.module.ts
providers: [
  CompanyPortAdapter,
  {
    provide: COMPANY_PORT_TOKEN,
    useExisting: CompanyPortAdapter,
  },
],
exports: [COMPANY_PORT_TOKEN],
```

Full template: `references/port-patterns.md`

---

## 7. Application Services

Extract reusable logic shared by 2+ handlers into an Application Service. If only one handler uses it, keep it inline.

```typescript
// application/services/-builder.service.ts
import { Injectable, Inject } from '@nestjs/common';

@Injectable()
export class BuilderService {
  constructor(
    @Inject(SOME_PORT_TOKEN)
    private readonly somePort: SomePort,
  ) {}

  async build(input: BuildInput): Promise {
    // Shared logic
  }
}
```

Register in module `providers` and inject into handlers via `@Inject` or constructor injection.

---

## 8. Domain Services

Shared domain logic with no I/O and no framework. Pure class, no decorators.

```typescript
// domain/services/-resolver.ts
export class Resolver {
  resolve(entity: Entity, data: ResolveInput): ResolveOutput {
    // Pure domain logic
  }
}
```

Instantiate directly in the handler or via a factory in the module provider.

---

## 9. Read Model / CQRS R/W Separation

When queries hit Redis instead of Postgres:

- Write side: Command handler → repository → Prisma/PostgreSQL → emit domain event.
- Read side: Query handler → Redis (projected view) → fallback to Prisma on miss.
- Projection updater: `@EventsHandler` that writes to Redis when domain events fire.

Full template: `references/read-model-patterns.md`

---

## 10. Event Handlers

React to domain events dispatched via `entity.commit()`. Use `@EventsHandler`, never `@OnEvent`, for new code.

```typescript
// infrastructure/listeners/-created.handler.ts
import { EventsHandler, IEventHandler } from '@nestjs/cqrs';
import { CreatedEvent } from '../../domain/events/-created.event';

@EventsHandler(CreatedEvent)
export class CreatedHandler implements IEventHandlerCreatedEvent> {
  handle(event: CreatedEvent): void {
    // side effects: RabbitMQ publish, WebSocket broadcast, Redis projection
  }
}
```

---

## 11. Rules

- No `@Injectable` / `@Inject` / NestJS imports in UseCase classes (Pattern A and C use cases).
- CQRS handlers (`@CommandHandler`, `@QueryHandler`, `@EventsHandler`) ARE allowed NestJS decorators.
- Always include `companyId` in queries and mutations (multi-tenant enforcement).
- Commands extend `Command`, Queries extend `Query` (NestJS CQRS native).
- Handlers specify full return type: `ICommandHandler`.
- Entity emits events via `this.apply(event)` — never `addDomainEvent()`.
- Handler calls `publisher.mergeObjectContext(entity)` + `entity.commit()` — never `pullDomainEvents()`.
- Modules export only PORT tokens — never use cases, never Prisma repositories.
- `useFactory` + `inject` for use cases in module providers — never `useClass`.
- Ports are defined in the **consumer** module's `application/ports/`, not the provider's.

---

## 12. Testing Checklist

- [ ] UseCase unit test: mock repository, assert output shape.
- [ ] Command handler test: mock repository + EventPublisher, assert `entity.commit()` called.
- [ ] Query handler test: mock repository, assert output mapper applied.
- [ ] Port test: mock adapter, assert interface contract.
- [ ] Application service test: mock ports, test shared logic in isolation.
- [ ] Read model test: mock Redis port, assert fallback to Prisma on miss.

---

## Reference Files

| File | Pattern |
|------|---------|
| `references/usecase-plain.md` | Pattern A full template with tests |
| `references/cqrs-command-query.md` | Pattern B full template with tests |
| `references/cqrs-orchestrator.md` | Pattern C f

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [Softtor](https://github.com/Softtor)
- **Source:** [Softtor/nestjs-hexagonal](https://github.com/Softtor/nestjs-hexagonal)
- **License:** MIT
- **Homepage:** https://github.com/Softtor/nestjs-hexagonal#readme

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-softtor-nestjs-hexagonal-application
- Seller: https://agentstack.voostack.com/s/softtor
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
