better-swagger-types
v0.3.0
Published
Prisma-like TypeScript type generation for Swagger/OpenAPI schemas
Maintainers
Readme
better-swagger-types
Prisma-style TypeScript type generation for Swagger / OpenAPI JSON schemas.
better-swagger-types generates stable, ergonomic TypeScript output into lib/generated without forcing an HTTP client. The generated code is types-first by default and works well with fetch, axios, swr, and React Query.
Features
- Prisma-like
initandgenerateworkflow - Multiple schemas with friendly names and namespaces
- Local file, directory, glob, and remote URL schema sources
- OpenAPI v3 and Swagger v2 support
- Deterministic output with stable operation naming
- Optional simple path aliases such as
HereTooSomethingHereV1API - Generated
paths,schemas,simple,operations,endpoints, andmetafiles - Optional watch mode and clean command
- No runtime dependency required in generated files
Installation
bun add -d better-swagger-typesnpm also works:
npm install --save-dev better-swagger-typesQuick Start
Initialize the config file:
bunx better-swagger-types --initGenerate types:
bunx better-swagger-types generateDefault command behavior is generate, so this is equivalent:
bunx better-swagger-typesCommands
better-swagger-types --init
better-swagger-types init [--config <path>] [--force]
better-swagger-types generate [--config <path>] [--no-cache] [--verbose]
better-swagger-types dev [--config <path>] [--no-cache] [--verbose]
better-swagger-types clean [--config <path>] [--verbose]Config
better-swagger-types creates api.swagger-types.ts in your project root.
import { defineConfig } from 'better-swagger-types/config';
export default defineConfig({
output: 'lib/generated',
prismaStyleNodeModulesOutput: false,
schemas: [
{
name: 'core',
source: './schemas/core.json',
headers: {
Authorization: `Bearer ${process.env.API_TOKEN ?? ''}`
},
format: 'auto',
namespace: 'Core'
}
],
generator: {
emitOperations: true,
emitSchemas: true,
emitSimpleAliases: false,
resolveRefs: true,
naming: 'stable'
}
});Config Options
output: relative or absolute output directory for generated filesprismaStyleNodeModulesOutput: writes a marker manifest tonode_modules/.better-swagger-typesschemas: one or more schema source definitionsschemas[].name: friendly schema nameschemas[].source: file path, directory, glob, orhttp(s)URLschemas[].headers: headers for remote fetchesschemas[].format:auto,openapi3, orswagger2schemas[].namespace: optional TS namespace overridegenerator.emitOperations: emitoperations.tsandendpoints.tsgenerator.emitSchemas: emitschemas.tsgenerator.emitSimpleAliases: emit path-derived aliases insimple.tsgenerator.resolveRefs: validate and bundle refs when possiblegenerator.naming: currently supportsstable
Generated Output
lib/generated/
index.ts
core/
index.ts
paths.ts
schemas.ts
simple.ts # when emitSimpleAliases is true
operations.ts
endpoints.ts
meta.tsGenerated files include:
paths.ts: OpenAPI-stylepathsmapping plus canonical component typesschemas.ts: schema component aliases such asUserandCreateUserInputsimple.ts: optional path-derived aliases such asUsersByIdAPIandHereTooSomethingHereV1APIoperations.ts: operation-level ergonomic mapping keyed by stable namesendpoints.ts: endpoint mapping keyed by"METHOD /path"meta.ts: deterministic metadata including schema hash
Simple Path Aliases
Enable generator.emitSimpleAliases to emit a simple.ts file with path item aliases derived from route paths.
import type { Simple } from './lib/generated';
type UsersByIdAPI = Simple.UsersByIdAPI;
type HereTooSomethingHereV1API = Simple.HereTooSomethingHereV1API;Usage Examples
fetch
import type { Core } from './lib/generated';
type GetUserEndpoint = Core.Endpoints['GET /users/{id}'];
type GetUserParams = Core.ParamsFor<GetUserEndpoint>;
type GetUserResponse = Core.EndpointSuccessResponseFor<GetUserEndpoint>;
async function getUser(params: GetUserParams): Promise<GetUserResponse> {
const response = await fetch(`/users/${params.path.id}`);
return response.json() as Promise<GetUserResponse>;
}axios
import axios from 'axios';
import type { Core } from './lib/generated';
type CreateUser = Core.Endpoints['POST /users'];
async function createUser(body: Core.RequestBodyFor<CreateUser>) {
return axios.post<Core.EndpointSuccessResponseFor<CreateUser>>('/users', body);
}SWR
import useSWR from 'swr';
import type { Core } from './lib/generated';
type GetUser = Core.Endpoints['GET /users/{id}'];
type GetUserResponse = Core.EndpointSuccessResponseFor<GetUser>;
const fetcher = async (url: string): Promise<GetUserResponse> => {
const response = await fetch(url);
if (!response.ok) {
throw new Error('Request failed');
}
return response.json() as Promise<GetUserResponse>;
};
export function useUser(id: string) {
return useSWR(`/users/${id}`, fetcher);
}React Query
import { useQuery } from '@tanstack/react-query';
import type { Core } from './lib/generated';
type GetUser = Core.Endpoints['GET /users/{id}'];
export function useUser(id: string) {
return useQuery<Core.EndpointSuccessResponseFor<GetUser>>({
queryKey: ['user', id],
queryFn: async () => {
const response = await fetch(`/users/${id}`);
return response.json() as Promise<Core.EndpointSuccessResponseFor<GetUser>>;
}
});
}Schema Sources
Single file
schemas: [{ name: 'core', source: './schemas/core.json' }]Directory
A directory expands non-recursively to all *.json files. If multiple files are found, each one becomes its own generated schema namespace.
schemas: [{ name: 'services', source: './schemas', namespace: 'Services' }]Glob
schemas: [{ name: 'apis', source: './schemas/**/*.json' }]Remote URL
schemas: [
{
name: 'remote',
source: 'https://example.com/openapi.json',
headers: {
Authorization: `Bearer ${process.env.API_TOKEN ?? ''}`
}
}
]Remote schemas are cached in .better-swagger-types/cache/. If the server returns ETag or Last-Modified, subsequent runs use conditional requests automatically. Use --no-cache to bypass the cache.
Troubleshooting
Config missing
Run:
bunx better-swagger-types --initRemote URL fails
- confirm the URL returns JSON
- verify any auth headers in
schemas[].headers - rerun with
--verboseto see cache and resolution details
Invalid schema
better-swagger-types validates the document and surfaces parser errors with path context. Fix the schema and rerun generation.
Output path contains old files
Run:
bunx better-swagger-types cleanMissing operationId
This is fine. better-swagger-types falls back to a stable Method + Path name such as GetUsersById.
Development
bun run build
bun run typecheck
bun run testExample schemas live in examples/.
Release Automation
Automated npm publishing is configured through deploy.yml.
The workflow is designed for npm trusted publishing via GitHub Actions OIDC:
- publishes only from
v*git tags - requires the GitHub Actions job to obtain an OIDC token
- uses the GitHub Environment named
release - runs
bun run release:checkbefore publishing - performs a packaged-artifact smoke test before
npm publish
One-time setup
- In GitHub, create an environment named
release. - In that environment, require at least one reviewer and disable self-review if you want a manual approval gate before publish.
- In npm package settings for
better-swagger-types, configure a trusted publisher for:- owner:
Sma-Das - repository:
better-swagger-types - workflow filename:
deploy.yml
- owner:
- In npm package settings, prefer tokenless publishing by disallowing classic automation tokens once trusted publishing is working.
Release flow
npm version patch
git push origin main --follow-tagsThat pushes a tag such as v0.1.4, triggers the deploy workflow, validates the packaged artifact, and publishes to npm if the release environment and npm trusted publisher both allow it.
