An utility to validate events and responses using JSON Schemas
npx @tessl/cli install tessl/npm-aws-lambda-powertools--validation@2.29.0JSON 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
// 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';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;
}
};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 }),
};
});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);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'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 });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],
});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) };
};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 }),
};
});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 };
};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) => { /* ... */ });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';
}SchemaValidationError:
"Schema validation failed" - standalone validate()"Inbound schema validation failed" - middleware/decorator input"Outbound schema validation failed" - middleware/decorator outputSchemaCompilationError:
"Failed to compile schema" - invalid schemaimport { 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;
}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 }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) => { /* ... */ });// 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;
};@aws-lambda-powertools/jmespath for data extraction