# Presentation

> Use when creating presentation layer artifacts for a NestJS bounded context — REST controllers, request DTOs with class-validator, Swagger decorators, custom validators, error filters, or API documentation. Covers the HTTP boundary with proper validation and error mapping.

- **Type:** Skill
- **Install:** `agentstack add skill-softtor-nestjs-hexagonal-presentation`
- **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/presentation
- **Website:** https://github.com/Softtor/nestjs-hexagonal#readme

## Install

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

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

## About

# Presentation Layer

The presentation layer is the HTTP boundary. It owns input validation, Swagger docs, and error mapping. It must not contain business logic.

## Directory Structure

```
infrastructure/
└── controllers/
    ├── .controller.ts
    ├── __tests__/
    │   └── .controller.spec.ts
    └── dtos/
        ├── create-.request.dto.ts
        ├── update-.request.dto.ts
        ├── list-.request.dto.ts
        └── __tests__/
            └── create-.request.dto.spec.ts
```

Or as a top-level `presentation/` folder when separated from `infrastructure/`:

```
presentation/
├── controllers/
│   └── .controller.ts
├── dtos/
│   └── create-.http-dto.ts
├── presenters/
│   └── create-.presenter.ts     # maps use case output to HTTP response
└── validators/
    └── .validator.ts              # @ValidatorConstraint classes
```

## 1. Controller Pattern

Use `CommandBus` / `QueryBus` for CQRS contexts. Use `@Inject(TOKEN)` for plain use case contexts.

```typescript
// controllers/.controller.ts
import {
  Controller, Post, Get, Patch, Delete, Body, Param, Query,
  HttpCode, HttpStatus, UseGuards,
} from '@nestjs/common';
import { CommandBus, QueryBus } from '@nestjs/cqrs';
import {
  ApiTags, ApiBearerAuth, ApiOperation, ApiResponse, ApiBody,
} from '@nestjs/swagger';
import { AuthGuard } from '@/auth/guards/auth.guard';
import { CurrentUser, User } from '@/auth/decorators/current-user.decorator';
import { CurrentOrganization, OrgContext } from '@/auth/decorators/current-organization.decorator';
import { CreateRequestDto } from './dtos/create-.request.dto';
import { CreateCommand } from '../../application/commands/create-.command';
import { GetQuery } from '../../application/queries/get-.query';
import { ListQuery } from '../../application/queries/list-.query';

@ApiTags('')
@Controller('')
@UseGuards(AuthGuard)
@ApiBearerAuth()
export class Controller {
  constructor(
    private readonly commandBus: CommandBus,
    private readonly queryBus: QueryBus,
  ) {}

  @Post()
  @HttpCode(HttpStatus.CREATED)
  @ApiOperation({ summary: 'Create a new ' })
  @ApiResponse({ status: 201, description: ' created' })
  @ApiResponse({ status: 400, description: 'Validation error' })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  async create(
    @Body() dto: CreateRequestDto,
    @CurrentOrganization() org: OrgContext,
  ): Promise {
    return this.commandBus.execute(
      new CreateCommand(org.id, dto.name),
    );
  }

  @Get(':id')
  @ApiOperation({ summary: 'Get  by id' })
  @ApiResponse({ status: 200, description: ' found' })
  @ApiResponse({ status: 404, description: ' not found' })
  async findOne(
    @Param('id') id: string,
    @CurrentOrganization() org: OrgContext,
  ) {
    return this.queryBus.execute(new GetQuery(id, org.id));
  }

  @Get()
  @ApiOperation({ summary: 'List  with pagination' })
  async findAll(
    @Query() query: ListRequestDto,
    @CurrentOrganization() org: OrgContext,
  ) {
    return this.queryBus.execute(
      new ListQuery({ organizationId: org.id, ...query }),
    );
  }

  @Delete(':id')
  @HttpCode(HttpStatus.NO_CONTENT)
  @ApiOperation({ summary: 'Delete a ' })
  @ApiResponse({ status: 204, description: 'Deleted' })
  async remove(
    @Param('id') id: string,
    @CurrentOrganization() org: OrgContext,
  ): Promise {
    await this.commandBus.execute(new DeleteCommand(id, org.id));
  }
}
```

See full template: `references/controller-patterns.md`

## 2. Request DTOs

`class-validator` and `class-transformer` live ONLY in request DTOs. Never in domain VOs or application services.

```typescript
// controllers/dtos/create-.request.dto.ts
import { IsString, IsNotEmpty, IsOptional, IsEmail } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';

export class CreateRequestDto {
  @ApiProperty({ description: 'Name', example: 'My ' })
  @IsString()
  @IsNotEmpty()
  name!: string;

  @ApiPropertyOptional({ description: 'Description', example: 'Optional description' })
  @IsOptional()
  @IsString()
  description?: string;
}
```

Global `ValidationPipe` config (in `main.ts`):

```typescript
app.useGlobalPipes(
  new ValidationPipe({ transform: true, whitelist: true, forbidNonWhitelisted: true }),
);
```

See full template: `references/request-dto-patterns.md`

## 3. Custom Validators

For cross-field or complex format rules that `class-validator` built-ins cannot express.

```typescript
// validators/.validator.ts
import {
  ValidatorConstraint, ValidatorConstraintInterface,
  ValidationArguments, ValidationOptions, registerDecorator,
} from 'class-validator';

@ValidatorConstraint({ name: '', async: false })
export class Validator implements ValidatorConstraintInterface {
  validate(value: unknown, args: ValidationArguments): boolean {
    // Return true = valid, false = invalid
    return typeof value === 'string' && value.length > 0;
  }

  defaultMessage(): string {
    return ' is invalid';
  }
}

export function Is(options?: ValidationOptions) {
  return function (object: object, propertyName: string) {
    registerDecorator({
      target: object.constructor,
      propertyName,
      options,
      constraints: [],
      validator: Validator,
    });
  };
}
```

See full template: `references/request-dto-patterns.md` (custom validator section)

## 4. Validation Layers

Each layer has a single validation responsibility. Do not duplicate across layers.

```
Request DTO (presentation)     — format, presence, types (class-validator)
     ↓
Application DTO (application)  — contract between layers (TypeScript interfaces)
     ↓
Domain VO (domain)             — business invariants (manual validate())
     ↓
Queue Schema (integration)     — inter-service contracts (Zod)
```

See full diagram: `references/validation-layers.md`

## 5. Error Filter

Maps domain errors to HTTP status codes. Registered globally.

```typescript
// infrastructure/filters/http-exception.filter.ts
import {
  ExceptionFilter, Catch, ArgumentsHost, HttpStatus, Logger,
} from '@nestjs/common';
import type { FastifyReply, FastifyRequest } from 'fastify';
import { NotFoundError } from '@/shared/domain/errors/not-found-error';
import { BusinessRuleViolationError } from '@/shared/domain/errors/business-rule-violation.error';
import { ConflictError } from '@/shared/domain/errors/conflict.error';

const ERROR_STATUS_MAP: Array Error, HttpStatus]> = [
  [NotFoundError, HttpStatus.NOT_FOUND],
  [BusinessRuleViolationError, HttpStatus.UNPROCESSABLE_ENTITY],
  [ConflictError, HttpStatus.CONFLICT],
];

@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
  private readonly logger = new Logger(HttpExceptionFilter.name);

  catch(exception: unknown, host: ArgumentsHost): void {
    const ctx = host.switchToHttp();
    const reply = ctx.getResponse();
    const request = ctx.getRequest();

    let status = HttpStatus.INTERNAL_SERVER_ERROR;
    for (const [ErrorClass, httpStatus] of ERROR_STATUS_MAP) {
      if (exception instanceof ErrorClass) {
        status = httpStatus;
        break;
      }
    }

    const message =
      exception instanceof Error ? exception.message : 'Internal server error';

    if (status === HttpStatus.INTERNAL_SERVER_ERROR) {
      this.logger.error(exception);
    }

    void reply.status(status).send({
      statusCode: status,
      message,
      timestamp: new Date().toISOString(),
      path: request.url,
    });
  }
}
```

See full template: `references/error-filter-patterns.md`

## 6. Swagger Documentation

Always add `@ApiOperation`, `@ApiResponse`, and `@ApiProperty`. Include request body examples for complex DTOs.

```typescript
@ApiBody({
  type: CreateRequestDto,
  examples: {
    basic: {
      summary: 'Basic example',
      value: { name: 'My ' },
    },
  },
})
```

## Rules

- `class-validator` decorators ONLY in request DTOs — never in domain VOs or application services.
- Global `ValidationPipe({ transform: true, whitelist: true, forbidNonWhitelisted: true })`.
- Controller methods contain no business logic — delegate immediately to `CommandBus`/`QueryBus` or use case.
- Always include `organizationId` from the current auth context — never trust it from the request body.
- `HttpExceptionFilter` maps domain errors to status codes — domain layer never imports NestJS.
- All endpoints documented with `@ApiOperation` and `@ApiResponse`.

## Checklist

- [ ] Controller: `@ApiTags`, `@UseGuards`, `@ApiBearerAuth`
- [ ] Controller: organization context from `@CurrentOrganization()`, not from body/params
- [ ] Request DTOs: `class-validator` decorators + `@ApiProperty`
- [ ] Request DTOs: `@IsOptional()` before other decorators for optional fields
- [ ] Nested DTOs: `@ValidateNested()` + `@Type(() => NestedDto)`
- [ ] Array DTOs: `@IsArray()` + `@ValidateNested({ each: true })` + `@Type(() => ItemDto)`
- [ ] Custom validators: `@ValidatorConstraint` + `registerDecorator` factory
- [ ] Error filter registered globally or at module level
- [ ] Swagger: `@ApiOperation`, `@ApiResponse` for all endpoints
- [ ] Controller tests: mock `CommandBus`/`QueryBus`, test routing and DTO mapping
- [ ] DTO tests: use `class-validator` `validate()` to test valid/invalid inputs
- [ ] Run `pnpm lint && pnpm check-types` before committing

## 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:** no
- **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-presentation
- 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%.
