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

@minimajs/schema

v1.4.0

Published

Schema validation powered by Zod for request/response validation with type safety

Downloads

207

Readme

@minimajs/schema

Type-safe request validation for Minima.js powered by Zod. Validate request bodies, headers, search params, and route params with full TypeScript inference.

Features

  • 🔒 Type-Safe - Full TypeScript inference from Zod schemas
  • Runtime Validation - Catch invalid data before it reaches your handlers
  • 🎯 Context-Aware - Validates and caches data in request context
  • 🔄 Async Support - Built-in async validation for database checks
  • 📦 Two APIs - Simple validators and resource-based validation

Installation

# Using Bun
bun add @minimajs/schema zod

# Using npm
npm install @minimajs/schema zod

Two Validation Approaches

1. Simple Validators (Recommended for Basic Use)

Direct validation functions that parse data immediately:

import { createBody, createHeaders, createSearchParams } from "@minimajs/schema";
import { z } from "zod";

const getUserData = createBody(
  z.object({
    name: z.string(),
    email: z.string().email(),
  })
);

app.post("/users", () => {
  const data = getUserData();
  return { created: data };
});

2. Resource API (Advanced - With Route Metadata)

Validation integrated with route metadata, validated in request hook:

import { createBody, schema, configureSchema } from "@minimajs/schema/resource";
import { z } from "zod";

// Register the schema plugin
app.register(configureSchema());

// Create validator
const getUserData = createBody(
  z.object({
    name: z.string(),
    email: z.string().email(),
  })
);

// Use with schema() descriptor
app.post("/users", schema(getUserData), () => {
  const data = getUserData(); // Already validated in request hook
  return { created: data };
});

API Reference

Simple Validators

createBody<T>(schema)

Validates request body against a Zod schema.

import { createBody } from "@minimajs/schema";
import { z } from "zod";

const getUserData = createBody(
  z.object({
    name: z.string().min(2),
    email: z.string().email(),
    age: z.number().int().positive().optional(),
  })
);

app.post("/users", () => {
  const data = getUserData();
  // data: { name: string; email: string; age?: number }
  return { created: data };
});
// Preserve unknown fields
const getUserData = createBody(userSchema);

createBodyAsync<T>(schema)

Async version for schemas with async refinements:

import { createBodyAsync } from "@minimajs/schema";
import { z } from "zod";

const getUserData = createBodyAsync(
  z.object({
    email: z
      .string()
      .email()
      .refine(
        async (email) => {
          const exists = await db.users.exists({ email });
          return !exists;
        },
        { message: "Email already taken" }
      ),
    name: z.string(),
  })
);

app.post("/users", async () => {
  const data = await getUserData();
  return { created: data };
});

createHeaders<T>(schema, options?)

Validates request headers:

import { createHeaders } from "@minimajs/schema";
import { z } from "zod";

const getHeaders = createHeaders({
  authorization: z.string().startsWith("Bearer "),
  "content-type": z.literal("application/json"),
});

app.post("/protected", () => {
  const headers = getHeaders();
  return { authenticated: true };
});

createHeadersAsync<T>(schema, options?)

Async version for header validation.

createSearchParams<T>(schema, options?)

Validates URL search/query parameters:

import { createSearchParams } from "@minimajs/schema";
import { z } from "zod";

const getQuery = createSearchParams({
  page: z.string().transform(Number).pipe(z.number().positive()),
  limit: z.string().transform(Number).pipe(z.number().max(100)),
  search: z.string().optional(),
});

app.get("/users", () => {
  const query = getQuery();

  return {
    page: query.page,
    limit: query.limit,
    users: [],
  };
});

createSearchParamsAsync<T>(schema, options?)

Async version for search params validation.

Resource API (Advanced)

createBody<T>(schema, options?)

Creates a validator function with metadata for use with schema() descriptor:

import { createBody, schema, configureSchema } from "@minimajs/schema/resource";
import { z } from "zod";

app.register(configureSchema());

const getUserData = createBody(
  z.object({
    name: z.string(),
    email: z.string().email(),
  })
);

app.post("/users", schema(getUserData), () => {
  const data = getUserData(); // Already validated
  return { created: data };
});

createHeaders<T>(schema, options?)

Header validator for resource API.

createSearchParams<T>(schema, options?)

Search params validator for resource API.

createParams<T>(schema, options?)

Route params validator (resource API only):

import { createParams, schema, configureSchema } from "@minimajs/schema/resource";
import { z } from "zod";

app.register(configureSchema());

const getParams = createParams({
  id: z.string().uuid(),
});

app.get("/users/:id", schema(getParams), () => {
  const params = getParams();
  return { userId: params.id };
});

schema(...validators)

Route metadata descriptor that attaches validators to a route:

import { schema, createBody, createParams } from "@minimajs/schema/resource";

const getUserData = createBody(userSchema);
const getParams = createParams({ id: z.string().uuid() });

app.post("/users/:id", schema(getUserData, getParams), () => {
  const data = getUserData();
  const params = getParams();
  return { updated: data };
});

configureSchema()

Plugin that validates all schemas in the request hook:

import { configureSchema } from "@minimajs/schema/resource";

app.register(configureSchema());

Error Handling

ValidationError

Validation failures throw ValidationError (422 status):

import { ValidationError } from "@minimajs/schema";

try {
  const data = createBody(userSchema);
} catch (err) {
  if (err instanceof ValidationError) {
    console.log(err.message); // "Validation failed for 'email'"
    console.log(err.issues); // Zod issues array
  }
}

Error Response Format

{
  "message": "Validation failed for 'email', 'name'",
  "issues": [
    {
      "code": "invalid_type",
      "expected": "string",
      "received": "undefined",
      "path": ["email"],
      "message": "Required"
    }
  ]
}

Custom Error Handling

import { hook } from "@minimajs/server";
import { ValidationError } from "@minimajs/schema";

app.register(
  hook("error", (error) => {
    if (error instanceof ValidationError) {
      return {
        success: false,
        errors: error.issues?.map((issue) => ({
          field: issue.path.join("."),
          message: issue.message,
        })),
      };
    }
  })
);

Advanced Examples

Nested Object Validation

const addressSchema = z.object({
  street: z.string(),
  city: z.string(),
  zipCode: z.string().regex(/^\d{5}$/),
});

const getUserData = createBody(
  z.object({
    name: z.string(),
    email: z.string().email(),
    address: addressSchema,
    alternateAddresses: z.array(addressSchema).optional(),
  })
);

app.post("/users", () => {
  const data = getUserData();
  return { created: data };
});

Cross-Field Validation

const getUserData = createBody(
  z
    .object({
      password: z.string().min(8),
      confirmPassword: z.string(),
    })
    .refine((data) => data.password === data.confirmPassword, {
      message: "Passwords must match",
      path: ["confirmPassword"],
    })
);

app.post("/register", () => {
  const data = getUserData();
  return { success: true };
});

Database Uniqueness Check

const getUserData = createBodyAsync(
  z.object({
    email: z
      .string()
      .email()
      .refine(
        async (email) => {
          const user = await db.users.findOne({ email });
          return !user;
        },
        { message: "Email already exists" }
      ),
    name: z.string(),
  })
);

app.post("/users", async () => {
  const data = await getUserData();
  return { created: data };
});

Transformations

const getQuery = createSearchParams({
  // Transform string to number
  page: z.string().transform(Number).pipe(z.number().positive()),

  // Trim whitespace
  search: z.string().trim().optional(),

  // Parse date
  startDate: z
    .string()
    .transform((str) => new Date(str))
    .pipe(z.date()),

  // Parse JSON
  filter: z
    .string()
    .transform((str) => JSON.parse(str))
    .pipe(z.object({ category: z.string() })),
});

app.get("/users", () => {
  const query = getQuery();

  return {
    page: query.page, // number
    search: query.search, // string | undefined
    startDate: query.startDate, // Date
    filter: query.filter, // { category: string }
  };
});

Discriminated Unions

const getEventData = createBody(
  z.discriminatedUnion("type", [
    z.object({
      type: z.literal("user.created"),
      userId: z.string(),
      email: z.string().email(),
    }),
    z.object({
      type: z.literal("user.deleted"),
      userId: z.string(),
    }),
  ])
);

app.post("/webhooks", () => {
  const event = getEventData();

  switch (event.type) {
    case "user.created":
      return handleUserCreated(event);
    case "user.deleted":
      return handleUserDeleted(event);
  }
});

Partial Updates

const userSchema = z.object({
  name: z.string(),
  email: z.string().email(),
  age: z.number(),
});

// Make all fields optional
const getUpdates = createBody(userSchema.partial());

app.patch("/users/:id", () => {
  const updates = getUpdates();
  return { updated: updates };
});

When to Use Each API

Simple Validators (@minimajs/schema)

Best for:

  • Quick validation without route metadata
  • Simple applications
  • One-off validators
  • Direct control over validation timing
import { createBody } from "@minimajs/schema";

Resource API (@minimajs/schema/resource)

Best for:

  • Complex applications with many routes
  • Consistent validation across routes
  • OpenAPI/documentation generation
  • Validation in request hooks (before handler)
import { createBody, schema, configureSchema } from "@minimajs/schema/resource";

TypeScript Tips

Infer Types from Schemas

import { z } from "zod";

const userSchema = z.object({
  name: z.string(),
  email: z.string().email(),
});

type User = z.infer<typeof userSchema>;
// { name: string; email: string }

Reusable Schemas

// schemas/user.ts
export const userIdSchema = z.string().uuid();

export const createUserSchema = z.object({
  name: z.string().min(2),
  email: z.string().email(),
});

export const updateUserSchema = createUserSchema.partial();

export type CreateUser = z.infer<typeof createUserSchema>;
export type UpdateUser = z.infer<typeof updateUserSchema>;

Related

License

MIT