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

api-spec-builder

v0.1.1

Published

api spec builder for microservices repositories

Readme

api-spec-builder

A TypeScript-first library for building type-safe API specifications. Define your API endpoints once and get fully typed URL builders with automatic path parameter extraction and query string support.

Features

  • Full TypeScript support - Path parameters are automatically extracted from URL templates
  • Type-safe URL building - Generate URLs with compile-time parameter validation
  • All HTTP methods - GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD
  • Query string builder - Built-in support for query parameters including arrays
  • Base parameters - Bind common path parameters (like tenantId) once and reuse
  • Duplicate detection - Prevents duplicate controller paths and endpoints
  • Zero runtime dependencies

Installation

npm install api-spec-builder
yarn add api-spec-builder
pnpm add api-spec-builder

Quick Start

import { ApiSpecBuilder } from 'api-spec-builder';

const api = new ApiSpecBuilder();

// Define a controller with a base path
const usersController = api.controller('/api/users');

// Define endpoints
const usersSpec = usersController.define({
  list: usersController.GET('/'),
  getById: usersController.GET('/:id'),
  create: usersController.POST('/'),
  update: usersController.PUT('/:id'),
  delete: usersController.DELETE('/:id'),
});

// Build URLs - TypeScript knows exactly which parameters are required!
usersSpec.endpoints.list.getUrl({});
// => '/api/users'

usersSpec.endpoints.getById.getUrl({ id: 42 });
// => '/api/users/42'

usersSpec.endpoints.getById.getUrl({ id: 42 }, { include: 'posts' });
// => '/api/users/42?include=posts'

Usage

Creating Controllers

Controllers group related endpoints under a common base path:

const api = new ApiSpecBuilder();

// Simple controller
const postsController = api.controller('/posts');

// Controller with path parameters
const tenantController = api.controller('/tenants/:tenantId');

Defining Endpoints

Use HTTP method helpers to create endpoints:

const controller = api.controller('/api/v1');

const spec = controller.define({
  // GET endpoints (no body)
  getUsers: controller.GET('/users'),
  getUser: controller.GET('/users/:id'),
  
  // POST, PUT, PATCH, DELETE (with body)
  createUser: controller.POST('/users'),
  updateUser: controller.PUT('/users/:id'),
  patchUser: controller.PATCH('/users/:id'),
  deleteUser: controller.DELETE('/users/:id'),
  
  // OPTIONS, HEAD (no body)
  options: controller.OPTIONS('/users'),
  head: controller.HEAD('/users/:id'),
});

Adding Type Information

Use the .type() method to specify response, body, and query types:

interface User {
  id: number;
  name: string;
  email: string;
}

interface CreateUserBody {
  name: string;
  email: string;
}

interface ListUsersQuery {
  page?: number;
  limit?: number;
  search?: string;
}

const spec = controller.define({
  // GET<ResponseType, QueryType>
  listUsers: controller.GET('/users').type<User[], ListUsersQuery>(),
  getUser: controller.GET('/users/:id').type<User>(),
  
  // POST<ResponseType, BodyType, QueryType>
  createUser: controller.POST('/users').type<User, CreateUserBody>(),
  updateUser: controller.PUT('/users/:id').type<User, Partial<User>>(),
});

// Now TypeScript knows the types:
spec.endpoints.listUsers.__responseType // -> User[]
spec.endpoints.listUsers.__queryType // -> ListUsersQuery
spec.endpoints.createUser.__bodyType // -> CreateUserBody

Building URLs

Every endpoint has a getUrl method that builds URLs with full type safety:

const spec = controller.define({
  getPost: controller.GET('/posts/:postId/comments/:commentId'),
});

// TypeScript requires both parameters
spec.endpoints.getPost.getUrl({ postId: 1, commentId: 5 });
// => '/api/v1/posts/1/comments/5'

// With query parameters
spec.endpoints.getPost.getUrl(
  { postId: 1, commentId: 5 },
  { highlight: true, format: 'html' }
);
// => '/api/v1/posts/1/comments/5?highlight=true&format=html'

Using Base Parameters

When your controller has path parameters that are constant across requests, use withBaseParams:

const api = new ApiSpecBuilder();
const controller = api.controller('/tenants/:tenantId/users');

const spec = controller.define({
  list: controller.GET('/'),
  getById: controller.GET('/:id'),
});

// Bind the tenantId once
const tenantSpec = spec.withBaseParams({ tenantId: 'acme-corp' });

// Now you don't need to pass tenantId every time
tenantSpec.endpoints.list.getUrl({});
// => '/tenants/acme-corp/users'

tenantSpec.endpoints.getById.getUrl({ id: 42 });
// => '/tenants/acme-corp/users/42'

Accessing Endpoint Metadata

Each endpoint exposes useful metadata:

const spec = controller.define({
  getUser: controller.GET('/users/:id'),
});

const endpoint = spec.endpoints.getUser;

endpoint.method;    // 'GET'
endpoint.path;      // '/users/:id'
endpoint.fullPath;  // '/api/v1/users/:id'

// Get parameter name (useful for dynamic access)
endpoint.getParam('id'); // 'id'

Accessing Paths

The spec object provides a convenient paths property:

const spec = controller.define({
  list: controller.GET('/users'),
  getById: controller.GET('/users/:id'),
});

spec.paths.list;    // '/users'
spec.paths.getById; // '/users/:id'
spec.controllerPath; // '/api/v1'

Error Handling

The library throws errors for common mistakes:

// Duplicate controller paths
const api = new ApiSpecBuilder();
api.controller('/users');
api.controller('/users'); // Error: Controller path duplicated: /users

// Duplicate endpoints (same method + path)
const controller = api.controller('/posts');
controller.define({
  first: controller.GET('/test'),
  second: controller.GET('/test'), // Error: Duplicate endpoint detected
});

API Reference

ApiSpecBuilder

Main entry point for creating API specifications.

controller(basePath: string)

Creates a new controller factory with the given base path.

Controller Factory

Returned by controller(), provides methods to create endpoints.

HTTP Methods

  • GET<R, Q>(path) - Create GET endpoint (Response, Query types)
  • POST<R, B, Q>(path) - Create POST endpoint (Response, Body, Query types)
  • PUT<R, B, Q>(path) - Create PUT endpoint
  • PATCH<R, B, Q>(path) - Create PATCH endpoint
  • DELETE<R, B, Q>(path) - Create DELETE endpoint
  • OPTIONS<R, Q>(path) - Create OPTIONS endpoint
  • HEAD<R, Q>(path) - Create HEAD endpoint

define(spec)

Finalizes the controller definition and returns an ApiSpec object.

EndpointDescriptor

Represents a single endpoint.

| Property | Type | Description | |----------|------|-------------| | method | MethodType | HTTP method | | path | string | Endpoint path template | | fullPath | string | Complete path including controller base | | getUrl(params, query?) | function | Build URL with parameters | | getParam(key) | function | Get parameter name | | __responseType | R | Response type (for type inference) | | __bodyType | B | Body type (for type inference) | | __queryType | Q | Query type (for type inference) |

ApiSpec

Returned by define(), contains the complete API specification.

| Property | Type | Description | |----------|------|-------------| | controllerPath | string | Base path of the controller | | paths | object | Map of endpoint names to path templates | | endpoints | object | Map of endpoint names to descriptors | | withBaseParams(params) | function | Create spec with bound parameters |

License

MIT