Blog
Import an OpenAPI spec in TypeScript

Import an OpenAPI spec in TypeScript

Published on 23.10.20264 min read

An OpenAPI spec already describes your whole API. Yet before TypeScript knows any of it, most projects run a generator and commit a folder of output. openapi-import drops that step. You import the spec, and TypeScript reads the types from it:

import type { Pet } from './petStore.openapi.yaml'

What OpenAPI gives you

An OpenAPI document is a YAML or JSON file with three parts that matter for a front end:

  • paths: the endpoints, their methods, parameters, and responses.
  • components.schemas: the data shapes, such as Pet or Order.
  • servers: the base URLs.
petStore.openapi.yaml
openapi: 3.1.0
info:
  title: Pet Store
  version: 1.0.0
paths:
  /pets/{petId}:
    get:
      operationId: getPetById
      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
      required: [id, name]
      properties:
        id:
          type: integer
        name:
          type: string
        status:
          type: string
          enum: [available, pending, sold]

Back end and front end both build against this file. If your types drift from it, you find out at run time, usually from a bug report.

Why generated files get in the way

Tools such as Kubb turn a spec into TypeScript types, Zod schemas, and clients. I have built Kubb around that idea for years. It works, but the output is a second copy of the spec, and copies drift:

  • You run the generator every time the spec changes, or the types go stale.
  • You choose between committing the files and generating them in CI.
  • Go to definition lands in generated code, not in the spec.

Import the spec instead

TypeScript 7.1 adds content mappers. A content mapper is a package that tells the compiler how to read a file type it does not know. openapi-import is a content mapper for OpenAPI. Install it with Kubb and the TypeScript beta:

Terminal
pnpm add -D kubb openapi-import typescript@beta

Name your spec <name>.openapi.yaml and register that extension in tsconfig.json:

tsconfig.json
{
  "contentMappers": [
    {
      "package": "openapi-import",
      "extensions": [".openapi.yaml", ".openapi.yml"]
    }
  ]
}

Now the spec is a module. Import a schema type and use it:

pets.ts
import type { Pet } from './petStore.openapi.yaml'

const pet: Pet = {
  id: 1,
  name: 'Rex',
  status: 'lost', // Type '"lost"' is not assignable to type '"available" | "pending" | "sold"'.
}

The editor autocompletes status from the enum in the spec and flags 'lost' as an error. Hover shows the Pet type, and go to definition jumps to Pet in the YAML. Rename the type and the key in the spec follows. That last one still surprises me every time.

Add Zod schemas

Without a config, openapi-import gives you the types from Kubb's pluginTs. Add a kubb.config.ts to get the output of more plugins, such as Zod schemas, from the same import:

kubb.config.ts
import { defineConfig } from 'kubb/config'
import { pluginTs } from '@kubb/plugin-ts'
import { pluginZod } from '@kubb/plugin-zod'

export default defineConfig({
  output: { path: './src/gen' },
  plugins: [pluginTs(), pluginZod()],
})
pets.ts
import { type Pet, petSchema } from './petStore.openapi.yaml'

const response = await fetch('https://petstore3.swagger.io/api/v3/pet/1')
const pet: Pet = petSchema.parse(await response.json())

The types come from the content mapper. The code that runs comes from unplugin-kubb, which loads the same module in Vite, webpack, Rollup, and other bundlers.

Check it in CI

The editor and the build use the same compiler, so CI reports the same errors you see while typing:

Terminal
tsc --runExternalCode

When someone changes the spec, every file that no longer matches it fails the type check, before the code ships.

Get started

Install openapi-import, add one entry to contentMappers, and import your *.openapi.yaml spec. The project page has a demo, and the repository takes issues and feedback.

Portrait of Stijn Van Hulle

Stijn Van Hulle

Front-end engineer