Auto-Generating Type-Safe API Clients from OpenAPI Specifications

Team 4 min read

#webdev

#tutorial

#api

#typescript

#codegen

#openapi

Introduction

OpenAPI specifications describe RESTful APIs in a machine-readable way. Auto-generating type-safe API clients from these specs can dramatically reduce boilerplate, improve type safety, and keep client code in sync with server behavior. In this post, we’ll walk through why this approach makes sense, how code generation works, and a practical example you can adapt to your project.

Why generate type-safe API clients from OpenAPI

  • Type safety across the boundary: API responses map to concrete TypeScript types, reducing runtime errors.
  • Faster iteration: clients stay in sync with the API as specs evolve.
  • Consistent error handling: generated clients can provide standardized error models.
  • Developer experience: autocomplete, docs, and in-editor validation improve speed and confidence.

How OpenAPI-driven code generation works

  • Start with an OpenAPI 3.x specification that describes endpoints, parameters, request/response bodies, and authentication.
  • Choose a code generator that targets your language and preferred fetch/HTTP library (e.g., TypeScript with fetch or axios).
  • Use the generator to emit a client library, typically including:
    • API classes with methods corresponding to operations
    • Type definitions for models
    • Configuration utilities (base URL, headers, auth)
  • Integrate the generated client into your codebase and regenerate when the spec changes.

Tools you’ll likely use

  • OpenAPI Generator (openapi-generator-cli): broad language support and options for TypeScript clients.
  • Autorest or NSwag: alternative options for .NET and TypeScript ecosystems.
  • Your package manager and build tool (npm, pnpm, Yarn) to wire up generation scripts.

A practical example: generating a TypeScript client

  1. Create a minimal OpenAPI spec (save as openapi.yaml)
openapi: 3.0.0
info:
  title: Pet API
  version: 1.0.0
paths:
  /pets:
    get:
      summary: List pets
      responses:
        '200':
          description: A list of pets
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Pet'
  /pets/{petId}:
    get:
      summary: Get a pet by ID
      parameters:
        - name: petId
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: A pet
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pet'
components:
  schemas:
    Pet:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        status:
          type: string
  1. Generate the TypeScript client
# Install the OpenAPI Generator CLI
npm install -g @openapitools/openapi-generator-cli

# Generate a TypeScript fetch client
openapi-generator-cli generate -i openapi.yaml -g typescript-fetch -o api-client
  1. Use the generated client in your TypeScript code
import { PetApi, Configuration } from './api-client';

const api = new PetApi(new Configuration({ basePath: 'https://api.example.com' }));

async function listPets() {
  const pets = await api.getPets();
  console.log(pets);
}

async function getPet(id: number) {
  const pet = await api.getPetById(id);
  console.log(pet);
}

listPets();
getPet(1);

Notes:

  • The exact class/method names depend on your spec and the generator’s conventions (e.g., PetApi, getPets, getPetById).
  • You can customize generation with properties like supportsES6, modelPropertyNaming, and additional type mappings.
  • Consider using a generated Configuration for base URL, headers (e.g., Authorization), and interceptors if you’re integrating with an existing HTTP layer.

Best practices for productive workflows

  • Keep the OpenAPI spec your single source of truth and automate regeneration as part of your CI/CD pipeline.
  • Seed a minimal, safe subset of endpoints in the initial spec to validate the workflow before expanding.
  • Use a dedicated build script in package.json (e.g., npm run generate) to reduce manual steps.
  • Consider tailoring templates for your project’s conventions (naming, error shapes, pagination).
  • Validate generated types with tests that cover common API payload shapes and error cases.
  • Manage versioning: when the API changes, update the spec version and regenerate the client, then run your tests to catch breaking changes early.
  • Security and authentication: include practice-ready mechanisms (e.g., Bearer tokens, OAuth) in the generated client configuration.

Potential pitfalls and how to mitigate

  • Spec accuracy matters: a phantom or incomplete OpenAPI spec can generate misleading types. Invest in spec reviews and contract testing.
  • Over-reliance on generation: generated clients are a solid baseline, but you may still need wrapper logic for retries, caching, or custom error handling.
  • Customization drift: if you heavily customize templates, remember to re-apply changes after re-generation. Use templates and versioned config to manage this.
  • Performance and bundle size: large clients can bloat your app; selectively generate only what you need and consider code-splitting.

Conclusion

Auto-generating type-safe API clients from OpenAPI specifications helps align frontend or client code with server behavior, reduces boilerplate, and improves developer confidence. With the right spec quality, generator tooling, and integration practices, you can achieve fast iteration cycles while keeping strong type safety across your API layer.

Further reading

  • OpenAPI Specification (OAS) basics and best practices
  • OpenAPI Generator documentation and templates
  • Practical patterns for TypeScript generation with fetch or axios-based clients