{"id":25050943,"url":"https://github.com/angablue/express-zod-safe","last_synced_at":"2025-09-23T20:56:06.299Z","repository":{"id":216039692,"uuid":"740306428","full_name":"AngaBlue/express-zod-safe","owner":"AngaBlue","description":"Typesafe middleware designed for Node.js applications, leveraging the robustness of Zod schemas to validate incoming request bodies, parameters, and queries.","archived":false,"fork":false,"pushed_at":"2025-09-21T16:00:52.000Z","size":161,"stargazers_count":35,"open_issues_count":2,"forks_count":6,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-09-21T18:19:50.277Z","etag":null,"topics":["express","javascript","nodejs","types","typescript","zod"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/express-zod-safe","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/AngaBlue.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":"2024-01-08T04:26:52.000Z","updated_at":"2025-09-17T13:18:23.000Z","dependencies_parsed_at":"2024-01-08T06:39:19.025Z","dependency_job_id":"768664c2-a2d1-4140-ad27-664f5181f60c","html_url":"https://github.com/AngaBlue/express-zod-safe","commit_stats":null,"previous_names":["angablue/express-zod-safe"],"tags_count":23,"template":false,"template_full_name":null,"purl":"pkg:github/AngaBlue/express-zod-safe","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AngaBlue%2Fexpress-zod-safe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AngaBlue%2Fexpress-zod-safe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AngaBlue%2Fexpress-zod-safe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AngaBlue%2Fexpress-zod-safe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AngaBlue","download_url":"https://codeload.github.com/AngaBlue/express-zod-safe/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AngaBlue%2Fexpress-zod-safe/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":276647749,"owners_count":25679621,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-23T02:00:09.130Z","response_time":73,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["express","javascript","nodejs","types","typescript","zod"],"created_at":"2025-02-06T09:18:38.628Z","updated_at":"2025-09-23T20:56:06.287Z","avatar_url":"https://github.com/AngaBlue.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003e🛡️ Express Zod Safe\u003c/h1\u003e\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://www.npmjs.com/package/express-zod-safe\" target=\"_blank\"\u003e\n    \u003cimg alt=\"Downloads\" src=\"https://img.shields.io/npm/dm/express-zod-safe.svg?color=blue\u0026label=Downloads\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/express-zod-safe\" target=\"_blank\"\u003e\n    \u003cimg alt=\"Version\" src=\"https://img.shields.io/npm/v/express-zod-safe.svg?label=Version\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/AngaBlue/exe/blob/master/LICENSE\" target=\"_blank\"\u003e\n    \u003cimg alt=\"Licence: MIT\" src=\"https://img.shields.io/npm/l/express-zod-safe?color=green\u0026label=Licence\" /\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\nExpress Zod Safe is a strict, typesafe middleware designed for Node.js applications, leveraging the robustness of Zod schemas to validate incoming request bodies, parameters, and queries. This package seamlessly integrates with Express.js (or similar frameworks) to provide developers with a typesafe, declarative approach to ensure data integrity and prevent invalid or malicious data from affecting their applications.\n\n_This package was inspired by Aquila169's [zod-express-middleware](https://github.com/Aquila169/zod-express-middleware) package, and is intended to be a more robust and typesafe alternative._\n\n## 🔒 Features\n\n - **Typesafe**: Built with TypeScript, offering complete typesafe interfaces that enrich your development experience.\n - **Zod Integration**: Utilises Zod schemas for comprehensive and customisable request validation.\n - **Middleware Flexibility**: Easily integrates with Express.js middleware stack, ensuring a smooth validation process without compromising performance.\n - **Parameter \u0026 Query Validation**: Validates not just request bodies but also URL parameters and query strings, covering all facets of incoming data.\n - **Error Handling**: Provides detailed, developer-friendly error responses to aid in debugging and informing API consumers.\n - **Simple \u0026 Intuitive**: Designed to be easy to use and understand, with a declarative API that is both concise and powerful.\n\n## ⬇️ Install\n\nInstall this package using your package manager of choice.\n\n```sh\nnpm i express-zod-safe\n```\n\n`zod`, `express` and `@types/express` are peer dependencies and must be installed separately.  This means you can bring your own version of these packages, and this package will not force you to use a specific version.\n\n```sh\nnpm i zod express \u0026\u0026 npm i -D @types/express\n```\n\n⚠️ Warning ⚠️: This package is designed to work with Zod `v4.0.0` and above.  If you are using Zod `v3.x.x`, you will need to use `express-zod-safe@1.5.4`.\n\n## 🛠️ Usage\n\n```ts\nimport express from 'express';\nimport validate from 'express-zod-safe';\nimport { z } from 'zod';\n \nconst app = express();\napp.use(express.json());\n \n// Define your Zod schemas\nconst params = {\n  userId: z.string().uuid(),\n};\nconst query = {\n  age: z.coerce.number().optional(), // Given all query params and url params are strings, this will coerce the value to a number.\n};\nconst body = {\n  name: z.string(),\n  email: z.string().email(),\n};\n \n// Use the validate middleware in your route\napp.post('/user/:userId', validate({ params, query, body }), (req, res) =\u003e {\n  // Your route logic here\n  res.send('User data is valid!');\n});\n\napp.listen(3000, () =\u003e console.log('Server running on port 3000'));\n```\n\n**Note:** The `validate` middleware must be used **after** any other middleware that parses/modifies the request body, such as `express.json()` or `express.urlencoded()`.\n\n### 📄 Using `ValidatedRequest`\nWhen you want to type the `Request` object outside of route handlers, you can use the `ValidatedRequest` utility to infer the validated types. Pass the same Zod schemas you provide to `validate` and the resulting `Request` type will be narrowed accordingly.\n\n```ts\nimport type { Response } from 'express';\nimport validate, { type ValidatedRequest } from 'express-zod-safe';\nimport { z } from 'zod';\n\nconst bodySchema = z.object({\n\ttitle: z.string(),\n\tdescription: z.string().max(200)\n});\n\ntype CreatePostRequest = ValidatedRequest\u003c{ body: typeof bodySchema }\u003e;\n\nfunction createPostHandler(req: CreatePostRequest, res: Response) {\n\t// req.body.title -\u003e string\n\t// req.body.description -\u003e string (max length validated by Zod)\n\tres.json({ message: 'Created!' });\n}\n\napp.post('/posts', validate({ body: bodySchema }), createPostHandler);\n```\n\n### 📦 Custom Error Handling\nBy default, the `validate` middleware will send a 400 Bad Request response with a JSON body containing the error message.  However, you can provide your own error handling function to customise the error response.\n\n```ts\n// ... extending the previous example\n\nconst handler = (errors, req, res, next) =\u003e {\n  res.status(400).json({\n    message: 'Invalid request data',\n    errors: errors.map((error) =\u003e error.message),\n  });\n};\n\n// Use the validate middleware in your route\napp.post('/user/:userId', validate({ handler, params, query, body }), (req, res) =\u003e {\n  // Your route logic here\n  res.send('User data is valid!');\n});\n```\n\n### ⚠️ Usage with Additional Middleware\nWhen using `express-zod-safe` with other middleware, it is important not to explicitly type the `Request` parameter in the middleware, as this will override the inferred type that `express-zod-safe` generates from your validation schemas.  The best way to do this is to instead type your other middleware (or cast them) to `WeakRequestHandler`, a weakly typed version of the `RequestHandler` type from `express`.\n\n```ts\nimport validate, { type WeakRequestHandler } from 'express-zod-safe';\n\n// Use the RequestHandler type, instead of explicitly typing (req: Request, res: Response, next: NextFunction)\nconst authenticate: WeakRequestHandler = (req, res, next) =\u003e {\n  // ... perform user authentication\n\n  next();\n};\n\napp.post('/user/:userId', authenticate, validate({ params, query, body }), (req, res) =\u003e {\n  // Your validation typing will work as expected here\n});\n\n```\n\nIf you do not control the middleware, such as when you import it from another library, you can instead cast the middleware to `WeakRequestHandler`.\n\n```ts\n// For one off cases...\napp.post('/user/:userId', authenticate as WeakRequestHandler, validate({ params, query, body }), (req, res) =\u003e {\n  // Your validation typing will work as expected here\n});\n\n// For a middleware with a lot of use, aliasing the middleware...\nconst auth = authenticate as WeakRequestHandler;\napp.post('/user/:userId', auth, validate({ params, query, body }), (req, res) =\u003e {\n  // Your validation typing will work as expected here\n});\n```\n\n### ⚠️ URL Parameters \u0026 Query Strings Coercion\nAs mentioned in the example above, all URL parameters and query strings are parsed as strings.  This means that if you have a URL parameter or query string that is expected to be a number, you must use the `z.coerce.number()` method to coerce the value to a number.  This is because Zod will not coerce the value for you, and will instead throw an error if the value is not a string.\n\n```ts\nconst params = {\n  userId: z.coerce.number(),\n};\n\napp.get('/user/:userId', validate({ params }), (req, res) =\u003e {\n  // req.params.userId -\u003e number\n});\n```\n\n### ⚠️ Missing Validation Schemas\nIf you do not provide a validation schema for a particular request component (e.g. `params`, `query`, or `body`), then that component will be assumed to be empty.  This means that requests with non-empty components will be rejected, and requests with empty components will be accepted.  The types on the `req` object will also reflect this, and will be an empty object `{}` if the component is not provided.\n\n```ts\nconst body = {\n  name: z.string(),\n  email: z.string().email(),\n};\n\napp.post('/user', validate({ body }), (req, res) =\u003e {\n  // req.body.name -\u003e string\n  // req.body.email -\u003e string\n  // req.params.age -\u003e Property 'age' does not exist on type unknown\n  // req.query.age -\u003e Property 'age' does not exist on type unknown\n});\n```\n\nThis behaviour is intentional and ensures that you do not try to access or use a property that does not exist on the `req` object.  If you'd prefer to allow any property for any given request component, you can do so by setting a loose validation schema with `z.any()`.\n\n```ts\nconst body = {\n  name: z.string(),\n  email: z.string().email(),\n};\n\nconst params = z.any()\n\napp.post('/user', validate({ body, params }), (req, res) =\u003e {\n  // req.body.name -\u003e string\n  // req.body.email -\u003e string\n  // req.params.age -\u003e any\n  // req.query.age -\u003e Property 'age' does not exist on type unknown\n});\n```\n\n## ⭐️ Show your support\n\nGive a ⭐️ on GitHub if this project helped you!\n\n## 📝 License\n\nCopyright © [AngaBlue](https://github.com/AngaBlue).\u003cbr /\u003e\nThis project is [MIT](https://github.com/AngaBlue/express-zod-safe/blob/master/LICENSE) licensed.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fangablue%2Fexpress-zod-safe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fangablue%2Fexpress-zod-safe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fangablue%2Fexpress-zod-safe/lists"}