Imported from RobinTail/express-zod-api (
AGENTS.md). Install upstream withnpx skills add RobinTail/express-zod-api. Copyright stays with the author.
Project Guide for AI Agents
This file provides essential context for working with the express-zod-api project.
Project Overview
The express-zod-api is a TypeScript framework for building Express.js APIs with Zod schema validation. It provides type-safe request/response handling, automatic OpenAPI documentation, and a clean API builder pattern.
- Package manager: pnpm
- Current version and Node version: See
express-zod-api/package.json
Project Structure
This is a pnpm monorepo. The directory name becomes the workspace name.
├── express-zod-api/ # Main package
│ ├── src/ # TypeScript source
│ └── tests/ # Unit tests (Vitest)
├── migration/ # Workspace: @express-zod-api/migration
├── example/ # Example API server
├── zod-plugin/ # Workspace: @express-zod-api/zod-plugin
├── cjs-test/ # CommonJS compatibility tests
├── esm-test/ # ESM compatibility tests
├── compat-test/ # Compatibility tests
└── issue952-test/ # Issue reproduction tests
Workspaces
| Directory | Package Name | Description |
|---|---|---|
express-zod-api/ |
express-zod-api |
Framework itself (main) |
migration/ |
@express-zod-api/migration |
ESLint migration rules |
zod-plugin/ |
@express-zod-api/zod-plugin |
Zod plugin for schema enhancements |
example/ |
— | Usage example |
The rest are test workspaces.
Architecture Overview
Quick reference on how the key modules connect:
flowchart LR
A[config-type.ts] -->|creates| B[createConfig]
C[endpoints-factory.ts] -->|builds| D[Endpoint]
D --> E[endpoint.ts]
E --> F[routing.ts]
F --> G[server.ts]
See src/index.ts for the complete public API exports.
Important Entry Points
express-zod-api/src/index.ts— Public API exportsexpress-zod-api/src/config-type.ts— Configuration types forcreateConfig()express-zod-api/src/routing.ts— Core routing logicexpress-zod-api/src/testing.ts— Test utilities:testEndpointandtestMiddlewareare public, others internalexpress-zod-api/tests/express-mock.ts— Express mocking for testsexpress-zod-api/tests/peers-mock.ts— Mocks for compression, cookie-parser, express-fileupload, express-rate-limitmigration/index.ts— Migration ESLint rulesCHANGELOG.md— Version history and breaking changes
Key Conventions
1. File Naming Conventions
- Source files:
src/*.ts - Test files:
tests/*.spec.ts(Vitest) - Test organization: In
express-zod-apiworkspace,tests/mirrorssrc/. Each source file has a corresponding test file with the same name.
2. Public API Convention
Everything exported via express-zod-api/src/index.ts is part of the public API.
3. JSDoc Convention for Public Entities
All properties of publicly available entities (exposed via index.ts) must have JSDoc documentation using directives:
@desc: Short description of what the property does, ends with a period@default: Default value (required for optional properties)@example: Example value (required for literal types, one per variant)
Each directive should aim to fit on one line. Several directives should be placed in a single multi-line comment:
interface SampleInterface {
/** @desc Enables certain feature. */
sampleRequiredProperty: boolean | SampleOptions;
/**
* @desc Controls another feature.
* @default true
* @example true — leads to one thing
* @example false — leads to another thing
* */
sampleOptionalProperty?: boolean;
}
4. Import Conventions
- Zod: Use named import
import { z } from "zod" - Ramda: Use namespace import
import * as R from "ramda" - Node.js built-ins: Use
node:prefix - Type-only imports: Use
import typefor types and interfaces (verbatimModuleSyntax) - Relative imports: Use
.tsextension when file is meant to be run bynode(exampleand testing workspaces) - Combine imports from the same module into a single statement
import { z } from "zod";
import * as R from "ramda";
import { dirname } from "node:path";
import type { SomeType } from "./module-a"; // for compiled code
import { someValue } from "./module-b.ts"; // for node execution
5. Type Declaration Convention
Object-based types should be declared as interfaces, not types:
// Good
interface User {
name: string;
age: number;
}
// Avoid
type User = { name: string; age: number };
6. Testing Convention
- Use
test.each(): Always prefer parameterized tests to reduce repetition - Placeholders: Use
%sfor the current value or%#for the index
test.each([true, false, undefined])(
"Should handle hintAllowedMethods=%s",
(hintAllowedMethods) => {},
);
- Mock patterns: Use utilities from
src/testing.ts(testEndpoint,testMiddleware, etc.) and Express mocks fromtests/express-mock.ts:
const { loggerMock, requestMock, responseMock } = await testEndpoint({
endpoint,
requestProps: { method: "GET" },
responseOptions: { locals: {} },
});
- Test runner: Vitest
- Location:
tests/directories within each package
7. Configuration (config-type.ts)
Public API config uses CommonConfig interface with JSDoc:
interface CommonConfig {
/** @desc Description of what this config does */
propertyName?: type; // default: value
}
8. API Building Pattern
const factory = new EndpointsFactory(defaultResultHandler);
const endpoint = factory.build({
method: "get",
path: "/users",
input: z.object({ ... }),
output: z.object({ ... }),
handler: async ({ input }) => ({ users: [] }),
});
9. Building
- Build tool:
tsdown - Commands:
pnpm build(root builds all workspaces)
10. Line Length
- Maximum line length: 120 characters (including Markdown files)
- Use line breaks and code folding to stay within limit
11. Code Refactoring
When moving statements, preserve any inline comments from the source location:
// Original:
if (entry.description) flat.description ??= entry.description; // can be empty
// After extracting (comment preserved in place):
const copyDescription = (entry, flat) => {
if (entry.description) flat.description ??= entry.description; // can be empty
};
12. Markdown files policy
Markdown files should avoid --- horizontal rule separators.
13. TypeScript Generators Policy
For generating TypeScript code, prefer using helpers from typescript-api.ts over direct factory methods. Native
factory methods have verbose APIs with many redundant arguments.
Files that generate TypeScript code:
express-zod-api/src/typescript-api.ts(defines the helpers)express-zod-api/src/integration.tsexpress-zod-api/src/integration-base.tsexpress-zod-api/src/zts.ts
Examples: Use these helpers instead of verbose factory calls:
// Use helpers:
makeId("User");
literally(0);
makeInterfaceProp("name", typeNode);
// Avoid native factory calls:
ts.factory.createIdentifier("User");
ts.factory.createNumericLiteral(0);
ts.factory.createPropertySignature();
14. Performance Priority
For code involved in API execution (routing, middlewares, schema definition, validation), performance is the highest
priority. This does not apply to generators (Integration, Documentation).
When editing that code or choosing between multiple implementations in performance-critical scope, always A/B test it:
- Create a benchmark in
express-zod-api/bench(similar toexperiment.bench.ts) - Compare current vs featured solution (for refactoring) or two variants (for new code)
- Run benchmark via
pnpm bench
describe("Experiment for feature", () => {
bench("current", () => {}); // implementation A
bench("featured", () => {}); // implementation B
});
Breaking Change Policy
Any breaking change to the public API requires an update to the migration script, its tests, and CHANGELOG.
Migration Organization
The migration is implemented as an ESLint rule in migration/index.ts:
Queriesinterface — defines AST node types to search for using esquery selectorsqueriesobject — maps query names to esquery selectorslisten()function — connects queries to their handler functions increate()create()function — implements the actual transformation
Tests are in migration/index.spec.ts using RuleTester:
valid— code that should NOT trigger the ruleinvalid— code that SHOULD be transformed, with expectedoutputand error assertions
CHANGELOG Convention
The CHANGELOG.md follows these rules:
- Structure: Nested in reverse order (the most recent version at top)
- Level 2: Major version title, e.g.,
## Version N - Level 3: Specific version, e.g.,
### vN.N.N(semantic) - Changes: Single list, may have nested items for clarifications
- Punctuation:
- Item with nested items ends with
: - Last item in a list ends with
. - Other items end with
;
- Item with nested items ends with
- Code samples: Prefer a single sample below the list
- Major releases (vN.0.0): Use
diffcode block with+/-prefixes and spacing offset
Common Tasks
Building
pnpm build # Build all workspaces
Running Tests
Note: Build everything before testing everything, because example workspace depends on express-zod-api.
pnpm build # Build all workspaces first
pnpm test # Run all tests
pnpm -F express-zod-api test # Run main package tests only
pnpm -F migration test # Run migration tests
pnpm -F express-zod-api test -- --run routing # Run specific test file
Linting
pnpm lint # Check lint and formatting
pnpm mdfix # Fix formatting of markdown files