Опирается на правила:
R-HDR-1..4иR-HDR-X1из REST API Style Guide → раздел Заголовки и трассировка.
Важно знать
- Стандартные заголовки — по назначению (
Content-Type,Authorization,Location,ETag).- Кастомные — с доменным префиксом, без
X-(устарел RFC 6648).Idempotency-Key— через@Headers('Idempotency-Key')для POST с retry-безопасностью.traceparent—@opentelemetry/instrumentation-httpпрокидывает автоматически.traceIdизtraceparentиспользуется в теле ошибки RFC 9457.Location—res.location(...)при 201 Created.
NestJS работает с заголовками через декораторы @Headers, @Header, res.setHeader(). Трассировка через OpenTelemetry SDK — автоматически без ручного кода в контроллерах.
Стандартные заголовки
R-HDR-1:
| Header | Назначение | Где в NestJS |
|---|---|---|
Content-Type | тип тела | res.type(...) или Express default |
Accept | ожидаемый тип ответа | читать через @Headers('accept') |
Authorization | аутентификация | @Headers('authorization') или Guard |
Location | URL созданного ресурса | res.location('/api/v1/orders/...') |
ETag | версия ресурса | res.setHeader('ETag', '"abc123"') |
Cache-Control | кеширование | res.setHeader('Cache-Control', 'private, max-age=60') |
@Get(':id')
async findOne(
@Param('id') id: string,
@Headers('if-none-match') ifNoneMatch: string,
@Res({ passthrough: true }) res: Response,
) {
const order = await this.ordersService.findOne(id);
const etag = `"${order.version}"`;
if (ifNoneMatch === etag) {
res.status(304).end();
return;
}
res.setHeader('ETag', etag);
return order;
}
Location при создании
R-RSP-3:
@Post()
async create(
@Body() dto: CreateOrderDto,
@Res({ passthrough: true }) res: Response,
) {
const order = await this.ordersService.create(dto);
res.location(`/api/v1/orders/${order.orderId}`);
return order;
}
Кастомные заголовки с доменным префиксом
R-HDR-2: единый префикс для всех сервисов проекта.
@Post()
async create(
@Headers('shop-request-id') requestId: string, // ✓ доменный префикс
@Headers('shop-client-version') clientVersion: string,
@Body() dto: CreateOrderDto,
) {
this.logger.log({ requestId, clientVersion }, 'create order');
return this.ordersService.create(dto);
}
// ✗ X- префикс — устарел по RFC 6648
@Headers('x-request-id')
// ✓
@Headers('shop-request-id')
Shop-Request-Id vs traceparent:
Shop-Request-Id— идентификатор конкретного запроса от клиента (дедупликация, логирование).traceparent— идентификатор всей цепочки вызовов (distributed trace).
Idempotency-Key
R-HDR-3: безопасный retry для POST.
export class IdempotencyGuard implements CanActivate {
constructor(private readonly idempotencyService: IdempotencyService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const req = context.switchToHttp().getRequest<Request>();
const key = req.headers['idempotency-key'] as string;
if (!key) {
throw new BadRequestException('Idempotency-Key header is required');
}
const existing = await this.idempotencyService.find(key);
if (existing) {
const res = context.switchToHttp().getResponse<Response>();
res.status(existing.status).json(existing.body);
return false;
}
return true;
}
}
@Post()
@UseGuards(IdempotencyGuard)
async create(
@Headers('idempotency-key') key: string,
@Body() dto: CreateOrderDto,
): Promise<OrderResponse> {
return this.ordersService.create(dto, key);
}
Контракт:
- Клиент генерирует
Idempotency-Key(UUID v4) один раз на бизнес-операцию. - Повторный POST с тем же ключом → сервер возвращает первый результат.
- Другой payload с тем же ключом →
409 Conflict.
traceparent — W3C Trace Context
R-HDR-4: через @opentelemetry/instrumentation-http.
// main.ts — инициализация до NestFactory.create
import { NodeSDK } from '@opentelemetry/sdk-node';
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
const sdk = new NodeSDK({
instrumentations: [new HttpInstrumentation()],
});
sdk.start();
После этого traceparent извлекается из входящего запроса автоматически — trace-id доступен через OpenTelemetry API:
// telemetry/trace-context.ts
import { context, trace } from '@opentelemetry/api';
export function getTraceId(): string {
const span = trace.getActiveSpan();
return span?.spanContext().traceId ?? '';
}
Использование traceId в теле ошибки RFC 9457 (см. Ошибки):
// edge/problem.ts
export function sendProblem(res: Response, status: number, title: string, detail: string): void {
res.status(status).type('application/problem+json').json({
title,
status,
detail,
traceId: getTraceId(), // из traceparent
});
}
Структура traceparent:
00-1f2a8b6c7d3e4f5a9b0c1d2e3f4a5b6c-7a8b9c0d1e2f3a4b-01
│ │ │ │
│ trace-id (32 hex) parent-id (16) flags
version
Правила обработки:
- Клиент прислал
traceparent→ использовать егоtrace-id, создать новыйparent-id. - Клиент не прислал →
HttpInstrumentationгенерирует новыйtraceparentна входе.
Что запрещено
| Антипаттерн | Правило | Что взамен |
|---|---|---|
@Headers('x-request-id') кастомный | R-HDR-X1 | @Headers('shop-request-id') |
@Headers('x-tenant-id') | R-HDR-X1 | @Headers('shop-tenant-id') |
Authorization без Bearer для JWT | R-HDR-1 | Bearer eyJhbGci... |
Idempotency-Key для GET-запросов | R-HDR-3 | только POST/PATCH |
Самописный Tracking-Id вместо traceparent | R-HDR-4 | W3C standard + OTel |
trace-id 16 hex вместо 32 | R-HDR-4 | 32 hex |
Location без /api/v1/ префикса | R-RSP-3 | полный путь |
Куда дальше
- Ошибки RFC 9457 —
traceIdв теле ошибки. - JSON и формат ответов —
Locationпри 201. - Rate limiting, файлы, deprecation —
Retry-After,Sunset. - REST API Style Guide (нормативно) — формулировки.