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

@ahmed109/apibridge-contracts

v1.0.2

Published

Shared framework-agnostic API contracts with Zod

Readme

@ahmed109/apibridge-contracts

npm version License: MIT TypeScript Strict Zod

Framework-independent, type-safe REST API contracts powered by Zod.
The single source of truth for full-stack TypeScript applications in the ApiBridge ecosystem.


Overview

In traditional full-stack web applications, backend controllers and frontend API clients duplicate endpoint definitions, route paths, parameter parsing, and TypeScript interfaces. When backend endpoints change, frontend types frequently drift out of sync, leading to silent production runtime crashes.

@ahmed109/apibridge-contracts solves this by allowing you to define your REST API routes, path parameters, query parameters, request bodies, expected response bodies, and error formats once using pure TypeScript and Zod.

The exact same contract definition is then consumed by:


Installation

# npm
npm install @ahmed109/apibridge-contracts zod

# pnpm
pnpm add @ahmed109/apibridge-contracts zod

# yarn
yarn add @ahmed109/apibridge-contracts zod

# bun
bun add @ahmed109/apibridge-contracts zod

Quickstart

1. Define Route Contracts

Create a shared contracts file (e.g. src/contracts/tasks.ts):

import { z } from "zod";
import { defineRoute, standardErrors } from "@ahmed109/apibridge-contracts";

// 1. Define Entity Schemas
export const TaskSchema = z.object({
  id: z.string().min(1),
  title: z.string().min(1).max(100),
  description: z.string().default(""),
  status: z.enum(["todo", "in_progress", "done"]),
  priority: z.enum(["low", "medium", "high"]),
  assigneeId: z.string().nullable().default(null),
  createdAt: z.string().datetime(),
  updatedAt: z.string().datetime(),
});

export type Task = z.infer<typeof TaskSchema>;

// 2. Define Query and Mutation Schemas
export const TaskQuerySchema = z.object({
  status: z.enum(["todo", "in_progress", "done"]).optional(),
  priority: z.enum(["low", "medium", "high"]).optional(),
  search: z.string().trim().optional(),
  page: z.coerce.number().int().min(1).default(1),
  limit: z.coerce.number().int().min(1).max(100).default(10),
  sortBy: z.enum(["createdAt", "updatedAt", "priority"]).default("createdAt"),
  sortOrder: z.enum(["asc", "desc"]).default("desc"),
});

export const CreateTaskInputSchema = z.object({
  title: z.string().min(1, "Title is required").max(100),
  description: z.string().max(1000).optional().default(""),
  status: z.enum(["todo", "in_progress", "done"]).default("todo"),
  priority: z.enum(["low", "medium", "high"]).default("medium"),
  assigneeId: z.string().nullable().optional().default(null),
});

export const TaskListResponseSchema = z.object({
  items: z.array(TaskSchema),
  total: z.number().int().nonnegative(),
  page: z.number().int().positive(),
  limit: z.number().int().positive(),
  totalPages: z.number().int().nonnegative(),
});

// 3. Define Shared Route Contracts
export const taskContracts = {
  list: defineRoute({
    method: "get",
    path: "/api/tasks",
    summary: "List tasks",
    description: "Retrieve a paginated, filterable list of tasks.",
    tags: ["Tasks"],
    query: TaskQuerySchema,
    responses: {
      200: TaskListResponseSchema,
      400: standardErrors[400],
      500: standardErrors[500],
    },
  }),

  get: defineRoute({
    method: "get",
    path: "/api/tasks/:id",
    summary: "Get task by ID",
    tags: ["Tasks"],
    params: z.object({ id: z.string().min(1) }),
    responses: {
      200: TaskSchema,
      400: standardErrors[400],
      404: standardErrors[404],
      500: standardErrors[500],
    },
  }),

  create: defineRoute({
    method: "post",
    path: "/api/tasks",
    summary: "Create task",
    tags: ["Tasks"],
    body: CreateTaskInputSchema,
    responses: {
      201: TaskSchema,
      400: standardErrors[400],
      401: standardErrors[401],
      500: standardErrors[500],
    },
  }),

  delete: defineRoute({
    method: "delete",
    path: "/api/tasks/:id",
    summary: "Delete task",
    tags: ["Tasks"],
    params: z.object({ id: z.string().min(1) }),
    responses: {
      200: z.object({ success: z.boolean(), id: z.string() }),
      400: standardErrors[400],
      401: standardErrors[401],
      404: standardErrors[404],
    },
  }),
};

Type Inference Utilities

@ahmed109/apibridge-contracts exports TypeScript utilities to infer exact static types from route contracts without writing duplicate interfaces:

import type {
  InferRouteParams,
  InferRouteQuery,
  InferRouteBody,
  InferRouteResponse,
} from "@ahmed109/apibridge-contracts";
import type { taskContracts } from "./contracts/tasks";

// Inferred: { id: string }
type TaskParams = InferRouteParams<typeof taskContracts.get>;

// Inferred: { status?: "todo" | "in_progress" | "done"; page: number; limit: number; ... }
type TaskQuery = InferRouteQuery<typeof taskContracts.list>;

// Inferred: { title: string; description?: string; status?: ...; priority?: ... }
type CreateTaskBody = InferRouteBody<typeof taskContracts.create>;

// Inferred: { items: Task[]; total: number; page: number; ... }
type ListResponse = InferRouteResponse<typeof taskContracts.list, 200>;

Standard Error Envelopes

ApiBridge enforces structured, predictable JSON error payloads across all endpoints:

import {
  standardErrors,
  ValidationErrorSchema,
  UnauthorizedErrorSchema,
  NotFoundErrorSchema,
  InternalServerErrorSchema,
  type ValidationError,
  type UnauthorizedError,
  type NotFoundError,
  type InternalServerError,
} from "@ahmed109/apibridge-contracts";

Supported Status Envelopes:

| HTTP Status | Error Code | Schema | Description | | :---------- | :---------------------- | :-------------------------- | :------------------------------------------------ | | 400 | VALIDATION_ERROR | ValidationErrorSchema | Contains issues: Array<{ path, message, code }> | | 401 | UNAUTHORIZED | UnauthorizedErrorSchema | Authentication credentials missing or invalid | | 404 | NOT_FOUND | NotFoundErrorSchema | Targeted entity or route was not found | | 500 | INTERNAL_SERVER_ERROR | InternalServerErrorSchema | Unhandled exceptions or internal server errors |


Related Packages


License

MIT © Ahmed Rivera & ApiBridge Contributors.