Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

Repository files navigation

openapi-domainify

Split your OpenAPI spec into domain-based TypeScript services.

Most OpenAPI generators dump everything into a single file or force you to restructure your spec. This tool reads your existing spec and generates modular, domain-separated service classes based on URL path prefixes.

// Your config
domains: [
  { name: 'auth', prefix: '/auth/' },
  { name: 'billing', prefix: '/billing/' },
  { name: 'users', prefix: '/users/' },
]

// Generated usage
api.auth.login({ email, password })
api.billing.getInvoice(invoiceId)
api.users.updateProfile(userId, data)

Why This Exists

Every OpenAPI TypeScript generator on npm does one of these:

Tool Approach Problem
openapi-typescript Types only No service layer
openapi-fetch Single client One massive file
@hey-api/openapi-ts Tag-based grouping Requires spec changes
orval React Query focused Framework lock-in
openapi-generator Mustache templates 15MB dependency, Java heritage

openapi-domainify uses path prefixes from your existing URLs. No spec modifications. No framework coupling. Just TypeScript.


Installation

npm install -D openapi-domainify
# or
pnpm add -D openapi-domainify
# or
bun add -D openapi-domainify

Quick Start

1. Create config file

npx openapi-domainify init

This creates domainify.config.ts:

import { defineConfig } from 'openapi-domainify'

export default defineConfig({
  // OpenAPI spec source (URL or local file path)
  input: 'https://api.example.com/openapi.json',
  
  // Where to write generated code
  output: './src/api',

  // Domain splitting rules (order matters - first match wins)
  domains: [
    { name: 'auth', prefix: '/auth/', className: 'Auth' },
    { name: 'users', prefix: '/users/', className: 'Users' },
  ],

  // Catch-all for unmatched paths
  fallback: { name: 'api', prefix: '/', className: 'Api' },
})

2. Generate

npx openapi-domainify generate

Output structure:

src/api/
├── index.ts              # API client (regenerated each time)
├── http.ts               # HTTP client template (safe to edit)
├── generated/            # Auto-generated (DO NOT EDIT)
│   ├── openapi.json
│   ├── openapi.ts
│   ├── auth/
│   │   ├── service.ts
│   │   └── types.ts
│   └── users/
│       ├── service.ts
│       └── types.ts
└── overrides/            # Your customizations (safe to edit)
    ├── auth/
    │   └── service.ts    # Extend/override AuthService
    └── users/
        └── service.ts    # Extend/override UsersService

Overrides

When the generated code has issues or you need custom methods, create an override instead of editing generated files:

// overrides/users/service.ts
import { UsersService as GeneratedUsersService } from '../generated/users/service'
import type { HttpClient } from '../../http'

export class UsersService extends GeneratedUsersService {
  constructor(http: HttpClient) {
    super(http)
  }

  // Override a broken method
  async getUser(id: string | number) {
    // Custom implementation
    return this.http.get(`/users/${id}`)
  }

  // Add a method not in the OpenAPI spec
  async customEndpoint() {
    return this.http.post('/users/custom')
  }
}

The generator:

  • Never overwrites existing override files
  • Always regenerates generated/ and index.ts
  • Auto-detects if you've customized an override and uses it in index.ts

Generated Code

Service Classes

// generated/users/service.ts
import type { HttpClient } from '../../http'
import type * as T from './types'

export class UsersService {
  constructor(private http: HttpClient) {}

  // GET /users
  users(params?: T.GetUsersQuery): Promise<T.GetUsersResponse> {
    return this.http.get('/users', { params })
  }

  // GET /users/{id}
  getUser(id: string | number): Promise<T.GetUsersByIdResponse> {
    return this.http.get(`/users/${id}`)
  }

  // POST /users
  createUser(data: T.PostUsersRequest): Promise<T.PostUsersResponse> {
    return this.http.post('/users', data)
  }
}

Type Extraction

Types are extracted directly from your OpenAPI spec using the TypeScript Compiler API:

// generated/users/types.ts

// Request types
export type PostUsersRequest = {
  email: string
  name: string
  role?: 'admin' | 'user'
}

// Response types
export type GetUsersResponse = {
  data: Array<{
    id: number
    email: string
    name: string
  }>
}

HTTP Client

The generator creates an http.ts template with a FetchHttpClient implementation. Edit it or replace with your preferred HTTP library:

// http.ts (generated once, safe to edit)
export interface HttpClient {
  get<T>(url: string, options?: { params?: Record<string, unknown> }): Promise<T>
  post<T>(url: string, body?: Record<string, unknown>): Promise<T>
  put<T>(url: string, body?: Record<string, unknown>): Promise<T>
  patch<T>(url: string, body?: Record<string, unknown>): Promise<T>
  delete<T>(url: string, options?: { params?: Record<string, unknown> }): Promise<T>
}

Configuration Reference

import { defineConfig } from 'openapi-domainify'

export default defineConfig({
  // Required: OpenAPI spec source
  input: 'https://api.example.com/openapi.json',

  // Required: Output directory
  output: './src/api',

  // Required: Domain splitting rules
  domains: [
    { 
      name: 'auth',           // Directory name
      prefix: '/auth/',       // URL prefix to match
      className: 'Auth'       // Generated class name suffix
    },
  ],

  // Optional: Catch-all for unmatched paths
  fallback: { name: 'api', prefix: '/', className: 'Api' },

  // Optional: Strip this prefix from all paths
  stripPrefix: '/api/v1',

  // Optional: Where to save the downloaded spec
  specOutput: './src/api/generated/openapi.json',

  // Optional: Custom HTTP client import path
  httpClientImport: '../../http',

  // Optional: Override files directory (relative to output)
  overridesDir: '../overrides',

  // Optional: Generate index.ts (default: true)
  generateIndex: true,
})

Method Naming Convention

Generated method names follow RESTful conventions:

HTTP Method Path Generated Method
GET /users users()
GET /users/{id} getUser(id)
POST /users createUser(data)
PUT /users/{id} updateUser(id, data)
PATCH /users/{id} patchUser(id, data)
DELETE /users/{id} deleteUser(id)
GET /users/{id}/orders getUserOrders(id)

How It Works

  1. Fetch spec — Downloads OpenAPI JSON from URL or reads local file
  2. Generate types — Runs openapi-typescript to create base TypeScript types
  3. Parse with TS Compiler API — Loads generated types into TypeScript's type checker
  4. Extract endpoints — Iterates over paths interface, extracting methods, params, bodies
  5. Group by domain — Matches each path against configured prefixes
  6. Generate services — Creates service classes with fully typed methods
  7. Generate scaffolding — Creates override templates (only if they don't exist)
  8. Wire up index — Generates index.ts that imports from overrides or generated

Comparison

Feature openapi-domainify openapi-fetch @hey-api/openapi-ts orval
Domain splitting ✅ Path prefix ❌ ⚠️ Tags only ❌
No spec changes ✅ ✅ ❌ ✅
Override system ✅ ❌ ❌ ❌
Framework agnostic ✅ ✅ ✅ ❌ React Query
Service classes ✅ ❌ ✅ ✅

License

MIT

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages