One Client, Two Worlds: Building a Type-Safe API and ReAct Agent Interface with Zod, LangGraph, and AWS

Matt MartzMatt Martz
6 min read

In this post, I’ll show you how I built a single TypeScript framework for creating robust, schema-validated API clients that serve both humans and ReAct agents without duplication. Using Zod, AWS Serverless tech, and a sprinkle of LangGraph, this project creates a unified client that's consumable out of the box — for apps, Lambdas, and LLMs alike. No translation layers. No schema drift. Just seamless, scalable type safety.

If you’ve ever stitched together a backend client and an LLM agent and wondered why you’ve got two subtly divergent ways of calling your API, this one's for you.

The code for this post is here: https://github.com/martzmakes/cdk-zod-agent

Why AWS, CDK, and Serverless?

As an AWS Community Builder, I’m passionate about leveraging AWS’s powerful cloud-native services to build scalable, reliable, and cost-effective solutions. This project is built from the ground up using AWS CDK (Cloud Development Kit) to define infrastructure as code, and it’s fully serverless—every API endpoint is powered by AWS Lambda, with DynamoDB as the persistent data store.

AWS CDK: Infrastructure as Code, Supercharged

The AWS CDK lets you define your entire cloud architecture in TypeScript, making it easy to version, review, and evolve your infrastructure alongside your application code. In this project, the lib/constructs/ directory contains reusable CDK constructs for DynamoDB tables, Lambda functions, and internal API routing. The stack is defined in lib/cdk-zod-agent-stack.ts, and you can deploy the whole system with a single command.

If you’d like to learn more about using AWS CDK, I put together a crash course of it for freeCodeCamp: https://www.youtube.com/watch?v=T-H4nJQyMig

Why Zod?

Zod lets you define schemas for your data and validate at runtime, giving you the confidence of TypeScript types with the safety net of runtime checks. In this project, every API endpoint is defined with Zod schemas for both requests and responses. That means whether you’re adding a new hero or logging a daring rescue, you know your data is always in tip-top shape.

Here’s the core insight: define your endpoints and schemas once with Zod, and you get two clients for the price of one—your human backend code and your ReAct agent both use the same strongly-typed, runtime-validated API client.

No discrepancies. No parallel validation stacks. No translation layers. Just one clean, unified source of truth.

The API Client Framework: Your Hero Utility Belt

Inside the package/ folder, you’ll find the core of the framework:

  • Endpoint Definitions: Each endpoint is defined with defineZodEndpoint, specifying the path, HTTP method, and Zod schemas for request/response.

  • Client Generator: createApiClient takes these definitions and generates a fully-typed client. Each method validates input/output with Zod.

  • Path Parameter Magic: The framework auto-generates Zod schemas for path parameters.

The real magic happens in helpers/endpoints.ts:

  • TypeScript Path Parameter Extraction: Recursively parses a path like /heroes/{hero}/rescues/{rescueId}into { hero: string; rescueId: string }.

  • Zod Path Parameter Schemas: Extracts {param} segments from paths and builds runtime validation schemas.

  • Endpoint Definition: Validates both request and response payloads at runtime and compile time.

Example:

addHero: defineZodEndpoint({
  path: "/heroes",
  method: "POST",
  schemas: {
    request: AddHeroRequestSchema,
    response: AddHeroResponseSchema,
  },
  description: "Add a new hero to the system."
})

You get full request/response validation, path parameter type inference—everything you’d expect for an internal client. But—crucially—those very same schemas and definitions are directly leveraged to generate callable, Zod-validated “tools” for your ReAct agent.

How Zod Parsing Works with the API Client

When using createApiClient:

  • Validates the request body before API call.

  • Validates the response after API call.

  • Validates path parameters.

The same client instance can be:

  • Imported into Lambda handlers and backend services.

  • Wrapped into agent tools for LLM workflows.

One source of truth. One validation story.

Lambda Handlers: Fortress of Validation (and Serverless Power)

Lambda handlers use the same Zod schemas for validation. initApiHandler wraps your handler to:

  • Parse and validate the incoming event body.

  • Validate the outgoing response.

Example:

export const handler = initApiHandler({
  apiHandler,
  inputSchema: AddHeroRequestSchema,
  outputSchema: AddHeroResponseSchema,
});

While creating the apiHandler for the addHero endpoint, you get a pre-typed body:

Similarly… the listHeroRescues endpoint’s path parameters are also typed:

When the LLM chooses an action, it’s calling the same code-path as your backend logic. This makes logs, errors, and even side effects far more predictable when debugging.

Deep Dive: The ReAct Agent in Action

1. Auto-Generating Tools from the API Client

Every endpoint becomes a tool validated with Zod:

const generateTools = <T extends Record<string, any>>(client: T) => {
  return Object.keys(client)
    .map((key) => { /* generates validated tools */ })
    .filter((tool) => tool !== null);
};

These tools are directly generated from the same client you use in your backend.

2. Making Tools Available to the Agent

const tools = [...generateTools(heroClient(process.env.API_ID!))];

As soon as a new endpoint is defined, it's available to the agent—no custom adapter required.

3. Customizing the Agent's Prompt

const prompt = (state, config) => {
  const userName = config.configurable?.userName || "Human";
  return [{ role: "system", content: `You are a helpful assistant. Address the user as ${userName}.` }, ...state.messages];
};

4. Running the Agent

const result = await agent.invoke({
  messages: [new HumanMessage(`What can you do?`)],
}, {
  configurable: { userName: "Matt", userId: "9efe72ed-b182-46b1-bc96-f125b7042599" }
});

5. Why This Approach is Super

  • Safety: Zod validation prevents malformed requests.

  • Flexibility: New endpoints become instantly usable by both humans and agents.

  • Observability: Unified logs and error handling across both human and LLM calls.

How the ReAct Agent Knows Exactly How to Format API Calls

Every tool generated from the API client is backed by a Zod schema that defines the expected structure for inputs (path parameters and body) and outputs.

When a ReAct agent is reasoning about which tool to call, it uses the tool's schema to automatically format requests correctly — including nesting, field names, required parameters, and types — without needing any extra code or custom adapters.

Because the tool's input schema is a real Zod object, the agent (through LangChain) can:

  • Auto-suggest the correct fields during planning.

  • Validate its proposed actions before sending them.

  • Receive clear, structured error messages if something goes wrong.

In short: the Zod schema acts like an instruction manual and a safety net at the same time — making sure the agent speaks the API’s language natively.

Example from generateTools:

const combinedSchema = z.object({
  pathParameters: pathParametersSchema || z.object({}),
  body: requestSchema || z.null(),
});

This means every tool knows exactly what shape of data to expect, and the agent never has to guess.

Practical Takeaways

  • Share definition and validation logic between humans and agents as a first principle.

  • If it isn’t easy for a human to call, it’s probably a minefield for the LLM.

  • Avoid translation adapters and parallel validation stacks wherever possible.

  • Build one honest client and let both humans and AI use it safely.


Up, Up, and Away!

By combining AWS, CDK, Serverless, Zod, TypeScript, and LangGraph, you get a framework that’s type-safe, runtime-safe, and seamlessly integrated with ReAct agents. Whether you’re building for superheroes or just want to avoid villainous bugs, this approach keeps your API and clients in perfect harmony—for both humans and machines.

Check out the code, try it out, and let me know how you’re using AWS and Zod to save your own day!

1
Subscribe to my newsletter

Read articles from Matt Martz directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Matt Martz
Matt Martz

I'm an AWS Community Builder and a Principal Software Architect I love all things CDK, Event Driven Architecture and Serverless