{"id":49656615,"url":"https://github.com/jderr-work/express-standard-schema-validation","last_synced_at":"2026-05-06T10:01:55.187Z","repository":{"id":329440313,"uuid":"1117754050","full_name":"jderr-work/express-standard-schema-validation","owner":"jderr-work","description":"validate express application inputs and parameters using any schema validation library that implements the standard schema interface","archived":false,"fork":false,"pushed_at":"2026-01-02T19:20:30.000Z","size":324,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-08T16:22:34.859Z","etag":null,"topics":["arktype","express","express-middleware","joi","standard-schema","valibot","zod"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jderr-work.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-16T19:05:37.000Z","updated_at":"2026-01-08T15:19:20.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jderr-work/express-standard-schema-validation","commit_stats":null,"previous_names":["jderr-work/express-standard-schema-validation"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/jderr-work/express-standard-schema-validation","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jderr-work%2Fexpress-standard-schema-validation","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jderr-work%2Fexpress-standard-schema-validation/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jderr-work%2Fexpress-standard-schema-validation/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jderr-work%2Fexpress-standard-schema-validation/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jderr-work","download_url":"https://codeload.github.com/jderr-work/express-standard-schema-validation/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jderr-work%2Fexpress-standard-schema-validation/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32688333,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-06T08:33:17.875Z","status":"ssl_error","status_checked_at":"2026-05-06T08:33:17.221Z","response_time":117,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["arktype","express","express-middleware","joi","standard-schema","valibot","zod"],"created_at":"2026-05-06T10:01:49.435Z","updated_at":"2026-05-06T10:01:55.052Z","avatar_url":"https://github.com/jderr-work.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# express-standard-schema-validation\n\n[![Coverage Status](https://coveralls.io/repos/github/jderr-work/express-standard-schema-validation/badge.svg?branch=update-ci)](https://coveralls.io/github/jderr-work/express-standard-schema-validation?branch=update-ci)\n\nThis package is derived from [Evan Shortiss'](https://github.com/evanshortiss) [express-joi-validation](https://github.com/evanshortiss/express-joi-validation)\nCredit goes to him for the original implementation.\n\nThis package is an Express middleware for validating requests using a library that implements [Standard Schema V1](https://github.com/standard-schema/standard-schema) interface.\n\nOne divergence from the original package is that the joi package supported joi specific options. Because that is a\nlibrary specific feature, any options will need to be set on the schema itself.\n\nThe tests have been using **Joi**, **Zod**, **ArkType**, and **Valibot** but any library that implements Standard Schema V1 should work.\n\n## Features\n\n- 🎯 **Multi-Library Support** - Will work with any validation library implementing Standard Schema V1\n- 📘 **TypeScript First** - Written in TypeScript with full type safety and intellisense\n- 🔄 **Value Replacement** - Replaces validated inputs (e.g., `req.body`) with validated/transformed values\n- 💾 **Original Value Retention** - Keeps original values in `req.originalBody`, `req.originalQuery`, etc.\n- 🎨 **Flexible Configuration** - Configure validation behavior per-route or globally\n- ⚡ **Standard Schema V1** - Built on the Standard Schema specification\n\n## Install\n\n```bash\nnpm install express-standard-schema-validation\n```\n\nThen install your preferred validation library:\nThis has been tested with: zod, joi, arktype, valibot\n\n## Quick Start\n\n### Using Joi\n\n```js\nconst Joi = require('joi');\nconst express = require('express');\nconst { createValidator } = require('express-standard-schema-validation');\n\nconst app = express();\nconst validator = createValidator();\n\nconst querySchema = Joi.object({\n  name: Joi.string().required(),\n  age: Joi.number().integer().min(0),\n});\n\napp.get('/hello', validator.query(querySchema), (req, res) =\u003e {\n  res.json({ message: `Hello ${req.query.name}!` });\n});\n```\n\n### Using Zod\n\n```js\nconst { z } = require('zod');\nconst express = require('express');\nconst { createValidator } = require('express-standard-schema-validation');\n\nconst app = express();\nconst validator = createValidator();\n\nconst querySchema = z.object({\n  name: z.string(),\n  age: z.coerce.number().int().min(0),\n});\n\napp.get('/hello', validator.query(querySchema), (req, res) =\u003e {\n  res.json({ message: `Hello ${req.query.name}!` });\n});\n```\n\n### Using ArkType\n\n```js\nconst { type } = require('arktype');\nconst express = require('express');\nconst { createValidator } = require('express-standard-schema-validation');\n\nconst app = express();\nconst validator = createValidator();\n\nconst querySchema = type({\n  name: 'string',\n  age: 'string.numeric.parse',\n});\n\napp.get('/hello', validator.query(querySchema), (req, res) =\u003e {\n  res.json({ message: `Hello ${req.query.name}!` });\n});\n```\n\n### Using Valibot\n\n```js\nconst v = require('valibot');\nconst express = require('express');\nconst { createValidator } = require('express-standard-schema-validation');\n\nconst app = express();\nconst validator = createValidator();\n\nconst querySchema = v.object({\n  name: v.string(),\n  age: v.pipe(v.string(), v.transform(Number), v.number(), v.minValue(0)),\n});\n\napp.get('/hello', validator.query(querySchema), (req, res) =\u003e {\n  res.json({ message: `Hello ${req.query.name}!` });\n});\n```\n\n## API Reference\n\n### `createValidator(config)`\n\nCreates a validator instance with optional global configuration.\n\n**Parameters:**\n\n- `config.passError` (boolean, default: `false`) - Pass validation errors to Express error handler\n- `config.statusCode` (number, default: `400`) - HTTP status code for validation failures\n- `config.errorMessageTemplate` (string, optional) - Custom prefix for error messages. Default: `\"Error validating {container}:\"`\n\n**Returns:** Validator instance with middleware methods\n\n**Example:**\n\n```js\nconst validator = createValidator({\n  passError: true,\n  statusCode: 422,\n  errorMessageTemplate: 'Validation failed for',\n});\n```\n\n### Validator Methods\n\nAll methods accept a schema and optional configuration that overrides global settings.\n\n#### `validator.query(schema, [options])`\n\nValidates `req.query`. Original value stored in `req.originalQuery`.\n\n```js\napp.get('/search', validator.query(searchSchema), handler);\n```\n\n#### `validator.body(schema, [options])`\n\nValidates `req.body`. Original value stored in `req.originalBody`.\n\n```js\napp.post('/users', validator.body(userSchema), handler);\n```\n\n#### `validator.params(schema, [options])`\n\nValidates `req.params`. Original value stored in `req.originalParams`.\n\n**Important:** Must be attached directly to the route:\n\n```js\n// ✅ CORRECT\napp.get('/users/:id', validator.params(idSchema), handler);\n\n// ❌ INCORRECT - won't work\napp.use(validator.params(idSchema));\napp.get('/users/:id', handler);\n```\n\n#### `validator.headers(schema, [options])`\n\nValidates `req.headers`. Original value stored in `req.originalHeaders`.\n\n```js\napp.get('/api', validator.headers(authSchema), handler);\n```\n\n#### `validator.fields(schema, [options])`\n\nValidates form fields (for use with `express-formidable`). Original value stored in `req.originalFields`.\n\n```js\napp.post('/upload', formidable(), validator.fields(fieldsSchema), handler);\n```\n\n#### `validator.response(schema, [options])`\n\nValidates outgoing response data.\n\n```js\napp.get('/users/:id', validator.response(userSchema), handler);\n```\n\n### Options Object\n\nEach validator method accepts an optional `options` parameter:\n\n```js\n{\n  passError: boolean,   // Override global passError setting\n  statusCode: number    // Override global statusCode setting\n}\n```\n\n## Library-Specific Configuration\n\n**Important:** With Standard Schema, validation behavior should be configured **on the schema itself**, not via middleware options.\n\n### Joi Configuration\n\nConfigure Joi schemas using Joi's built-in methods:\n\n```js\nconst schema = Joi.object({\n  name: Joi.string().required(),\n  extra: Joi.any(),\n})\n  .unknown(true) // Allow extra properties\n  .options({\n    convert: true, // Type coercion\n    abortEarly: false, // Return all errors\n  });\n```\n\n### Zod Configuration\n\nConfigure Zod schemas using Zod's built-in methods:\n\n```js\n// v3\nconst schema = z\n  .object({\n    name: z.string(),\n    age: z.coerce.number(), // Type coercion\n  })\n  .strict(); // Reject extra properties\n// .passthrough()         // Allow extra properties\n// .strip()               // Remove extra properties (default)\n\n// v4\n// .strictObject()    // Reject extra properties\n// .looseObject() // Allow extra properties\nconst schema = z.looseObject({\n  //\n  name: z.string(),\n  age: z.coerce.number(), // Type coercion\n});\n\n// .strip()               // Remove extra properties (default)\n```\n\n### ArkType Configuration\n\nConfigure validation directly in ArkType's syntax:\n\n```js\nconst schema = type({\n  name: 'string',\n  age: 'string.numeric.parse', // Parse string to number\n  score: 'number\u003e0\u003c100', // Number between 0 and 100\n});\n```\n\n### Valibot Configuration\n\nConfigure Valibot schemas using pipes and modifiers:\n\n```js\nconst schema = v.object({\n  name: v.string(),\n  age: v.pipe(v.string(), v.transform(Number), v.number()),\n});\n// v.strictObject() - Reject extra properties\n// v.looseObject()  - Allow extra properties\n```\n\n## Library Migration Guide\n\n### Joi → Zod\n\n| Joi                                 | Zod v4              |\n| ----------------------------------- | ------------------- |\n| `Joi.number()` with `convert: true` | `z.coerce.number()` |\n| `.unknown(true)`                    | `z.looseObject`     |\n| `.unknown(false)`                   | `z.strictObject`    |\n| `.options({ stripUnknown: true })`  | Default behavior    |\n| `.options({ abortEarly: false })`   | Default behavior    |\n\n### Joi → ArkType\n\n| Joi                                 | ArkType                  |\n| ----------------------------------- | ------------------------ |\n| `Joi.number()` with `convert: true` | `'string.numeric.parse'` |\n| `Joi.number().min(0).max(100)`      | `'number\u003e-1\u003c101'`        |\n| `Joi.string().min(3).max(20)`       | `'string\u003e2\u003c21'`          |\n| `Joi.string().valid('a', 'b')`      | `'\"a\" \\| \"b\"'`           |\n\n### Joi → Valibot\n\n| Joi                                 | Valibot                                               |\n| ----------------------------------- | ----------------------------------------------------- |\n| `Joi.number()` with `convert: true` | `v.pipe(v.string(), v.transform(Number), v.number())` |\n| `Joi.number().min(0).max(100)`      | `v.pipe(v.number(), v.minValue(0), v.maxValue(100))`  |\n| `.unknown(true)`                    | `v.looseObject()`                                     |\n| `.unknown(false)`                   | `v.strictObject()`                                    |\n\n## Error Handling\n\n### Default Behavior\n\nBy default, validation failures return HTTP 400 with error message as plain text:\n\n```js\n// Request: GET /hello?name=123\u0026age=invalid\n// Response: 400 Bad Request\n// Body: Error validating request query: Expected string, received number. Age must be a number.\n```\n\nYou can customize the error message prefix using `errorMessageTemplate`:\n\n```js\nconst validator = createValidator({\n  errorMessageTemplate: 'Validation failed for',\n});\n\n// Request: GET /hello?name=123\u0026age=invalid\n// Response: 400 Bad Request\n// Body: Validation failed for request query: Expected string, received number. Age must be a number.\n```\n\n### Custom Error Handler\n\nUse `passError: true` to handle errors with Express error middleware:\n\n```js\nconst validator = createValidator({ passError: true });\n\napp.get('/hello', validator.query(schema), handler);\n\napp.use((err, req, res, next) =\u003e {\n  if (err \u0026\u0026 err.error) {\n    return res.status(400).json({\n      type: err.type, // 'query', 'body', 'params', 'headers', or 'fields'\n      message: err.error.message,\n      issues: err.issues, // Array of Standard Schema issues\n    });\n  }\n  next(err);\n});\n```\n\n### TypeScript Error Handling\n\n```ts\nimport { ExpressValidatorError, ContainerTypes } from 'express-standard-schema-validation';\n\napp.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) =\u003e {\n  if (err \u0026\u0026 'type' in err \u0026\u0026 err.type in ContainerTypes) {\n    const validationError = err as ExpressValidatorError;\n    return res.status(400).json({\n      type: validationError.type,\n      issues: validationError.issues,\n    });\n  }\n  next(err);\n});\n```\n\n## TypeScript Usage\n\n### Basic TypeScript Example\n\n```ts\nimport { z } from 'zod';\nimport express from 'express';\nimport {\n  createValidator,\n  ValidatedRequest,\n  ValidatedRequestSchema,\n  ContainerTypes,\n} from 'express-standard-schema-validation';\n\nconst app = express();\nconst validator = createValidator();\n\nconst querySchema = z.object({\n  name: z.string(),\n  age: z.coerce.number(),\n});\n\ninterface HelloRequestSchema extends ValidatedRequestSchema {\n  [ContainerTypes.Query]: z.infer\u003ctypeof querySchema\u003e;\n}\n\napp.get('/hello', validator.query(querySchema), (req: ValidatedRequest\u003cHelloRequestSchema\u003e, res) =\u003e {\n  // req.query.name is string\n  // req.query.age is number\n  res.json({ message: `Hello ${req.query.name}!` });\n});\n```\n\n### Multiple Validations\n\n```ts\nconst headerSchema = z\n  .object({\n    'x-api-key': z.string(),\n  })\n  .looseObject();\n\nconst bodySchema = z.object({\n  email: z.string().email(),\n  age: z.number(),\n});\n\ninterface CreateUserSchema extends ValidatedRequestSchema {\n  [ContainerTypes.Headers]: z.infer\u003ctypeof headerSchema\u003e;\n  [ContainerTypes.Body]: z.infer\u003ctypeof bodySchema\u003e;\n}\n\napp.post(\n  '/users',\n  validator.headers(headerSchema),\n  validator.body(bodySchema),\n  (req: ValidatedRequest\u003cCreateUserSchema\u003e, res) =\u003e {\n    // Fully typed access to headers and body\n    const apiKey = req.headers['x-api-key'];\n    const { email, age } = req.body;\n    res.json({ success: true });\n  },\n);\n```\n\n## Validation Order\n\nValidators execute in the order they're passed to the route:\n\n```js\napp.post(\n  '/tickets',\n  validator.headers(headerSchema), // Validates first\n  validator.body(bodySchema), // Validates second\n  validator.query(querySchema), // Validates third\n  handler,\n);\n```\n\nIf any validation fails, subsequent validators and the handler won't execute.\n\n## Original Values\n\nOriginal (pre-validation) values are preserved:\n\n```js\nconst schema = z.object({\n  age: z.coerce.number(),\n});\n\napp.get('/test', validator.query(schema), (req, res) =\u003e {\n  console.log(req.originalQuery.age); // \"25\" (string)\n  console.log(req.query.age); // 25 (number)\n});\n```\n\nAvailable original properties:\n\n- `req.originalQuery`\n- `req.originalBody`\n- `req.originalParams`\n- `req.originalHeaders`\n- `req.originalFields`\n\n## Examples\n\nFull JavaScript and TypeScript examples are in the [`example/`](./example) directory.\n\n## Development\n\n### Prerequisites\n\n- Node.js \u003e= 20.0.0\n- npm \u003e= 9.0.0\n\n### Setup\n\n```bash\ngit clone https://github.com/jderr-work/express-standard-schema-validation.git\ncd express-standard-schema-validation\nnpm install\n```\n\n### Running Checks\n\nRun all verification checks (what CI runs):\n\n```bash\nnpm run verify\n```\n\nThis runs:\n\n- ✅ Code formatting check (Prettier)\n- ✅ Linting (ESLint)\n- ✅ Type checking (TypeScript)\n- ✅ All tests (Vitest)\n- ✅ Build verification\n\nRun quick verification (skips tests, runs on pre-commit):\n\n```bash\nnpm run verify:quick\n```\n\nRun individual checks:\n\n```bash\nnpm run format:check  # Check code formatting\nnpm run format        # Auto-fix formatting\nnpm run lint          # Check code quality\nnpm run lint:fix      # Auto-fix linting issues\nnpm run type-check    # Check TypeScript types\nnpm test              # Run all tests\nnpm run test:unit     # Run unit tests only (watch mode)\nnpm run test:acceptance # Run acceptance tests only (watch mode)\nnpm run build         # Build the package\nnpm run coverage      # Generate coverage report\n```\n\n### Pre-commit Hooks\n\nThis project uses [simple-git-hooks](https://github.com/toplenboren/simple-git-hooks) to automatically run `verify:quick` before each commit. This ensures code quality and catches issues early.\n\nIf you need to bypass the pre-commit hook (not recommended):\n\n```bash\ngit commit --no-verify -m \"message\"\n```\n\n### Code Coverage\n\nThis project maintains **100% code coverage**. All tests must pass with 100% coverage for:\n\n- Lines\n- Functions\n- Branches\n- Statements\n\nCoverage reports are automatically generated and uploaded to Coveralls on CI builds.\n\n### CI/CD\n\nThis project uses GitHub Actions for continuous integration:\n\n- **All checks** (format, lint, type-check, tests, build) run on every Node.js version (18, 20, 22) and Express version (4, 5)\n- **Coverage** is uploaded to Coveralls and must maintain 100%\n- **CI runs** on all PRs and pushes to `main` branch\n- **CI skips** for documentation-only changes (\\*.md files)\n\nSee [.github/workflows/ci.yaml](.github/workflows/ci.yaml) for the complete CI configuration.\n\n### Testing with Different Validators\n\nThe test suite includes acceptance tests for all supported validation libraries:\n\n```bash\n# All validators are tested by default\nnpm test\n\n# Run specific validator tests\nnpm run test:acceptance -- joi.test.ts\nnpm run test:acceptance -- zod.test.ts\nnpm run test:acceptance -- arktype.test.ts\nnpm run test:acceptance -- valibot.test.ts\n```\n\n## Standard Schema Support\n\nThis module validates that schemas implement the [Standard Schema V1](https://github.com/standard-schema/standard-schema) interface:\n\n```js\n{\n  '~standard': {\n    version: 1,\n    vendor: string,\n    validate: (value: unknown) =\u003e Promise\u003cResult\u003e\n  }\n}\n```\n\nSupported libraries and minimum versions:\n\n- **Joi** \u003e= 18.0.0\n- **Zod** \u003e= 3.23.0\n- **ArkType** \u003e= 2.0.0-rc\n- **Valibot** \u003e= 1.0.0\n\n## Migration from express-joi-validation\n\nThis module is a fork of [express-joi-validation](https://github.com/evanshortiss/express-joi-validation) with Standard Schema support.\n\n### Breaking Changes\n\n1. **No library options parameter** - Configure validation on schemas, not middleware\n2. **Package name** - `express-joi-validation` → `express-standard-schema-validation`\n3. **Multi-library support** - Works with Joi, Zod, ArkType, and Valibot\n\n### Migration Steps\n\n```diff\n- const validator = require('express-joi-validation').createValidator({\n-   joi: { convert: true, allowUnknown: false }\n- })\n+ const validator = require('express-standard-schema-validation').createValidator()\n\n- const schema = Joi.object({ name: Joi.string() })\n+ const schema = Joi.object({ name: Joi.string() })\n+   .options({ convert: true, allowUnknown: false })\n\n  app.get('/hello', validator.query(schema), handler)\n```\n\n## License\n\nMIT\n\n## Credits\n\nOriginal [express-joi-validation](https://github.com/evanshortiss/express-joi-validation) by Evan Shortiss.\n\nStandard Schema support and multi-library architecture by the contributors to this fork.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjderr-work%2Fexpress-standard-schema-validation","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjderr-work%2Fexpress-standard-schema-validation","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjderr-work%2Fexpress-standard-schema-validation/lists"}