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:

ExportTypeDescription
resolveLambdaResponseTemplateHelper FunctionGenerates the AppSync VTL response mapping template to bridge Lambda error payloads and GraphQL resolver error handling.
AppsyncErrorError ClassBase error class implementing the payload structure expected by resolveLambdaResponseTemplate.
appsyncErrorHandlerMiddy MiddlewareIntercepts AppsyncError (and subclasses) during errors and formats them into parsable responses without crashing Lambda.
precognitionMiddy MiddlewareHandles 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:

  1. Short-circuit execution during precognitive validation requests when input is valid (returns { data: null } early without running the main handler).
  2. Transform custom validation errors (such as Zod's ZodError or other schema validators) into structured validation errors that appsyncErrorHandler and resolveLambdaResponseTemplate can 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 }. Return null if 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

1
Client FormPrecognition: true

The Nuxt client dispatches a GraphQL mutation with Precognition HTTP headers.

2
AppSync ResolverInvoke Lambda

AppSync invokes the Lambda data source, forwarding mutation arguments and headers.

3
Lambda • precognition()Validation

The precognition() middleware validates inputs with your schema. If valid on a precognitive request, it returns early and halts execution.

4
Lambda • appsyncErrorHandler()Error Bridge

If validation fails or an AppsyncError is thrown, the middleware formats it into a structured error payload without crashing Lambda.

5
AppSync ResolverresolveLambdaResponseTemplate()

The response template parses the payload and maps errors into native AppSync GraphQL errors ($util.appendError()).

6
Client FormUI Binding

The Nuxt client receives the GraphQL errors and maps them directly back to the respective form fields.