Skip to main content

Overview

The @apisr/zod package extends Zod with additional utilities for API development. It adds the .from() method to Zod schemas, enabling you to specify where field values should be extracted from (query params, body, headers, etc.) in HTTP requests.

Installation

npm install @apisr/zod
This package includes zod as a dependency, so you don’t need to install it separately.

Exports

The package exports:
  • zod / z - Extended Zod instance with .from() method
  • _z - Original Zod instance (without extensions)
  • extendZod() - Function to extend a Zod instance
  • resolveZodSchemaMeta() - Extract metadata from schemas
  • resolveZodSchemaFromSources() - Resolve field values from multiple sources
  • Types: FieldMeta, SchemaFieldsMeta, FromKey, FromOptions

Quick Start

Import the extended Zod instance:
import { z } from '@apisr/zod';

// Use like regular Zod, but with .from() support
const schema = z.object({
  name: z.string().from('body'),
  id: z.string().from('params'),
  search: z.string().from('query'),
});

The .from() Method

The .from() method annotates schema fields with metadata about their source location in an HTTP request:
import { z } from '@apisr/zod';

const userSchema = z.object({
  // From request body
  name: z.string().from('body'),
  email: z.string().email().from('body'),
  
  // From URL params
  id: z.string().from('params'),
  
  // From query string
  search: z.string().optional().from('query'),
  
  // From headers
  token: z.string().from('headers', { key: 'authorization' }),
});

Supported Source Types

from
FromKey
required
The source location for the field value:
  • "query" - URL query parameters
  • "params" - URL path parameters
  • "body" - Request body
  • "headers" - HTTP headers
options
FromOptions
Optional configuration:
  • key - Custom key name(s) to look up in the source

Extracting Metadata

Use resolveZodSchemaMeta() to extract source metadata from a schema:
import { z, resolveZodSchemaMeta } from '@apisr/zod';

const schema = z.object({
  name: z.string().from('body'),
  id: z.string().from('params'),
  token: z.string().from('headers', { key: 'authorization' }),
});

const meta = resolveZodSchemaMeta(schema);

console.log(meta);
// {
//   name:  { from: "body" },
//   id:    { from: "params" },
//   token: { from: "headers", key: "authorization" },
// }

Resolving Values from Sources

Use resolveZodSchemaFromSources() to automatically extract and map values from different request sources:
import { z, resolveZodSchemaFromSources } from '@apisr/zod';

const schema = z.object({
  name: z.string().from('body'),
  id: z.string().from('params'),
  search: z.string().from('query'),
  auth: z.string().from('headers', { key: 'authorization' }),
});

const resolved = resolveZodSchemaFromSources(schema, {
  body: { name: 'John Doe' },
  params: { id: '123' },
  query: { search: 'test' },
  headers: { authorization: 'Bearer token123' },
});

console.log(resolved);
// {
//   name: "John Doe",
//   id: "123",
//   search: "test",
//   auth: "Bearer token123"
// }

Custom Key Mapping

Map fields to different keys in the source:
import { z, resolveZodSchemaFromSources } from '@apisr/zod';

const schema = z.object({
  // Map 'token' field to 'authorization' header
  token: z.string().from('headers', { key: 'authorization' }),
  
  // Map 'userId' field to 'id' param
  userId: z.string().from('params', { key: 'id' }),
});

const resolved = resolveZodSchemaFromSources(schema, {
  headers: { authorization: 'Bearer xyz' },
  params: { id: '456' },
});

console.log(resolved);
// { token: "Bearer xyz", userId: "456" }

Multiple Key Fallbacks

Provide multiple keys to check in order:
const schema = z.object({
  // Try 'authorization' first, then 'x-auth-token'
  token: z.string().from('headers', {
    key: ['authorization', 'x-auth-token'],
  }),
});

const resolved = resolveZodSchemaFromSources(schema, {
  headers: { 'x-auth-token': 'token123' },
});

console.log(resolved);
// { token: "token123" }

Nested Key Paths

Access nested object properties using dot notation:
const schema = z.object({
  userName: z.string().from('body', { key: 'user.name' }),
  userEmail: z.string().from('body', { key: 'user.email' }),
});

const resolved = resolveZodSchemaFromSources(schema, {
  body: {
    user: {
      name: 'John',
      email: '[email protected]',
    },
  },
});

console.log(resolved);
// { userName: "John", userEmail: "[email protected]" }

Advanced Usage

Custom Source Types

You can use custom source identifiers beyond the standard request parts:
const schema = z.object({
  prevName: z.string().from('handler.payload', { key: 'name' }),
  contextId: z.string().from('context.user', { key: 'id' }),
});

const resolved = resolveZodSchemaFromSources(schema, {
  'handler.payload': { name: 'Previous Name' },
  'context.user': { id: 'user-123' },
});

Working with Optional Fields

The resolver handles optional fields gracefully:
const schema = z.object({
  required: z.string().from('body'),
  optional: z.string().optional().from('query'),
});

const resolved = resolveZodSchemaFromSources(schema, {
  body: { required: 'present' },
  query: {}, // optional field missing
});

console.log(resolved);
// { required: "present" }
// 'optional' is not included since it wasn't found

Extending Your Own Zod Instance

If you need to extend a specific Zod instance:
import { extendZod } from '@apisr/zod';
import { z as myZod } from 'zod';

const extended = extendZod(myZod);

// Now use with .from() support
const schema = extended.object({
  name: extended.string().from('body'),
});
Calling extendZod() multiple times on the same Zod instance is safe - it will detect if the instance is already extended and return it as-is.

Type Definitions

FieldMeta

Metadata for a single schema field:
interface FieldMeta {
  from: string;              // Source identifier
  key?: string | string[];   // Optional key mapping
}

SchemaFieldsMeta

Metadata for all fields in a schema:
type SchemaFieldsMeta = Record<string, FieldMeta>;

FromKey

Standard request source types:
type FromKey = "query" | "params" | "body" | "headers";

FromOptions

Options for the .from() method:
interface FromOptions {
  key: string | string[];
}

Real-World Example

Here’s a complete example of using @apisr/zod in an API endpoint:
import { z, resolveZodSchemaFromSources } from '@apisr/zod';

// Define the schema with source annotations
const updateUserSchema = z.object({
  userId: z.string().uuid().from('params', { key: 'id' }),
  name: z.string().min(1).from('body'),
  email: z.string().email().from('body'),
  age: z.number().int().positive().optional().from('body'),
  auth: z.string().from('headers', { key: 'authorization' }),
});

// In your request handler
function handleUpdateUser(req) {
  // Resolve all values from different sources
  const resolved = resolveZodSchemaFromSources(updateUserSchema, {
    params: req.params,
    body: req.body,
    headers: req.headers,
  });
  
  // Validate the resolved values
  const validated = updateUserSchema.parse(resolved);
  
  // Use validated data
  return updateUser(validated);
}

Benefits

Type Safety

Full TypeScript support with type inference

Declarative

Schema and source mapping in one place

Flexible

Support for custom sources and key mappings

DRY

Avoid repetitive value extraction code

@apisr/api-aggregator

Use schemas with endpoint definitions

@apisr/transform

Data transformation with tag system

Build docs developers (and LLMs) love