Logo

dev-resources.site

for different kinds of informations.

How to return meaningful error messages with Zod, Lambda and API Gateway in AWS CDK

Published at
12/21/2024
Categories
serverless
aws
lambda
apigateway
Author
katherine_m
Categories
4 categories in total
serverless
open
aws
open
lambda
open
apigateway
open
Author
11 person written this
katherine_m
open
How to return meaningful error messages with Zod, Lambda and API Gateway in AWS CDK

In this post you’ll learn how to generate Schemas with Zod and use them to validate incoming requests at the level of API Gateway and Lambda.

Source Code of this project

What is Zod?

Zod is a TypeScript-first schema declaration and validation library. It allows you to define schemas for your data structures, validate that data at runtime, and provides strong type inference for better type safety.

Let’s start with the schema declaration, this is an example of a Zod schema of a User:

import { z } from 'zod';

export const userSchema = z.object({
  name: z.string(), // Name must be a string
  age: z.number().int().min(18), // User's age must be an integer greater than 18
  email: z.string().email(), // Must be a valid email
});

export type UserEventType = z.infer<typeof userSchema>;
Enter fullscreen mode Exit fullscreen mode

Types of validation

Usually you will have the following architecture to handle Rest API Requests:

Image description

For this architecture we can apply two types of validations

Validation at the Level of API Gateway

API Gateway-level validation is powerful because it rejects invalid requests before they reach your downstream services. This prevents unnecessary consumption of Lambda invocations or other resources and saves you money(resources not used = resources not paid).

But the biggest Con is that you can not customize the error message returned by API Gateway’s built-in validation and the schema only supports JSON Schema Draft 4.

Image description

In order to validate the incoming request body at the level of the API Gateway, we need to define our schema using JSON Schema draft 4.
In order to transform our previously User Zod Schema to a valid JSON Schema we are going to use the library zod-to-json-schema.

import { userSchema } from './src/schema/user';
import { zodToJsonSchema } from 'zod-to-json-schema';

const myRequestJsonSchema = zodToJsonSchema(userSchema, {
   target: 'openApi3',
});
Enter fullscreen mode Exit fullscreen mode

which will have as output:

Image description
We use the generated JSON Schema to create the Model to be used as Validator.
On AWS CDK this will look like:

export class CDKRestAPI extends Stack {
  constructor(scope: App, id: string, props: StackProps) {
    super(scope, id, props);

    /* ----------------- Lambda ----------------- */

    const lambdaExample = new NodejsFunction(this, 'LambdaExample', {
      functionName: 'lambda-example',
      runtime: Runtime.NODEJS_20_X,
      entry: path.join(__dirname, './src/lambdaExample.ts'),
      architecture: Architecture.ARM_64,
    });

    // We transform the Zod schema to a valid schema
    const myRequestJsonSchema = zodToJsonSchema(userSchema, {
      target: 'openApi3',
    });

    /* ----------------- API Gateway ----------------- */

    const myAPI = new RestApi(this, 'MyAPI', {
      restApiName: 'myAPI',
    });

    const lambdaIntegration = new LambdaIntegration(lambdaExample);

    myAPI.root.addMethod('POST', lambdaIntegration, {
      // We configure it to validate the request using the Model
      requestValidatorOptions: {
        requestValidatorName: 'rest-api-validator',
        validateRequestBody: true,
      },
      requestModels: {
        'application/json':
          new Model(this, 'my-request-model', {
            restApi: myAPI,
            contentType: 'application/json',
            description: 'Validation model for the request body',
            modelName: 'myRequestJsonSchema',
            schema: myRequestJsonSchema,
          }),
      },
      methodResponses: [
        {
          statusCode: '200',
          responseModels: {
            'application/json': Model.EMPTY_MODEL,
          },
        },
        {
          statusCode: '400',
          responseModels: {
            'application/json': Model.ERROR_MODEL,
          },
        },
        {
          statusCode: '500',
          responseModels: {
            'application/json': Model.ERROR_MODEL,
          },
        },
      ],
    });

    myAPI.addGatewayResponse('ValidationError', {
      type: apigateway.ResponseType.BAD_REQUEST_BODY,
      statusCode: '400',
      templates: {
         // We format the response to include the errors returned from the validation
        'application/json': JSON.stringify({
          errors: '$context.error.validationErrorString',
          details: '$context.error.message',
        }),
      },
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Beyond validation of the body request, you can also validate the presence of headers, query string parameters and path parameters.

Validation at the level of Lambda

Lambda validation is a great tool to validate the schema if you'd like to apply more advanced validation not supported by JSON Draft 4 ( like the conditionals if, then, else supported from JSON Schema Draft 7) or customized the error messages returned to the client.

Using Zod inside the Lambda, you can parse and validate the request body:

import type { Handler } from 'aws-lambda';
import { userSchema } from './schema/user';

export const handler: Handler = async (event) => {
  const body = JSON.parse(event.body);
  const result = userSchema.safeParse(body);
  if (!result.success) {
    const errors = result.error.errors.map((err) => ({
      path: err.path.join('.'),
      message: err.message,
    }));

    return {
      statusCode: 400,
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        message: 'Validation failed',
        errors: errors,
      }),
    };
  }
  return {
    statusCode: 200,
    body: JSON.stringify({
      message: 'hellou, hellou',
    }),
    headers: {
      'Access-Control-Allow-Headers': 'Content-Type',
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': '*',
    },
  };
};
Enter fullscreen mode Exit fullscreen mode

Which will return a descriptive error message, when we invoke to our API.

Image description

Summary

Having a combination of validation at the Level of API Gateway and a validation at the level Lambda you can protect your APIs while at the same time returning meaningful messages.

lambda Article's
30 articles in total
Favicon
Getting Started with AWS Lambda: A Guide to Serverless Computing for Beginners
Favicon
Interfaces funcionais predefinidas
Favicon
Pergunte ao especialista - expressÔes lambda nas biblioteca de APIs
Favicon
ReferĂȘncias de construtor
Favicon
ReferĂȘncias de mĂ©todo
Favicon
Pergunte ao especialista - referĂȘncia a um mĂ©todo genĂ©rico
Favicon
AWS Serverless: How to Create and Use a Lambda Layer via the AWS SAM - Part 2
Favicon
Setting Up AWS SNS, Lambda, and EventBridge via CLI: A Beginner's Guide
Favicon
As expressÔes lambda em ação
Favicon
Fundamentos das expressÔes lambda
Favicon
Pergunte ao especialista - especificando os tipos de dados em lambdas
Favicon
Introdução às expressÔes lambda
Favicon
AWS Serverless: How to Create and Use a Lambda Layer via the AWS SAM - Part 1
Favicon
Optimizing AWS Costs: Practical Tips for Budget-Conscious Cloud Engineers
Favicon
Build a highly scalable Serverless CRUD Microservice with AWS Lambda and the Serverless Framework
Favicon
Serverless or Server for Django Apps?
Favicon
Optimizing Serverless Lambda with GraalVM Native Image
Favicon
Solving the Empty Path Issue in Go Lambda Functions with API Gateway HTTP API
Favicon
AWS workshop #2: Leveraging Amazon Bedrock to enhance customer service with AI-powered Automated Email Response
Favicon
How to return meaningful error messages with Zod, Lambda and API Gateway in AWS CDK
Favicon
Managing EKS Clusters Using AWS Lambda: A Step-by-Step Approach
Favicon
Schedule Events in EventBridge with Lambda
Favicon
Ingesting Data in F# with Aether: A Practical Guide to Using Lenses, Prisms, and Morphisms
Favicon
How to Create a Lambda Function to Export IAM Users to S3 as a CSV File
Favicon
New explorations at Serverless day
Favicon
Mastering AWS Lambda Performance: Advanced Optimization Strategies for 2025
Favicon
Lambda vs. Named Functions: Choosing the Right Tool for the Job
Favicon
How did I contribute for OpenAI’s Xmas Bonus before cutting 50% costs while scaling 10x with GenAI processing
Favicon
My (non-AI) AWS re:Invent 24 picks
Favicon
Alarme Dynamo Throttle Events - Discord

Featured ones: