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.
- 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
bun add -d better-swagger-typesnpm also works:
npm install --save-dev better-swagger-typesInitialize 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-typesbetter-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]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'
}
});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
lib/generated/
index.ts
core/
index.ts
paths.ts
schemas.ts
simple.ts # when emitSimpleAliases is true
operations.ts
endpoints.ts
meta.ts
Generated 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
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;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>;
}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);
}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);
}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>>;
}
});
}schemas: [{ name: 'core', source: './schemas/core.json' }]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' }]schemas: [{ name: 'apis', source: './schemas/**/*.json' }]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.
Run:
bunx better-swagger-types --init- confirm the URL returns JSON
- verify any auth headers in
schemas[].headers - rerun with
--verboseto see cache and resolution details
better-swagger-types validates the document and surfaces parser errors with path context. Fix the schema and rerun generation.
Run:
bunx better-swagger-types cleanThis is fine. better-swagger-types falls back to a stable Method + Path name such as GetUsersById.
bun run build
bun run typecheck
bun run testExample schemas live in examples/.
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
- 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.
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.