Home
Softono

Apigen Ts

Open source MIT TypeScript
54
Stars
9
Forks
5
Issues
3
Watchers
2 months
Last Commit

 About Apigen Ts

Generate a fully typed TypeScript fetch client from OpenAPI/Swagger in one file. Supports OpenAPI 2.0, 3.0, and 3.1.

Platforms

Web Self-hosted

Languages

TypeScript

Need Help Installing Apigen Ts?

We provide expert installation service for this software. Our team will install, configure, and secure Apigen Ts on your server. plans start at just $30.

apigen-ts

version size downloads license donate

apigen-ts logo

Turn your OpenAPI spec into a typed TypeScript client with one command.

  • One file. Outputs a single api-client.ts — no scattered modules, no runtime deps in generated code.
  • Fully typed. Every method returns the exact response type from your schema. No casting, no any.
  • Pure Node.js. No Java, no Docker. Works with npx in any project.
  • Fetch-based. Uses native fetch. Override it with your own function for auth, retries, or logging.
  • All OpenAPI versions. Supports v2 (Swagger), v3, and v3.1 — auto-upgrades v2 on the fly.
  • Extras built in. Automatic date parsing, string literal unions instead of enums, Prettier formatting.
  • Filterable. Include or exclude endpoints by path regex or tag — essential for large schemas.

Unlike openapi-typescript, it generates a ready-to-call client — not just types. Unlike openapi-generator-cli, it's pure Node.js with zero Java dependency. Unlike openapi-typescript-codegen, it outputs a single file.

Install

npm i -D apigen-ts

Usage

1. Generate

# From a local file
npx apigen-ts ./openapi.json ./api-client.ts

# From a URL
npx apigen-ts https://petstore3.swagger.io/api/v3/openapi.json ./api-client.ts

# From a protected URL
npx apigen-ts https://secret-api.example.com ./api-client.ts -H "x-api-key: secret-key"

Run npx apigen-ts --help for all options. See generated examples.

2. Import

import { ApiClient } from "./api-client"

const api = new ApiClient({
  baseUrl: "https://example.com/api",
  headers: { Authorization: "secret-token" },
})

3. Use

// GET /pet/{petId}
await api.pet.getPetById(1) // → Pet

// GET /pet/findByStatus?status=sold
await api.pet.findPetsByStatus({ status: "sold" }) // → Pet[]

// PUT /user/{username} — second arg is typed request body
await api.user.updateUser("username", { firstName: "John" })

Advanced

Login flow

const { token } = await api.auth.login({ username, password })
api.Config.headers = { Authorization: token }

await api.protectedRoute.get() // authenticated

Automatic date parsing

npx apigen-ts ./openapi.json ./api-client.ts --parse-dates
const pet = await api.pet.getPetById(1)
const createdAt: Date = pet.createdAt // parsed from format=date-time string

String unions instead of enums

Pass --inline-enums to generate string literal unions — useful for Node.js type stripping:

npx apigen-ts ./openapi.json ./api-client.ts --inline-enums
// Generated:
type MyEnum = "OptionA" | "OptionB"

// Instead of:
enum MyEnum {
  OptionA = "OptionA",
  OptionB = "OptionB",
}

Filter by path

Include only the endpoints you need — useful with large schemas (e.g. Cloudflare's 8 MB monolith):

npx apigen-ts ./openapi.json ./api-client.ts --filter-paths '^/accounts'

Filter by tag

# include only endpoints tagged "pets" or "store"
npx apigen-ts ./openapi.json ./api-client.ts --include-tags pets,store

# exclude endpoints tagged "internal"
npx apigen-ts ./openapi.json ./api-client.ts --exclude-tags internal

When both flags are set, --exclude-tags wins.

Fetch options

Pass --fetch-options to add an optional last argument to every generated method, accepting any RequestInit field:

npx apigen-ts ./openapi.json ./api-client.ts --fetch-options
const controller = new AbortController()
await api.pet.getPetById(1, { signal: controller.signal })

// cancel the request
controller.abort()

// or disable automatic redirects for a single request
await api.pet.getPetById(1, { redirect: "manual" })

Error handling

Non-2xx responses throw — the caught value is the parsed response body:

try {
  await api.pet.getPetById(404)
} catch (e) {
  console.log(e) // awaited response.json()
}

Override ParseError to control the shape:

class MyClient extends ApiClient {
  async ParseError(rep: Response) {
    return { code: "API_ERROR" }
  }
}

Base URL resolving

By default uses the URL constructor: new URL(path, baseUrl). Notable behavior:

  • new URL("/v2/cats", "https://example.com/v1/")https://example.com/v2/cats
  • new URL("v2/cats", "https://example.com/v1/")https://example.com/v1/v2/cats

Override PrepareFetchUrl to change this (see #2):

class MyClient extends ApiClient {
  PrepareFetchUrl(path: string) {
    return new URL(`${this.Config.baseUrl}/${path}`.replace(/\/{2,}/g, "/"))
  }
}

const api = new MyClient({ baseUrl: "https://example.com/v1" })
await api.pet.getPetById(1) // → https://example.com/v1/pet/1

Node.js API

import { apigen } from "apigen-ts"

await apigen({
  source: "https://petstore3.swagger.io/api/v3/openapi.json",
  output: "./api-client.ts",
  // optional:
  name: "MyApiClient", // default: "ApiClient"
  parseDates: true, // default: false
  inlineEnums: false, // default: false
  fetchOptions: true, // default: false
  filterPaths: /^\/pets/, // only include paths matching regex
  includeTags: ["pets", "store"], // only include these tags
  excludeTags: ["internal"], // exclude these tags (wins over includeTags)
  headers: { "x-api-key": "secret-key" },
  resolveName(ctx, op, proposal) {
    // proposal is [namespace, methodName]
    if (proposal[0] === "users") return // use default

    const [a, b] = op.name.split("/").slice(3, 5) // /api/v1/store/items/search
    return [a, `${op.method}_${b}`] // → api.store.get_items()
  },
})

Usage with FastAPI

By default, FastAPI generates verbose operationIds. Fix with a custom resolver:

from fastapi import FastAPI
from fastapi.routing import APIRoute

app = FastAPI()

# add your routes here

def update_operation_ids(app: FastAPI) -> None:
    for route in app.routes:
        if isinstance(route, APIRoute):
            ns = route.tags[0] if route.tags else "general"
            route.operation_id = f"{ns}_{route.name}".lower()

# call after all routes are added
update_operation_ids(app)

Alternatives

Package Issue
openapi-typescript-codegen No single-file output (#1263)
openapi-typescript Low-level types only — no callable client, no named type exports
openapi-generator-cli Wraps a Java library
swagger-typescript-api Complex config, breaking API changes between versions

Development