api-spec-builder
v0.1.1
Published
api spec builder for microservices repositories
Maintainers
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-builderyarn add api-spec-builderpnpm add api-spec-builderQuick 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 // -> CreateUserBodyBuilding 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 endpointPATCH<R, B, Q>(path)- Create PATCH endpointDELETE<R, B, Q>(path)- Create DELETE endpointOPTIONS<R, Q>(path)- Create OPTIONS endpointHEAD<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
