OPEN SWAG GO
Guides

Exporting Your OpenAPI Spec

openapi-typescript can generate types directly from a running server — no need to export to a file first. There are two approaches:

If your Go API is running and serves the OpenAPI spec at an endpoint (Mount and the adapters serve it at <basePath>/openapi.json, e.g. /docs/openapi.json), you can point openapi-typescript directly at it:

package.json
{
  "scripts": {
    "generate:api-schema": "npx openapi-typescript http://localhost:3434/docs/openapi.json -o ./app/types/api-schema.ts"
  }
}
package.json
{
  "scripts": {
    "generate:api-schema": "bunx openapi-typescript http://localhost:3434/docs/openapi.json -o ./app/types/api-schema.ts"
  }
}

Start your Go server, then run:

npm run generate:api-schema
bun run generate:api-schema

This fetches the live spec and generates types in one step. Adjust the URL and port to match your server configuration.

Option 2: Export to a file first

If you prefer to work with a local file (useful for CI/CD or offline generation), export the spec from your Go app:

cmd/generate-spec/main.go
package main
 
import (
    "encoding/json"
    "os"
 
    openswag "github.com/gopackx/open-swag-go"
)
 
func main() {
    docs := openswag.New(openswag.Config{
        Info: openswag.Info{
        	Title:   "My API",
        	Version: "1.0.0",
        },
    })
 
    // Register your endpoints
    registerEndpoints(docs)
 
    // v1.3.0+: refuse to export a spec with duplicate routes or operationIds
    if err := docs.Validate(); err != nil {
        panic(err)
    }
 
    data, err := docs.SpecJSON()
    if err != nil {
        panic(err)
    }
 
    os.WriteFile("openapi.json", data, 0644)
}
go run cmd/generate-spec/main.go

Or curl it from a running server:

curl http://localhost:3434/docs/openapi.json -o openapi.json

Then generate types from the file:

package.json
{
  "scripts": {
    "generate:api-schema": "npx openapi-typescript ./openapi.json -o ./app/types/api-schema.ts"
  }
}
package.json
{
  "scripts": {
    "generate:api-schema": "bunx openapi-typescript ./openapi.json -o ./app/types/api-schema.ts"
  }
}

openapi-typescript

openapi-typescript generates TypeScript types directly from an OpenAPI spec. It produces type definitions only — no runtime code.

Setup

npm install -D openapi-typescript
bun add -d openapi-typescript

Then add a generate:api-schema script as shown in Exporting Your OpenAPI Spec above and run it.

Usage

src/api/client.ts
import type { paths } from './types';
 
type ListUsersResponse = paths['/api/users']['get']['responses']['200']['content']['application/json'];
type CreateUserBody = paths['/api/users']['post']['requestBody']['content']['application/json'];

Named types (v1.3.0+)

Every named Go struct used in a request or response body is emitted under components.schemas and referenced with $ref (see Schema Components), so openapi-typescript generates a named type for it instead of an anonymous inline object:

import type { components } from './types';
 
type User = components['schemas']['User'];

The paths[...] types above still work; they now resolve to components['schemas'][...]. orval likewise generates named interfaces (User, CreateUserRequest, …). Set Config.InlineSchemas: true if you need the previous anonymous output while migrating.

Nullable fields

Since v1.3.0, Go pointer fields are emitted as OpenAPI 3.1 type arrays, so they become union types with null:

type User struct {
    ID       string  `json:"id"`
    Nickname *string `json:"nickname"` // "type": ["string", "null"]
    Age      *int    `json:"age,omitempty"`
}
// components['schemas']['User'] as generated by openapi-typescript
{
  id?: string;
  nickname?: string | null;
  age?: number | null;
}

Before v1.3.0 the same fields were plain string / number, so regenerating may surface new null checks. Fields are only non-optional (no ?) when they are listed in required (swagger:"required" or a validator required rule, without omitempty).

Operation IDs

Every operation has an operationId (v1.3.0+). openapi-typescript uses it as the key of the generated operations interface, and orval names its functions and hooks after it. The default is the method plus path in lowerCamelCase (GET /api/users → getApiUsers, GET /api/users/{id} → getApiUsersById). Set Endpoint.OperationID for a name you choose:

openswag.Endpoint{
    Method:      "GET",
    Path:        "/api/users",
    OperationID: "listUsers", // orval generates useListUsers
}

Run docs.Validate() before exporting the spec: it reports duplicate operationIds (and duplicate routes), which would otherwise produce colliding generated names.

openapi-fetch

openapi-fetch builds on openapi-typescript to provide a type-safe fetch client with zero runtime overhead.

Setup

npm install openapi-fetch
npm install -D openapi-typescript
bun add openapi-fetch
bun add -d openapi-typescript

Generate types first, then create a client:

src/api/client.ts
import createClient from 'openapi-fetch';
import type { paths } from './types';
 
const client = createClient<paths>({
  baseUrl: 'https://api.example.com',
});
 
// Fully typed — params, body, and response are all inferred
const { data, error } = await client.GET('/api/users', {
  params: {
    query: { page: 1, limit: 20 },
  },
});
 
// data is typed as ListUsersResponse
// error is typed as the error response

Usage with React

src/hooks/useUsers.ts
import { useEffect, useState } from 'react';
import { client } from '../api/client';
 
export function useUsers(page: number) {
  const [users, setUsers] = useState<Awaited<ReturnType<typeof fetchUsers>>>();
 
  async function fetchUsers() {
    const { data } = await client.GET('/api/users', {
      params: { query: { page } },
    });
    return data;
  }
 
  useEffect(() => {
    fetchUsers().then(setUsers);
  }, [page]);
 
  return users;
}

Usage with Next.js Server Components

app/users/page.tsx
import { client } from '@/api/client';
 
export default async function UsersPage() {
  const { data } = await client.GET('/api/users', {
    params: { query: { page: 1 } },
  });
 
  return (
    <ul>
      {data?.users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

Orval

Orval generates a complete API client with React Query hooks, Axios instances, or custom HTTP clients.

Setup

npm install -D orval
bun add -d orval

Create a configuration file:

orval.config.ts
import { defineConfig } from 'orval';
 
export default defineConfig({
  api: {
    input: './openapi.json',
    output: {
      target: './src/api/generated.ts',
      client: 'react-query',
      mode: 'tags-split',
    },
  },
});

Add a script:

package.json
{
  "scripts": {
    "generate:api": "npx orval"
  }
}
package.json
{
  "scripts": {
    "generate:api": "bunx orval"
  }
}

Usage with React Query

The hook names below assume OperationID: "listUsers" on the GET /api/users endpoint; with the default operationId orval would generate useGetApiUsers. See Operation IDs.

src/components/UserList.tsx
import { useListUsers } from '../api/generated/users';
 
export function UserList() {
  const { data, isLoading } = useListUsers({ page: 1, limit: 20 });
 
  if (isLoading) return <div>Loading...</div>;
 
  return (
    <ul>
      {data?.users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

Usage with Vue

orval.config.ts
import { defineConfig } from 'orval';
 
export default defineConfig({
  api: {
    input: './openapi.json',
    output: {
      target: './src/api/generated.ts',
      client: 'vue-query',
    },
  },
});
src/components/UserList.vue
<script setup lang="ts">
import { useListUsers } from '../api/generated/users';
 
const { data, isLoading } = useListUsers({ page: 1, limit: 20 });
</script>
 
<template>
  <div v-if="isLoading">Loading...</div>
  <ul v-else>
    <li v-for="user in data?.users" :key="user.id">{{ user.name }}</li>
  </ul>
</template>

Comparison

Featureopenapi-typescriptopenapi-fetchOrval
Types only✅✅✅
Fetch client❌✅✅
React Query hooks❌❌✅
Vue Query hooks❌❌✅
Runtime overheadNoneMinimalVaries
Custom HTTP clientN/AFetchAxios, Fetch, Angular