An utility to validate events and responses using JSON Schemas
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
// 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 extractionInstall with Tessl CLI
npx tessl i tessl/npm-aws-lambda-powertools--validation