appsync
AppSync Lambda Functions
When business logic requires external databases, third-party APIs, or complex schema validations (e.g. Zod), you can validate and process AppSync GraphQL mutations inside AWS Lambda functions using Middy.
The @sot1986/appsync-precognition/middy subpath provides four core exports designed to streamline validation, error handling, and precognitive early exits in Lambda-backed AppSync resolvers.
Core Exports
The module exposes four primary exports under the middy namespace:
| Export | Type | Description |
|---|---|---|
resolveLambdaResponseTemplate | Helper Function | Generates the AppSync VTL response mapping template to bridge Lambda error payloads and GraphQL resolver error handling. |
AppsyncError | Error Class | Base error class implementing the payload structure expected by resolveLambdaResponseTemplate. |
appsyncErrorHandler | Middy Middleware | Intercepts AppsyncError (and subclasses) during errors and formats them into parsable responses without crashing Lambda. |
precognition | Middy Middleware | Handles precognitive early return ({ data: null }) and transforms custom validation errors (e.g. ZodError) into structured error payloads. |
1. resolveLambdaResponseTemplate
resolveLambdaResponseTemplate generates an AppSync VTL response mapping template string. It inspects the Lambda invocation response and maps structured errors to native AppSync GraphQL errors ($util.appendError(...)) so client-side forms can receive field-specific validation messages.
Usage Example (Amplify Gen 2 / CDK)
Apply resolveLambdaResponseTemplate() in amplify/backend.ts to configure the response mapping template on your AppSync Lambda function resolvers:
import { defineBackend } from '@aws-amplify/backend'
import { resolveLambdaResponseTemplate } from '@sot1986/appsync-precognition/middy'
import { CfnFunctionConfiguration } from 'aws-cdk-lib/aws-appsync'
import { auth } from './auth/resource'
import { data } from './data/resource'
import { createUserHandler } from './functions/create-user/resource'
const backend = defineBackend({
auth,
data,
createUserHandler,
})
// Override response mapping templates for all VTL Lambda function resolvers
backend.data.resources.graphqlApi.stack.node.findAll().forEach((child) => {
if (child instanceof CfnFunctionConfiguration && !child.runtime) {
child.responseMappingTemplate = resolveLambdaResponseTemplate()
child.responseMappingTemplateS3Location = undefined
child.addPropertyDeletionOverride('ResponseMappingTemplateS3Location')
}
})
2. AppsyncError
AppsyncError is an Error subclass implementing the error interface that resolveLambdaResponseTemplate is designed to parse. You can throw AppsyncError directly or extend it for custom domain errors.
Properties
message(string): The human-readable error message.errorType(string): The error category (e.g.'ValidationError','NotFoundError','Unauthorized').data(any): Optional data payload to attach to the GraphQL error.errorInfo(Record<string, unknown> | null): Additional error metadata (such as{ path, value }for field errors).
Usage Example
import { AppsyncError } from '@sot1986/appsync-precognition/middy'
// Throwing a direct AppsyncError
if (userAlreadyExists) {
throw new AppsyncError(
'A user with this email address already exists.',
'ValidationError',
null,
{ path: 'email' },
)
}
// Or creating custom error classes
export class NotFoundError extends AppsyncError {
constructor(message = 'Resource not found', errorInfo?: Record<string, unknown>) {
super(message, 'NotFoundError', null, errorInfo)
}
}
3. appsyncErrorHandler
appsyncErrorHandler is a Middy middleware that hooks into the onError phase. It intercepts any AppsyncError or its subclasses thrown during handler or middleware execution, converting them into structured response objects formatted for resolveLambdaResponseTemplate.
This prevents Lambda execution crashes / unhandled rejections and ensures AppSync receives the exact payload needed to populate GraphQL error details.
Usage Example
import middy from '@middy/core'
import { appsyncErrorHandler } from '@sot1986/appsync-precognition/middy'
async function baseHandler(event: any) {
// Your business logic...
}
// Attach the error handler middleware to your Middy chain
export const handler = middy(baseHandler)
.use(appsyncErrorHandler())
4. precognition
precognition is a Middy middleware designed to:
- Short-circuit execution during precognitive validation requests when input is valid (returns
{ data: null }early without running the main handler). - Transform custom validation errors (such as Zod's
ZodErroror other schema validators) into structured validation errors thatappsyncErrorHandlerandresolveLambdaResponseTemplatecan process.
Options
validator(event): Function to validate the Lambda event. Should throw an error when validation fails.toValidationErrors(error): Adapter function converting caught validation errors into an array of{ path: string[], message: string, value?: unknown }. Returnnullif the error is not a validation error.
Usage Example (Zod Adapter Middleware)
import type middy from '@middy/core'
import { precognition } from '@sot1986/appsync-precognition/middy'
import * as z from 'zod'
export function parser<TSchema extends z.ZodType, TResult>(options: {
schema: TSchema
}): middy.MiddlewareObj<z.infer<TSchema>, TResult> {
return precognition({
validator: event => options.schema.parse(event),
toValidationErrors: (error) => {
if (error instanceof z.ZodError === false)
return null
return error.issues.map((issue) => {
// Strip top-level 'arguments' prefix to match form field names
const path = issue.path.at(0) === 'arguments' ? issue.path.slice(1) : issue.path
return {
path: path.map(String),
message: issue.message,
value: issue.input,
}
})
},
})
}
Complete Lambda Function Example
Here is how all four exports collaborate in a complete AppSync Lambda function:
import middy from '@middy/core'
import { AppsyncError, appsyncErrorHandler } from '@sot1986/appsync-precognition/middy'
import * as z from 'zod'
import { parser } from '../_middlewares/validate'
// 1. Define the event validation schema
const CreateUserEventSchema = z.object({
arguments: z.object({
email: z.string().email('Please enter a valid email address'),
name: z.string().min(3, 'Name must be at least 3 characters'),
age: z.number().min(18, 'Must be at least 18 years old'),
}),
})
// 2. Base handler (executes only when validation passes on non-precognitive requests)
async function baseHandler(event: z.infer<typeof CreateUserEventSchema>) {
const { email, name, age } = event.arguments
// Example business validation throwing an AppsyncError
const existingUser = await findUserByEmail(email)
if (existingUser) {
throw new AppsyncError(
'Email is already taken.',
'ValidationError',
null,
{ path: 'email' },
)
}
return {
id: 'user_123',
email,
name,
age,
createdAt: new Date().toISOString(),
}
}
// 3. Compose with Middy
export const handler = middy(baseHandler)
.use(appsyncErrorHandler())
.use(parser({ schema: CreateUserEventSchema }))
Lifecycle Flow
The Nuxt client dispatches a GraphQL mutation with Precognition HTTP headers.
AppSync invokes the Lambda data source, forwarding mutation arguments and headers.
The precognition() middleware validates inputs with your schema. If valid on a precognitive request, it returns early and halts execution.
If validation fails or an AppsyncError is thrown, the middleware formats it into a structured error payload without crashing Lambda.
The response template parses the payload and maps errors into native AppSync GraphQL errors ($util.appendError()).
The Nuxt client receives the GraphQL errors and maps them directly back to the respective form fields.