Skip to content

AI Prompt: Forma3D.Connect — Phase 5p: API Versioning Headers

Purpose: This prompt instructs an AI to implement API versioning response headers for better client compatibility
Estimated Effort: 3-4 hours
Prerequisites: Phase 5o completed (Connection Pool)
Output: API version headers in all responses with deprecation support
Status: 🟡 PENDING


🎯 Mission

You are continuing development of Forma3D.Connect, building on the Phase 5o foundation. Your task is to implement Phase 5p: API Versioning Headers — specifically addressing TD-014 (Missing API Versioning Headers) from the technical debt register.

Why This Matters:

While URL versioning exists (/api/v1/), clients lack important metadata:

  1. No Version Confirmation: Clients can't verify which API version they're using
  2. No Deprecation Notices: No way to signal upcoming breaking changes
  3. Hard to Debug: Support can't easily identify API version in logs
  4. No Sunset Dates: Clients aren't informed of end-of-life schedules

Phase 5p delivers:

  • X-API-Version header on all responses
  • X-API-Deprecated header for deprecated endpoints
  • Sunset header for EOL dates
  • X-API-Min-Version for client compatibility checks

📋 Context: Technical Debt Item

TD-014: Missing API Versioning Headers

Attribute Value
Type Architecture Debt
Priority Low-Medium
Location API controllers
Interest Rate Low-Medium
Principal (Effort) 3-4 hours

🛠️ Implementation Phases

Phase 1: Create API Version Configuration (30 minutes)

Priority: Critical | Impact: High | Dependencies: None

1. Add Version Constants

Create apps/api/src/versioning/api-version.constants.ts:

/**
 * API Version Configuration
 * Update these when releasing new API versions
 */
export const API_VERSION = {
  /** Current API version (semver) */
  current: '1.0.0',

  /** Minimum supported client version */
  minSupported: '1.0.0',

  /** API version in URL path */
  urlVersion: 'v1',

  /** Date when v1 will be deprecated (null if not planned) */
  deprecationDate: null as Date | null,

  /** Date when v1 will be sunset/removed (null if not planned) */
  sunsetDate: null as Date | null,
};

/**
 * Standard API response headers
 */
export const API_HEADERS = {
  VERSION: 'X-API-Version',
  MIN_VERSION: 'X-API-Min-Version',
  DEPRECATED: 'X-API-Deprecated',
  SUNSET: 'Sunset',
  DEPRECATION: 'Deprecation',
} as const;

2. Add to Configuration Service (Optional)

If versions should be configurable per environment:

// apps/api/src/config/config.service.ts
get apiVersion(): string {
  return this.configService.get<string>('API_VERSION', '1.0.0');
}

Phase 2: Create Versioning Middleware (1 hour)

Priority: High | Impact: High | Dependencies: Phase 1

1. Create Version Headers Interceptor

Create apps/api/src/versioning/api-version.interceptor.ts:

import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { Response } from 'express';
import { API_VERSION, API_HEADERS } from './api-version.constants';

@Injectable()
export class ApiVersionInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
    const response = context.switchToHttp().getResponse<Response>();

    // Set version headers before response
    this.setVersionHeaders(response);

    return next.handle().pipe(
      tap(() => {
        // Headers already set, nothing more to do
      }),
    );
  }

  private setVersionHeaders(response: Response): void {
    if (response.headersSent) {
      return;
    }

    // Always include current version
    response.setHeader(API_HEADERS.VERSION, API_VERSION.current);
    response.setHeader(API_HEADERS.MIN_VERSION, API_VERSION.minSupported);

    // Add deprecation headers if applicable
    if (API_VERSION.deprecationDate) {
      response.setHeader(API_HEADERS.DEPRECATED, 'true');
      response.setHeader(
        API_HEADERS.DEPRECATION,
        API_VERSION.deprecationDate.toISOString(),
      );
    } else {
      response.setHeader(API_HEADERS.DEPRECATED, 'false');
    }

    // Add sunset header if applicable
    if (API_VERSION.sunsetDate) {
      // RFC 8594 format
      response.setHeader(
        API_HEADERS.SUNSET,
        API_VERSION.sunsetDate.toUTCString(),
      );
    }
  }
}

2. Register Interceptor Globally

Update apps/api/src/app.module.ts:

import { APP_INTERCEPTOR } from '@nestjs/core';
import { ApiVersionInterceptor } from './versioning/api-version.interceptor';

@Module({
  // ...
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: ApiVersionInterceptor,
    },
    // ... other providers
  ],
})
export class AppModule {}

Phase 3: Create Deprecation Decorator (1 hour)

Priority: Medium | Impact: Medium | Dependencies: Phase 2

1. Create Deprecated Endpoint Decorator

Create apps/api/src/versioning/deprecated.decorator.ts:

import { SetMetadata, applyDecorators } from '@nestjs/common';
import { ApiHeader } from '@nestjs/swagger';

export const DEPRECATED_KEY = 'api:deprecated';

export interface DeprecationInfo {
  /** Date when endpoint will be removed */
  sunsetDate?: Date;
  /** Alternative endpoint to use */
  alternative?: string;
  /** Additional deprecation message */
  message?: string;
}

/**
 * Mark an endpoint as deprecated
 * Adds deprecation headers and Swagger documentation
 * 
 * @example
 * @Deprecated({
 *   sunsetDate: new Date('2026-06-01'),
 *   alternative: '/api/v2/orders',
 *   message: 'Use v2 API for improved performance'
 * })
 * @Get('legacy-orders')
 * getLegacyOrders() { ... }
 */
export function Deprecated(info: DeprecationInfo = {}) {
  return applyDecorators(
    SetMetadata(DEPRECATED_KEY, info),
    ApiHeader({
      name: 'X-API-Deprecated',
      description: 'This endpoint is deprecated',
      example: 'true',
    }),
    ApiHeader({
      name: 'Sunset',
      description: 'Date when this endpoint will be removed',
      example: 'Sat, 01 Jun 2026 00:00:00 GMT',
      required: false,
    }),
  );
}

2. Create Deprecation Interceptor

Create apps/api/src/versioning/deprecated.interceptor.ts:

import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
  Logger,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Observable } from 'rxjs';
import { Response } from 'express';
import { DEPRECATED_KEY, DeprecationInfo } from './deprecated.decorator';
import { API_HEADERS } from './api-version.constants';

@Injectable()
export class DeprecatedInterceptor implements NestInterceptor {
  private readonly logger = new Logger(DeprecatedInterceptor.name);

  constructor(private readonly reflector: Reflector) {}

  intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
    const deprecationInfo = this.reflector.get<DeprecationInfo>(
      DEPRECATED_KEY,
      context.getHandler(),
    );

    if (deprecationInfo) {
      const response = context.switchToHttp().getResponse<Response>();
      const request = context.switchToHttp().getRequest();

      this.setDeprecationHeaders(response, deprecationInfo);
      this.logDeprecatedUsage(request, deprecationInfo);
    }

    return next.handle();
  }

  private setDeprecationHeaders(
    response: Response,
    info: DeprecationInfo,
  ): void {
    if (response.headersSent) {
      return;
    }

    response.setHeader(API_HEADERS.DEPRECATED, 'true');

    if (info.sunsetDate) {
      response.setHeader(API_HEADERS.SUNSET, info.sunsetDate.toUTCString());
    }

    if (info.alternative) {
      response.setHeader('Link', `<${info.alternative}>; rel="successor-version"`);
    }

    if (info.message) {
      response.setHeader('X-API-Deprecation-Notice', info.message);
    }
  }

  private logDeprecatedUsage(
    request: { path: string; ip: string },
    info: DeprecationInfo,
  ): void {
    this.logger.warn({
      message: 'Deprecated endpoint accessed',
      path: request.path,
      clientIp: request.ip,
      sunsetDate: info.sunsetDate?.toISOString(),
      alternative: info.alternative,
    });
  }
}

3. Register Deprecation Interceptor

Update apps/api/src/app.module.ts:

import { DeprecatedInterceptor } from './versioning/deprecated.interceptor';

@Module({
  // ...
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: ApiVersionInterceptor,
    },
    {
      provide: APP_INTERCEPTOR,
      useClass: DeprecatedInterceptor,
    },
  ],
})
export class AppModule {}

Phase 4: Add Swagger Documentation (30 minutes)

Priority: Medium | Impact: Low | Dependencies: Phase 2

1. Update Swagger Configuration

Update apps/api/src/main.ts:

import { API_VERSION } from './versioning/api-version.constants';

// In bootstrap function:
const config = new DocumentBuilder()
  .setTitle('Forma3D.Connect API')
  .setDescription('Order fulfillment automation API')
  .setVersion(API_VERSION.current)
  .addApiKey({ type: 'apiKey', name: 'X-API-Key', in: 'header' }, 'api-key')
  .build();

2. Add Global Response Headers to Swagger

const document = SwaggerModule.createDocument(app, config, {
  extraModels: [],
  operationIdFactory: (controllerKey: string, methodKey: string) => methodKey,
});

// Add global response headers documentation
Object.values(document.paths).forEach((path) => {
  Object.values(path).forEach((operation: any) => {
    if (operation.responses) {
      Object.values(operation.responses).forEach((response: any) => {
        response.headers = {
          ...response.headers,
          'X-API-Version': {
            description: 'Current API version',
            schema: { type: 'string', example: '1.0.0' },
          },
          'X-API-Deprecated': {
            description: 'Whether this endpoint is deprecated',
            schema: { type: 'string', example: 'false' },
          },
        };
      });
    }
  });
});

Phase 5: Testing (30 minutes)

Priority: High | Impact: Medium | Dependencies: Phase 2

1. Create Unit Tests

Create apps/api/src/versioning/api-version.interceptor.spec.ts:

import { Test } from '@nestjs/testing';
import { ApiVersionInterceptor } from './api-version.interceptor';
import { ExecutionContext, CallHandler } from '@nestjs/common';
import { of } from 'rxjs';
import { API_VERSION } from './api-version.constants';

describe('ApiVersionInterceptor', () => {
  let interceptor: ApiVersionInterceptor;

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      providers: [ApiVersionInterceptor],
    }).compile();

    interceptor = module.get(ApiVersionInterceptor);
  });

  it('should add version headers to response', (done) => {
    const mockResponse = {
      headersSent: false,
      setHeader: jest.fn(),
    };

    const mockContext = {
      switchToHttp: () => ({
        getResponse: () => mockResponse,
      }),
    } as unknown as ExecutionContext;

    const mockHandler: CallHandler = {
      handle: () => of({}),
    };

    interceptor.intercept(mockContext, mockHandler).subscribe(() => {
      expect(mockResponse.setHeader).toHaveBeenCalledWith(
        'X-API-Version',
        API_VERSION.current,
      );
      expect(mockResponse.setHeader).toHaveBeenCalledWith(
        'X-API-Deprecated',
        'false',
      );
      done();
    });
  });
});

📁 Files to Create/Modify

New Files

apps/api/src/versioning/api-version.constants.ts
apps/api/src/versioning/api-version.interceptor.ts
apps/api/src/versioning/deprecated.decorator.ts
apps/api/src/versioning/deprecated.interceptor.ts
apps/api/src/versioning/api-version.interceptor.spec.ts
apps/api/src/versioning/index.ts

Modified Files

apps/api/src/app.module.ts
apps/api/src/main.ts (Swagger config)

✅ Validation Checklist

  • API version constants defined
  • ApiVersionInterceptor adds headers to all responses
  • Deprecated decorator available for endpoints
  • Swagger documentation updated with version
  • Tests verify header presence
  • pnpm nx build api passes
  • pnpm nx test api passes

Final Verification

# Build passes
pnpm nx build api

# Tests pass
pnpm nx test api

# Start API and check headers
pnpm nx serve api

# Verify headers in response
curl -i http://localhost:3000/api/v1/orders
# Should include:
# X-API-Version: 1.0.0
# X-API-Min-Version: 1.0.0
# X-API-Deprecated: false

📝 Usage Example

// For deprecated endpoints:
@Controller('api/v1/legacy')
export class LegacyController {
  @Get('orders')
  @Deprecated({
    sunsetDate: new Date('2026-12-31'),
    alternative: '/api/v2/orders',
    message: 'Migrate to v2 API for improved filtering'
  })
  getLegacyOrders() {
    // Will add deprecation headers automatically
  }
}

END OF PROMPT


This prompt resolves TD-014 from the technical debt register by implementing API versioning headers.