or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/npm-aws-lambda-powertools--validation

An utility to validate events and responses using JSON Schemas

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/@aws-lambda-powertools/validation@2.29.x

To install, run

npx @tessl/cli install tessl/npm-aws-lambda-powertools--validation@2.29.0

index.mddocs/

AWS Lambda Powertools Validation

JSON Schema validation for AWS Lambda events and responses using Ajv. Supports JMESPath data extraction, custom formats, external schema references, and three integration patterns: standalone function, Middy.js middleware, and TypeScript decorator.

Package: @aws-lambda-powertools/validation (npm) Installation: npm install @aws-lambda-powertools/validation

Imports

// Standalone validation
import { validate } from '@aws-lambda-powertools/validation';

// Middy.js middleware
import { validator } from '@aws-lambda-powertools/validation/middleware';

// TypeScript decorator
import { validator } from '@aws-lambda-powertools/validation/decorator';

// Error classes
import {
  ValidationError,
  SchemaValidationError,
  SchemaCompilationError
} from '@aws-lambda-powertools/validation/errors';

// Types (from Ajv)
import type { Ajv, AnySchema, Format } from 'ajv';

Core API

validate() - Standalone Function

Validates payload against JSON schema, returns typed data.

function validate<T = unknown>(params: ValidateParams): T;

type ValidateParams = {
  payload: unknown;              // Data to validate
  schema: AnySchema;             // JSON schema (Draft-07)
  envelope?: string;             // JMESPath to extract nested data
  formats?: Record<string, Format>; // Custom format validators
  externalRefs?: AnySchema | AnySchema[]; // External schemas for $ref
  ajv?: Ajv;                     // Pre-configured Ajv instance
};

Throws: SchemaValidationError (validation fails), SchemaCompilationError (invalid schema)

Example:

import { validate } from '@aws-lambda-powertools/validation';
import { SchemaValidationError } from '@aws-lambda-powertools/validation/errors';

const schema = {
  type: 'object',
  properties: {
    name: { type: 'string' },
    age: { type: 'number', minimum: 0 },
  },
  required: ['name', 'age'],
} as const;

export const handler = async (event: unknown) => {
  try {
    const data = validate({ payload: event, schema });
    return { statusCode: 200, body: JSON.stringify(data) };
  } catch (error) {
    if (error instanceof SchemaValidationError) {
      return {
        statusCode: 400,
        body: JSON.stringify({ error: 'Invalid input', details: error.cause }),
      };
    }
    throw error;
  }
};

validator() - Middy.js Middleware

Validates inbound events (before handler) and/or outbound responses (after handler).

function validator(options: ValidatorOptions): {
  before: MiddlewareFn;
  after: (handler: MiddyLikeRequest) => void;
};

interface ValidatorOptions {
  inboundSchema?: AnySchema;     // Validate incoming event
  outboundSchema?: AnySchema;    // Validate response
  envelope?: string;             // JMESPath for data extraction
  formats?: Record<string, Format>;
  externalRefs?: AnySchema | AnySchema[];
  ajv?: Ajv;
}

Throws: SchemaValidationError with message "Inbound schema validation failed" or "Outbound schema validation failed"

Example:

import { validator } from '@aws-lambda-powertools/validation/middleware';
import middy from '@middy/core';

const inboundSchema = {
  type: 'object',
  properties: {
    userId: { type: 'string' },
    action: { type: 'string' },
  },
  required: ['userId', 'action'],
};

const outboundSchema = {
  type: 'object',
  properties: {
    statusCode: { type: 'number' },
    body: { type: 'string' },
  },
  required: ['statusCode', 'body'],
};

export const handler = middy()
  .use(validator({ inboundSchema, outboundSchema }))
  .handler(async (event) => {
    // event validated, response will be validated
    return {
      statusCode: 200,
      body: JSON.stringify({ success: true }),
    };
  });

@validator() - TypeScript Decorator

Validates class method input (first argument) and/or output (return value).

function validator(options: ValidatorOptions): PropertyDescriptor;

Throws: SchemaValidationError with message "Inbound schema validation failed" or "Outbound schema validation failed"

Example:

import { validator } from '@aws-lambda-powertools/validation/decorator';
import type { Context } from 'aws-lambda';

const inboundSchema = {
  type: 'object',
  properties: {
    value: { type: 'number' },
  },
  required: ['value'],
};

const outboundSchema = {
  type: 'object',
  properties: {
    result: { type: 'number' },
  },
  required: ['result'],
};

class Lambda {
  @validator({ inboundSchema, outboundSchema })
  async handler(event: { value: number }, _context: Context) {
    return { result: event.value * 2 };
  }
}

const lambda = new Lambda();
export const handler = lambda.handler.bind(lambda);

Common Patterns

JMESPath Envelope Extraction

Extract nested data before validation:

// EventBridge detail
const detailSchema = { /* schema for detail object */ };

export const handler = middy()
  .use(validator({
    inboundSchema: detailSchema,
    envelope: 'detail', // Extracts event.detail
  }))
  .handler(async (event) => { /* ... */ });

// API Gateway body
envelope: 'body'

// SQS/SNS message
envelope: 'Records[0].body'
envelope: 'Records[0].Sns.Message'

Custom Format Validators

const customFormats = {
  uuid: (value: string) =>
    /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value),
  positiveInt: (value: number) => Number.isInteger(value) && value > 0,
};

const schema = {
  type: 'object',
  properties: {
    id: { type: 'string', format: 'uuid' },
    quantity: { type: 'number', format: 'positiveInt' },
  },
};

validate({ payload, schema, formats: customFormats });

External Schema References

const addressSchema = {
  $id: 'https://example.com/address.json',
  type: 'object',
  properties: {
    street: { type: 'string' },
    city: { type: 'string' },
  },
  required: ['street', 'city'],
};

const userSchema = {
  type: 'object',
  properties: {
    name: { type: 'string' },
    address: { $ref: 'address.json' },
  },
  required: ['name', 'address'],
};

validate({
  payload,
  schema: userSchema,
  externalRefs: [addressSchema],
});

Custom Ajv Instance (Performance)

Reuse Ajv instance across invocations for better performance:

import Ajv from 'ajv';
import addFormats from 'ajv-formats';

// Outside handler (Lambda warm starts)
const ajvInstance = new Ajv({
  allErrors: true,
  coerceTypes: true,
  useDefaults: true,
});
addFormats(ajvInstance);

export const handler = async (event: unknown) => {
  const data = validate({
    payload: event,
    schema: mySchema,
    ajv: ajvInstance, // Reuse compiled schemas
  });
  return { statusCode: 200, body: JSON.stringify(data) };
};

API Gateway REST API

const requestSchema = {
  type: 'object',
  properties: {
    body: { type: 'string' },
    pathParameters: {
      type: 'object',
      properties: { id: { type: 'string' } },
      required: ['id'],
    },
  },
  required: ['body', 'pathParameters'],
};

const responseSchema = {
  type: 'object',
  properties: {
    statusCode: { type: 'number' },
    body: { type: 'string' },
  },
  required: ['statusCode', 'body'],
};

export const handler = middy()
  .use(validator({ inboundSchema: requestSchema, outboundSchema: responseSchema }))
  .handler(async (event) => {
    const body = JSON.parse(event.body);
    const { id } = event.pathParameters;
    return {
      statusCode: 200,
      body: JSON.stringify({ id, data: body }),
    };
  });

SQS Event Processing

const recordSchema = {
  type: 'object',
  properties: {
    orderId: { type: 'string' },
    items: {
      type: 'array',
      items: {
        type: 'object',
        properties: {
          productId: { type: 'string' },
          quantity: { type: 'number' },
        },
        required: ['productId', 'quantity'],
      },
    },
  },
  required: ['orderId', 'items'],
};

export const handler = async (event: SQSEvent) => {
  for (const record of event.Records) {
    const body = JSON.parse(record.body);
    validate({ payload: body, schema: recordSchema });
    // Process validated record
  }
  return { statusCode: 200 };
};

Combining with Other Middy Middleware

import httpJsonBodyParser from '@middy/http-json-body-parser';
import httpErrorHandler from '@middy/http-error-handler';

export const handler = middy()
  .use(httpJsonBodyParser())      // Parse JSON first
  .use(validator({ inboundSchema })) // Then validate
  .use(httpErrorHandler())         // Handle errors
  .handler(async (event) => { /* ... */ });

Error Handling

Error Class Hierarchy

class ValidationError extends Error {
  name: string;
  message: string;
  cause?: unknown;
}

class SchemaValidationError extends ValidationError {
  name: 'SchemaValidationError';
  cause?: unknown; // Contains Ajv ErrorObject[]
}

class SchemaCompilationError extends ValidationError {
  name: 'SchemaCompilationError';
  message: 'Failed to compile schema';
}

Error Messages

  • SchemaValidationError:

    • "Schema validation failed" - standalone validate()
    • "Inbound schema validation failed" - middleware/decorator input
    • "Outbound schema validation failed" - middleware/decorator output
  • SchemaCompilationError:

    • "Failed to compile schema" - invalid schema

Handling Specific Errors

import { SchemaValidationError, SchemaCompilationError } from '@aws-lambda-powertools/validation/errors';

try {
  const data = validate({ payload: event, schema: mySchema });
  return { statusCode: 200, body: JSON.stringify(data) };
} catch (error) {
  if (error instanceof SchemaCompilationError) {
    // Developer error - invalid schema
    console.error('Schema compilation failed:', error.cause);
    return { statusCode: 500, body: 'Internal error' };
  }

  if (error instanceof SchemaValidationError) {
    // Client error - invalid input
    const ajvErrors = error.cause; // Array of Ajv ErrorObject
    return {
      statusCode: 400,
      body: JSON.stringify({ error: 'Validation failed', details: ajvErrors }),
    };
  }

  throw error;
}

Ajv ErrorObject Structure

interface ErrorObject {
  keyword: string;        // e.g., "required", "type", "minimum"
  instancePath: string;   // JSON Pointer to failing property
  schemaPath: string;     // JSON Pointer to schema rule
  params: Record<string, any>; // Keyword-specific params
  message?: string;       // Human-readable message
  data?: unknown;         // The invalid data
}

Example Errors:

// Missing required property
{ keyword: 'required', params: { missingProperty: 'name' }, message: "must have required property 'name'" }

// Type mismatch
{ keyword: 'type', instancePath: '/age', params: { type: 'number' }, message: 'must be number', data: 'invalid' }

// Range violation
{ keyword: 'minimum', instancePath: '/age', params: { comparison: '>=', limit: 0 }, message: 'must be >= 0', data: -5 }

Middleware Error Handler

import middy from '@middy/core';
import type { MiddyLikeRequest } from '@middy/core';

const errorHandler = (): middy.MiddlewareObj => ({
  onError: async (request: MiddyLikeRequest) => {
    const { error } = request;

    if (error instanceof SchemaValidationError) {
      return {
        statusCode: 400,
        body: JSON.stringify({
          message: error.message,
          errors: error.cause,
        }),
      };
    }

    throw error; // Propagate other errors
  },
});

export const handler = middy()
  .use(validator({ inboundSchema }))
  .use(errorHandler())
  .handler(async (event) => { /* ... */ });

Types Reference

// Re-exported from Ajv (import from 'ajv' package)
import type { Ajv, AnySchema, Format } from 'ajv';

type ValidateParams = {
  payload: unknown;
  schema: AnySchema;
  envelope?: string;
  formats?: Record<string, Format>;
  externalRefs?: AnySchema | AnySchema[];
  ajv?: Ajv;
};

interface ValidatorOptions extends Omit<ValidateParams, 'payload' | 'schema'> {
  inboundSchema?: AnySchema;
  outboundSchema?: AnySchema;
}

// Ajv Format type
type Format =
  | ((data: unknown) => boolean)
  | {
      type?: 'string' | 'number';
      validate: (data: unknown) => boolean;
      async?: boolean;
      compare?: (data1: unknown, data2: unknown) => number;
    };

Key Behaviors

  • Built on Ajv v8: JSON Schema Draft-07 validation
  • JMESPath Integration: Uses @aws-lambda-powertools/jmespath for data extraction
  • Type Safety: Generic return types for TypeScript inference
  • Event Cloning: Middleware clones events before validation to prevent mutation
  • Schema Caching: Ajv compiles and caches schemas; reuse Ajv instances for best performance
  • Decorator Requirements: Must be async methods, always bind to class instance when exporting
  • Multiple Validators: Both inbound/outbound schemas are optional; provide at least one