npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@json-express/middleware-validation

v2.0.0

Published

Zod Validation Middleware plugin for JSON Express

Downloads

35

Readme

@json-express/middleware-validation

Model-driven validation middleware for JSONExpress v2. Reads each model's validation block at boot, derives a Zod baseline from fields, and rejects malformed requests with a structured 400.

Features

  • Schema-driven — declare validation alongside the entity it guards, in models/*.ts. No second surface in jex.config.ts.
  • Auto-derived baselinerequired, min/max, minLength/maxLength field options map to Zod automatically. Free 400s without restating the field shape.
  • Extend or override — pass a builder (baseline) => baseline.extend({...}) for richer rules, or a hand-rolled Zod schema to fully replace the baseline.
  • Custom endpoints — per-endpoint validation lives next to the handler in the model's endpoints block.
  • Plugin-swappable — anything with a safeParse(v) method works; core stays Zod-agnostic so a future middleware-validation-yup could ship without forking.

Installation

npm install @json-express/middleware-validation

The CLI auto-discovers and registers it. No further wiring needed.

Usage

Entity validation

// models/products.ts
import { defineModel, types } from '@json-express/core';
import { z } from 'zod';

export default defineModel({
    fields: {
        id: types.id(),
        name: types.string({ required: true, minLength: 2 }),
        price: types.number({ required: true, min: 0 }),
        inStock: types.boolean(),
    },
    validation: {
        // Use the auto-derived baseline as-is — empty op block opts in.
        list: {},
        // Extend the baseline for create — adds an email-format check on a new field.
        create: {
            body: (baseline) =>
                (baseline as any).extend({
                    inStock: z.boolean().default(true),
                }),
        },
        // Hand-rolled override for update.
        update: {
            body: z.object({
                name: z.string().min(2).optional(),
                price: z.number().min(0).optional(),
            }),
        },
    },
});

This wires:

  • POST /products → validates body against the create schema.
  • PATCH /products/:id → validates body against the update schema (or .partial() of the baseline if no override).
  • GET /products → validates query string.

A failing request returns:

{
    "error": "Validation failed",
    "details": { "body": { "_errors": [], "name": { "_errors": ["Required"] } } }
}

Custom endpoints

// models/albums.ts
export default defineModel({
    fields: { /* ... */ },
    endpoints: {
        'POST /:id/play': {
            handler: async (req, res, ctx) => { /* ... */ },
            validation: {
                body: z.object({ trackNumber: z.number().int().positive() }),
            },
        },
    },
});

Fieldless route-only models

For non-entity routes (/search, /auth/login, webhooks), use defineRoutes():

// models/search.ts
import { defineRoutes } from '@json-express/core';
import { z } from 'zod';

export default defineRoutes({
    endpoints: {
        'GET /': {
            handler: async (req, res, ctx) => {
                const products = await ctx.db.getAll('products');
                res.json(products.filter(p => p.name.includes(String(req.query.q))));
            },
            validation: {
                query: z.object({ q: z.string().min(2) }),
            },
        },
    },
});

Mounts as GET /search. No CRUD codegen, no entity schema — just behavior.

Validator Resolution

Each validation[op].body (and validation[op].query) accepts:

| Form | Behavior | |---|---| | absent (no op block) | No validation — middleware passes through. | | {} (op block, no body) | Use auto-derived baseline. | | (baseline) => Validator | Builder — receives the baseline, returns the final validator. | | Validator (e.g. z.object(...)) | Used directly; baseline ignored. |

The Validator type is structural: anything with safeParse(v): { success, data, error } works.

Field → Zod Mapping (Baseline)

| Field type | Zod | Honored options | |---|---|---| | types.string | z.string() | minLength.min(n), maxLength.max(n) | | types.number | z.number() | min.min(n), max.max(n) | | types.boolean | z.boolean() | — | | types.date | z.union([z.string(), z.date()]) | — | | types.id | skipped (server-generated) | — | | types.relation | skipped (resolved server-side) | — |

required: true keeps the field non-optional; otherwise it's marked .optional(). For update ops the baseline is automatically .partial() to match PATCH semantics.

License

MIT