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 🙏

© 2025 – Pkg Stats / Ryan Hefner

mock-dash

v0.2.10

Published

A TypeScript library for generating type-safe API clients and mock servers from Zod schemas

Readme

MockDash

A TypeScript library that lets you define your API schema once and get both a type-safe API client for your frontend and a Hono-based mock server for development.

npm version TypeScript License: MIT

Table of Contents

Why MockDash?

  • Single Source of Truth: Define your API schema once using Zod
  • Type-Safe Client: Get a fully typed API client for your frontend
  • Mock Server: Automatically generate a Hono mock server for development
  • Frontend Independence: Work on frontend features while waiting for backend implementation
  • Zero Configuration: Works out of the box with sensible defaults

Installation

npm install mock-dash zod
npm install --save-dev hono
# or
pnpm add mock-dash zod
pnpm add -D hono

MockDash has minimal dependencies:

  • zod (peer dependency) - For schema validation and type inference
  • hono (dev dependency) - For mock server generation
  • @hono/zod-validator - Built-in for request validation

Quick Start

Here's a simple example to get you started:

import z from 'zod'
import { defineGet, definePost, createApiClient, createMockServer } from 'mock-dash'

// 1. Define your API schema once
const apiSchema = {
  getUser: defineGet('/users/:id', {
    response: z.object({
      id: z.string(),
      name: z.string(),
      email: z.string().email(),
    }),
  }),
  createUser: definePost('/users', {
    input: {
      json: z.object({
        name: z.string(),
        email: z.string().email(),
      }),
    },
    response: z.object({
      id: z.string(),
      name: z.string(),
      email: z.string(),
      createdAt: z.string(),
    }),
  }),
}

// 2. Create a type-safe API client
const client = createApiClient({
  apiSchema,
  baseURL: 'https://api.example.com',
})

// 3. Use the client with full type safety
const response = await client.api.users.id('123').get()
if (response.data) {
  console.log(response.data.name) // TypeScript knows this is a string
}

// 4. Create a mock server for development
apiSchema.getUser.defineMock((ctx) => ({
  id: ctx.inputs.param.id,
  name: 'John Doe',
  email: '[email protected]',
}))

apiSchema.createUser.defineMock((ctx) => ({
  id: 'new-user-123',
  name: ctx.inputs.json.name,
  email: ctx.inputs.json.email,
  createdAt: new Date().toISOString(),
}))

const mockServer = createMockServer(apiSchema)

Features

  • Type-Safe API Client: Automatically generated client with full TypeScript support
  • Mock Server: Hono-based mock server for development and testing
  • Zod Validation: Request/response validation using Zod schemas
  • Path Parameters: Support for dynamic URL segments (:id, :slug, etc.)
  • Query Parameters: Type-safe query string handling
  • Request Bodies: JSON, form data, and custom content types
  • Stream Support: Server-Sent Events (SSE) and JSON streaming
  • WebSocket Support: Real-time bidirectional communication
  • Error Handling: Structured error types for different failure modes
  • Interceptors: Request/response transformation and middleware
  • OpenAPI Generation: Generate schemas from existing OpenAPI specs
  • Path Aliases: Support for API versioning and prefixes

Usage

Define Endpoints

HTTP Methods

MockDash supports all standard HTTP methods with type-safe definitions:

import z from 'zod'
import { defineGet, definePost, definePut, definePatch, defineDelete } from 'mock-dash'

// GET endpoint with query parameters
const getUsers = defineGet('/users', {
  input: {
    query: {
      page: z.string().optional(),
      limit: z.coerce.number().optional(),
      search: z.string().optional(),
    },
  },
  response: z.array(z.object({
    id: z.string(),
    name: z.string(),
    email: z.string(),
  })),
})

// POST endpoint with JSON body
const createUser = definePost('/users', {
  input: {
    json: z.object({
      name: z.string(),
      email: z.string().email(),
      age: z.number().min(18),
    }),
  },
  response: z.object({
    id: z.string(),
    name: z.string(),
    email: z.string(),
    age: z.number(),
    createdAt: z.string(),
  }),
})

// PUT endpoint with path parameters
const updateUser = definePut('/users/:id', {
  input: {
    json: z.object({
      name: z.string().optional(),
      email: z.string().email().optional(),
    }),
  },
  response: z.object({
    id: z.string(),
    name: z.string(),
    email: z.string(),
    updatedAt: z.string(),
  }),
})

// DELETE endpoint
const deleteUser = defineDelete('/users/:id', {
  response: z.void(),
})

Streams

MockDash supports different types of streaming responses:

import { defineSSE, defineJSONStream, defineBinaryStream } from 'mock-dash'

// Server-Sent Events
const notifications = defineGet('/notifications', {
  response: defineSSE({
    message: z.object({
      type: z.literal('message'),
      content: z.string(),
    }),
    alert: z.object({
      type: z.literal('alert'),
      level: z.enum(['info', 'warning', 'error']),
      message: z.string(),
    }),
  }),
})

// JSON streaming
const streamData = defineGet('/stream', {
  response: defineJSONStream(
    z.object({
      id: z.string(),
      timestamp: z.number(),
      data: z.any(),
    })
  ),
})

// Binary streaming
const downloadFile = defineGet('/files/:id', {
  response: defineBinaryStream('application/pdf'),
})

WebSockets

Define real-time WebSocket endpoints with typed message schemas:

import { defineWebSocket } from 'mock-dash'

const chatEndpoint = defineGet('/chat/:roomId', {
  response: defineWebSocket(
    // Server-to-client messages
    [
      z.object({ type: z.literal('message'), text: z.string(), user: z.string() }),
      z.object({ type: z.literal('userJoined'), user: z.string() }),
      z.object({ type: z.literal('userLeft'), user: z.string() }),
    ],
    // Client-to-server messages
    [
      z.object({ type: z.literal('sendMessage'), text: z.string() }),
      z.object({ type: z.literal('join'), user: z.string() }),
    ]
  ),
})

Options

Configure endpoints with additional options:

// Path aliases for API versioning
const getUser = defineGet('{api}/users/:id', {
  response: userSchema,
  options: {
    alias: { api: '/api/v1' },
  },
})

// Custom headers or middleware configuration
const secureEndpoint = defineGet('/admin/users', {
  response: userListSchema,
  options: {
    // Custom options can be used by your middleware
    requiresAuth: true,
  },
})

Default Values

You can use Zod's .default() modifier to specify default values for input fields (query parameters, JSON body, or form data). These defaults are automatically applied by the client when the field is omitted.

const searchUsers = defineGet('/users', {
  input: {
    query: {
      // Default to page 1 if not provided
      page: z.coerce.number().default(1),
      // Default to 10 items per page
      limit: z.coerce.number().default(10),
    },
  },
  response: userListSchema,
})

// Client usage:
// resulting URL: /users?page=1&limit=10
await client.api.users.get()

// resulting URL: /users?page=2&limit=10
await client.api.users.get({ query: { page: 2 } })

Generate Type-Safe Client

Client Methods

The API client provides a fluent interface matching your endpoint paths:

const client = createApiClient({
  apiSchema,
  baseURL: 'https://api.example.com',
  // Optional: custom fetch implementation
  fetch: customFetch,
})

// Simple GET request
const users = await client.api.users.get({ query: { limit: 10 } })

// GET with path parameters
const user = await client.api.users.id('123').get()

// POST with JSON body
const newUser = await client.api.users.post({
  json: { name: 'John', email: '[email protected]' },
})

// Nested paths
const userPosts = await client.api.users.id('123').posts.get()

// Complex nested paths with multiple parameters
const comment = await client.api.users
  .userId('123')
  .posts.postId('456')
  .comments.commentId('789')
  .get()

Creating Endpoint URIs

Use createEndpointUri to generate full URLs for your endpoints with automatic path parameter replacement. This is useful for sharing links, debugging, or constructing URLs manually:

const client = createApiClient({
  apiSchema,
  baseURL: 'https://api.example.com',
  alias: { api: '/api/v1' },
})

// Generate a full URL with path parameters
const userUrl = client.createEndpointUri('/users/:id', { id: '123' })
// Returns: 'https://api.example.com/users/123'

// Works with multiple path parameters
const commentUrl = client.createEndpointUri(
  '/users/:userId/posts/:postId/comments/:commentId',
  { userId: '1', postId: '42', commentId: '789' }
)
// Returns: 'https://api.example.com/users/1/posts/42/comments/789'

// Works with path aliases
const aliasedUrl = client.createEndpointUri('/{api}/products/:id', { id: '456' })
// Returns: 'https://api.example.com/api/v1/products/456'

// Endpoints without parameters don't require the second argument
const listUrl = client.createEndpointUri('/users')
// Returns: 'https://api.example.com/users'

The method is fully type-safe: TypeScript will require the parameters object only when the path contains parameters (:param), and will infer the exact parameter names from the path string.

Note: This method generates complete URLs with all path parameters replaced. For making actual API calls, use the normal client methods which provide additional type safety and request options.

orThrow Methods

For scenarios where you want to throw errors directly instead of handling them in the response object, use the orThrow method:

try {
  const user = await client.api.users.id('123').get.orThrow()
  console.log(user.name)
} catch (error) {
  // Handle errors directly
  console.error('Error fetching user:', error)
}

Form Data Parsing

For endpoints that accept JSON input, MockDash provides a safeParseForm utility method to validate and parse FormData into the expected schema format, including support for nested objects and arrays:

// Define an endpoint that accepts JSON input with nested structures
const createProject = definePost('/projects', {
  input: {
    json: z.object({
      name: z.string(),
      description: z.string().optional(),
      settings: z.object({
        isPublic: z.boolean(),
        tags: z.array(z.string()),
      }),
      members: z.array(z.object({
        name: z.string(),
        email: z.string().email(),
        role: z.string(),
      })),
    }),
  },
  response: projectSchema,
})

// Parse FormData with nested objects and arrays
const formData = new FormData()
formData.append('name', 'My Project')
formData.append('description', 'A sample project')

// Nested object using dot notation
formData.append('settings.isPublic', 'true')
formData.append('settings.tags', 'frontend')
formData.append('settings.tags', 'typescript')

// Array of objects using indexed notation
formData.append('members[0].name', 'Alice Johnson')
formData.append('members[0].email', '[email protected]')
formData.append('members[0].role', 'admin')
formData.append('members[1].name', 'Bob Wilson')
formData.append('members[1].email', '[email protected]')
formData.append('members[1].role', 'developer')

// Validate and parse the form data
const parseResult = client.api.projects.post.safeParseForm(formData)

if (parseResult.success) {
  // parseResult.data is fully typed and validated
  console.log(parseResult.data.name) // "My Project"
  console.log(parseResult.data.settings.isPublic) // true (boolean)
  console.log(parseResult.data.settings.tags) // ["frontend", "typescript"]
  console.log(parseResult.data.members[0].name) // "Alice Johnson"
  
  // Use the parsed data in your API call
  const response = await client.api.projects.post({ json: parseResult.data })
} else {
  // Handle validation errors
  console.error('Form validation failed:', parseResult.error)
}

// Disable automatic type coercion (optional)
const parseResultNoCoerce = client.api.projects.post.safeParseForm(formData, false)

The safeParseForm method:

  • Only available on endpoints with json input schemas (not available for streams or WebSockets)
  • Supports nested objects using dot notation (object.field)
  • Supports arrays of objects using indexed notation (array[0].field)
  • Supports arrays of primitives with repeated field names
  • Automatically coerces string form values to appropriate types (unless autoCoerce is false)
  • Returns a result object with success boolean and either data or error
  • Provides full TypeScript type safety for the parsed data
  • Uses the same validation schema as the endpoint's JSON input
  • Handles optional fields, nullable fields, and default values

Error Handling

MockDash provides structured error types for comprehensive error handling:

import { isApiError, isNetworkError, isValidationError } from 'mock-dash'

const response = await client.api.users.id('123').get()

if (response.error) {
  if (isApiError(response.error)) {
    // HTTP error (4xx, 5xx)
    console.error(`API Error ${response.error.status}:`, response.error.message)
  } else if (isNetworkError(response.error)) {
    // Network connectivity issues
    console.error('Network Error:', response.error.message)
  } else if (isValidationError(response.error)) {
    // Schema validation failures
    console.error('Validation Error:', response.error.getFieldErrors())
  }
} else {
  // Success - response.data is fully typed
  console.log('User:', response.data)
}

Type Inference

The API client provides a powerful infer property that allows you to extract TypeScript types from your API schema without making actual API calls. This is especially useful for typing variables and function parameters in your application:

Type Inference Patterns:

// HTTP endpoint types
client.infer.path.to.endpoint.method.response     // Response body type
client.infer.path.to.endpoint.method.json         // JSON input type
client.infer.path.to.endpoint.method.query        // Query parameters type
client.infer.path.to.endpoint.method.form         // Form data type
client.infer.path.to.endpoint.method.params       // Path parameters type
client.infer.path.to.endpoint.method.parseError   // Validation error from safeParseForm

// Streaming endpoint types
client.infer.path.to.stream.$stream.response       // Stream item type

// SSE endpoint types
client.infer.path.to.sse.$stream.response.eventName  // Specific event type

// WebSocket endpoint types
client.infer.path.to.ws.$ws.serverToClient         // Server messages union type
client.infer.path.to.ws.$ws.clientToServer         // Client messages union type

The infer property follows the same path structure as your API calls but provides compile-time type information instead of runtime functionality. This enables:

  • Type-safe function signatures using inferred types as parameters and return types
  • Consistent data structures across your application
  • IntelliSense support in your IDE with full autocomplete
  • Compile-time validation of data shapes and structures
  • Refactoring safety when API schemas change

Interceptors

Add global request/response interceptors for authentication, logging, etc.:

// Request interceptor for authentication
client.interceptors.request.use((context, options) => ({
  ...options,
  headers: {
    ...options.headers,
    Authorization: `Bearer ${getAuthToken()}`,
  },
}))

// Response interceptor for logging
client.interceptors.response.use((context, response) => {
  console.log(`${context.method} ${context.url} - ${response.status}`)
  return response
})

// Local interceptors for specific requests
await client.api.users.get({
  transformRequest: (context, options) => ({
    ...options,
    headers: { ...options.headers, 'X-Custom': 'value' },
  }),
  transformResponse: (context, response) => {
    // Process response
    return response
  },
})

Create Mock Server

Define Mock Responses

Create realistic mock responses for development and testing:

// Static mock responses
apiSchema.getUser.defineMock({
  id: '123',
  name: 'John Doe',
  email: '[email protected]',
})

// Dynamic mock responses with access to request context
apiSchema.getUser.defineMock((ctx) => ({
  id: ctx.inputs.param.id,
  name: 'Dynamic User',
  email: `user${ctx.inputs.param.id}@example.com`,
}))

// Mock with query parameters
apiSchema.searchUsers.defineMock((ctx) => ({
  users: [
    {
      id: '1',
      name: `Search result for: ${ctx.inputs.query.q}`,
      email: '[email protected]',
    },
  ],
  total: 1,
}))

// Mock with JSON body
apiSchema.createUser.defineMock((ctx) => ({
  id: Math.random().toString(36),
  name: ctx.inputs.json.name,
  email: ctx.inputs.json.email,
  createdAt: new Date().toISOString(),
}))

// Async mock functions
apiSchema.getUser.defineMock(async (ctx) => {
  // Simulate database lookup
  await new Promise(resolve => setTimeout(resolve, 100))
  return {
    id: ctx.inputs.param.id,
    name: 'Async User',
    email: '[email protected]',
  }
})

// Access to Hono context for advanced scenarios
apiSchema.getUser.defineMock((ctx) => ({
  id: ctx.inputs.param.id,
  name: 'User',
  customHeader: ctx.honoContext.req.header('X-Custom') || 'none',
}))

Start Server

Create and configure your mock server:

import { createMockServer } from 'mock-dash'

// Basic server
const app = createMockServer(apiSchema)

// Server with custom options
const app = createMockServer(apiSchema, {
  // Base path for all endpoints
  base: '/api/v1',
  
  // Custom fetch function for network requests
  fetch: customFetch,
  
  // Automatic mock generation from Zod schemas
  zodToMock: (schema) => {
    // Custom logic to generate mock data from Zod schema
    if (schema instanceof z.ZodString) return 'mock-string'
    if (schema instanceof z.ZodNumber) return 42
    // ... handle other types
  },
  
  // Custom middleware
  addMiddleware: (app) => {
    app.use('*', async (c, next) => {
      // Add CORS headers
      c.header('Access-Control-Allow-Origin', '*')
      await next()
    })
  },
})

// Start the server (if using in Node.js)
import { serve } from '@hono/node-server'
serve({ fetch: app.fetch, port: 3000 })

WebSocket Support

For WebSocket endpoints, provide the upgradeWebSocket function (see hono):

import { createMockServer } from 'mock-dash'

// Define WebSocket mocks
apiSchema.chat.defineMock((ctx) => ({
  onOpen: () => {
    console.log(`User joined room ${ctx.inputs.param.roomId}`)
  },
  onMessage: (ws, message) => {
    // Echo messages back to all clients
    ws.send({
      type: 'message',
      text: message.text,
      user: 'mock-user',
    })
  },
  onClose: () => {
    console.log('User left')
  },
}))

const app = createMockServer(apiSchema, {
  // Provide WebSocket upgrade function (depends on your runtime)
  upgradeWebSocket: (handler) => (c) => {
    // Implementation depends on your WebSocket library
    // This is just an example structure
    return c.upgradeWebSocket(handler)
  },
})

Utilities

MockDash includes several utility functions for common tasks:

import { MockError } from 'mock-dash'

// Throw specific HTTP errors from mock functions
apiSchema.getUser.defineMock((ctx) => {
  if (ctx.inputs.param.id === 'not-found') {
    throw new MockError('User not found', 404)
  }
  
  return { id: ctx.inputs.param.id, name: 'Found User' }
})

CLI Tool

MockDash provides a CLI tool to generate schemas from OpenAPI specifications.

Generate specs from OpenAPI

Convert existing OpenAPI specifications to MockDash schemas:

# Generate from OpenAPI JSON
npx mock-dash generate ./api-spec.json --out ./src/api-schema.ts

# Generate from OpenAPI YAML  
npx mock-dash generate ./api-spec.yaml --out ./src/api-schema.ts

# Strip prefixes and use aliases for API versioning
npx mock-dash generate ./api-spec.json \
  --out ./src/api-schema.ts \
  --prefix "/api/v1,/api/v2"

# Make all properties required by default
npx mock-dash generate ./api-spec.json \
  --out ./src/api-schema.ts \
  --properties-required-by-default

# Short form options
npx mock-dash generate ./api-spec.json -o ./schema.ts -p "/api/v1" -prbd

The generated file will export:

// Component schemas
export const userModel = z.object({ /* ... */ })
export const productModel = z.object({ /* ... */ })

// Endpoint definitions
export const getUsersId = defineGet('/users/:id', {
  response: userModel,
})

export const postUsers = definePost('/users', {
  input: { json: userModel },
  response: userModel,
})

// With prefix aliases
export const getApiUsers = defineGet('/{api}/users', {
  response: z.array(userModel),
  options: { alias: { api: '/api/v1' } },
})

Contributing

We welcome contributions! Please see our Contributing Guide for details.

Development Setup

git clone https://github.com/MrPorky/mock-dash.git
cd mock-dash
pnpm install
pnpm test

License

MIT © MrPorky