If you work with REST APIs in TypeScript, you have probably been here before: you open the Swagger docs in one tab, your editor in another, and you start writing fetch wrappers by hand. You copy-paste field names. You guess at nullable fields. You forget that one enum changed last sprint.

It is tedious, error-prone, and completely unnecessary.

Enter swagger-typescript-api

swagger-typescript-api is a code generator that takes an OpenAPI (Swagger) spec — JSON or YAML, v2 or v3 — and produces a fully typed TypeScript API client. One command, and you get types, enums, and a ready-to-use HTTP client with zero hand-written boilerplate.

Install it:

npm i -D swagger-typescript-api

Point it at a spec:

npx swagger-typescript-api -p ./swagger.json -o ./src/api --name Api.ts

That is it. You now have a single file with every endpoint typed, every request body and response shape known and type-checked at compile time.

What you get

The generated output gives you three things:

  1. TypeScript interfaces for every schema in your spec — request bodies, response objects, enums, the lot.
  2. A typed HTTP client class with methods for every endpoint, grouped by tag.
  3. Full generic support so you can swap the underlying HTTP layer if you need to.

Here is a quick taste. Say your API has a GET /users/{id} endpoint that returns a User object. After generation, you use it like this:

import { Api } from "./api/Api";

const api = new Api({
    baseUrl: "https://api.example.com",
});

// Fully typed — response is User, params are type-checked
const { data } = await api.users.getUser(userId);
console.log(data.email); // autocomplete works, typos caught at compile time

No manual type definitions. No as any. No hoping the backend hasn’t changed the shape under you.

Why I prefer this over alternatives

There are other codegen tools out there — openapi-typescript-codegen, openapi-generator with its TypeScript targets, orval, and more. I keep coming back to swagger-typescript-api for a few reasons:

  • Single-file output by default. One file, easy to commit, easy to diff when the API changes.
  • Readable generated code. You can actually open the output and understand it. It is not a wall of abstract factory patterns.
  • ETA templates. If the defaults do not fit, you can customize the generated code with ETA templates. Need to add a custom header to every request? Add an auth interceptor? Wrap responses in a Result type? You can do that without forking the tool.
  • Axios or Fetch. It supports both HTTP clients out of the box via the --axios flag.

Fitting it into your workflow

I typically add a generate:api script to package.json:

{
    "scripts": {
        "generate:api": "swagger-typescript-api -p https://api.example.com/swagger.json -o ./src/api --name Api.ts --axios"
    }
}

Then I run npm run generate:api whenever the backend publishes a new spec. The diff in the generated file tells me exactly what changed — new endpoints, renamed fields, removed parameters. It turns API changes from a “surprise at runtime” into a “catch at code review” situation.

For teams, you can go further and run generation in CI. If the generated output differs from what is committed, fail the build. That way nobody forgets to regenerate after the spec updates.

A practical example

Let us say you are working with a pet store API (the classic Swagger example). You can generate a client directly from a remote spec:

npx swagger-typescript-api \
    -p https://petstore3.swagger.io/api/v3/openapi.json \
    -o ./src/api \
    --name PetStore.ts

Now you have typed methods for every pet store endpoint:

import { Api } from "./api/PetStore";

const petStore = new Api();

// TypeScript knows the exact shape of Pet
const { data: pet } = await petStore.pet.getPetById(1);
console.log(pet.name, pet.status);

// This would be a compile error — no such field
// console.log(pet.nonExistent);

The compiler has your back. Refactoring becomes straightforward because your editor can find every usage of every field.

Tips from real usage

A few things I have learned from using this across multiple projects:

Pin your spec version. Do not point at a live endpoint that changes without warning. Either download the spec file and commit it, or point at a versioned URL. You want regeneration to be deliberate.

Review the generated diff. Treat the generated file like any other code in review. A renamed field in the diff means a renamed field you need to update everywhere.

Use the --unwrap-response-data flag. By default the client returns the full Axios/Fetch response. With this flag, methods return just the data. Cleaner calling code, fewer .data chains.

Customize with templates when needed. The ETA template system is powerful. I have used it to add automatic retry logic, inject auth tokens, and add request logging — all at the generated code level so every endpoint gets the same treatment.

The bottom line

Hand-writing API clients is grunt work that adds risk. Every manually typed interface is a potential drift point between your frontend and your backend. swagger-typescript-api eliminates that entire category of bugs.

If your backend already publishes an OpenAPI spec — and it should — generating your client from it is one of the highest-value, lowest-effort improvements you can make to your TypeScript codebase.

Give it a try: github.com/acacode/swagger-typescript-api