clientforge
v1.0.0
Published
Front-end SDK generator from OpenAPI specifications — supports Angular, Vue, and React
Downloads
193
Maintainers
Readme
ClientForge
Front-end SDK generator from OpenAPI specifications — supports Angular, Vue, and React.
ClientForge reads your OpenAPI/Swagger document and generates fully-typed TypeScript models, HTTP services, and optional utilities (hooks, validators) for your front-end framework of choice.
Table of Contents
- Features
- Quick Start
- Installation
- CLI Commands
- Configuration
- Target Reference
- Architecture
- Programmatic API
- Region Preservation
- OpenAPI Support
- Contributing
- License
Features
- Multi-framework — generate SDKs for Angular, Vue, and React from a single config.
- Multi-spec — Swagger 2.0, OpenAPI 3.0.x, and OpenAPI 3.1.x.
- Type-safe — fully-typed TypeScript models, enums, unions, intersections, records, and generics.
- Validators — optional validation functions generated alongside models for all targets.
- Configurable output — choose model style (
interface/type/class/auto), enum style (enum/union), date type, binary type, and more. - Smart clean — only removes auto-generated files; preserves HTTP clients, user-created models, and custom code.
- Region preservation — regenerate only auto-generated regions; keep your custom code intact.
- Diagnostics — structured warnings and errors during normalization and generation.
- Extensible — plug-in architecture for targets via the
TargetGeneratorinterface. - Zero runtime dependencies — generated code relies only on your framework and HTTP client.
Quick Start
# 1. Install
npm install -D clientforge
# 2. Initialize a config file
npx clientforge init
# 3. Edit clientforge.config.json — set your OpenAPI source and targets
# 4. Generate
npx clientforge generateInstallation
As a dev dependency (recommended)
npm install -D clientforge
# or
pnpm add -D clientforge
# or
yarn add -D clientforgeThen use it via npx, pnpm, or an npm script:
npx clientforge generate
pnpm clientforge generateGlobal install
npm install -g clientforgeAfter a global install, both clientforge and the short alias cf are available:
clientforge generate
cf generate
cf -g # shortcut for generatenpm scripts
Add a script to your package.json:
{
"scripts": {
"generate:api": "clientforge generate"
}
}CLI Commands
| Command | Alias | Description |
| ---------------------- | ------ | ------------------------------------------------------------------------------ |
| clientforge init | | Create a clientforge.config.json with sensible defaults |
| clientforge generate | cf g | Generate models and services from the configured OpenAPI source |
| clientforge validate | | Validate configuration, document access, version, and target setup |
| clientforge inspect | | Print a summary of the parsed contract (schemas, operations, groups, warnings) |
Configuration
Running clientforge init creates a clientforge.config.json at the project root:
{
"input": {
"source": "http://localhost:5000/swagger/v1/swagger.json",
"resolveRefs": true,
},
"output": {
"rootDir": "./src/api-generated",
"clean": false,
"format": true,
},
"naming": {
"groupStrategy": "tag", // "first-segment" | "tag" | "operationId-prefix"
"modelStyle": "auto", // "interface" | "type" | "class" | "auto"
"enumStyle": "enum", // "enum" | "union"
"dateType": "string", // "string" | "Date"
"binaryType": "Blob", // "Blob" | "File" | "string"
"typeOnlyImports": false,
},
"features": {
"validators": false,
"regionPreservation": true,
"emitDiagnostics": true,
},
"targets": [
{
"name": "angular",
"enabled": true,
"outputDir": "./src/app/api",
"httpClient": "angular-http-client",
"generateTests": true,
},
],
}Configuration Reference
input
| Field | Type | Default | Description |
| ------------- | --------- | ------- | ------------------------------------------------------------------------------------------ |
| source | string | — | Required. URL or local path to an OpenAPI/Swagger document (.json, .yaml, .yml). |
| resolveRefs | boolean | true | Resolve $ref pointers before normalization. |
output
| Field | Type | Default | Description |
| --------- | --------- | ----------------------- | ----------------------------------------------------------------------------------------------- |
| rootDir | string | "./src/api-generated" | Output root directory. |
| clean | boolean | false | Remove auto-generated files before regenerating. Preserves HTTP clients and user-created files. |
| format | boolean | true | Apply formatting to generated files. |
naming
| Field | Type | Default | Description |
| ----------------- | --------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------- |
| groupStrategy | string | "tag" | How operations are grouped into services: "first-segment", "tag", or "operationId-prefix". |
| modelStyle | string | "auto" | Generated model form: "interface", "type", "class", or "auto" (classes for request bodies, interfaces for the rest). |
| enumStyle | string | "enum" | "enum" for TypeScript enums, "union" for string literal unions. |
| dateType | string | "string" | TypeScript type for date/date-time fields. |
| binaryType | string | "Blob" | TypeScript type for binary fields. |
| typeOnlyImports | boolean | false | Use import type syntax. |
features
| Field | Type | Default | Description |
| -------------------- | --------- | ------- | ------------------------------------------------------------- |
| validators | boolean | false | Generate validation functions alongside models. |
| regionPreservation | boolean | true | Preserve user-written code outside of auto-generated regions. |
| emitDiagnostics | boolean | true | Print diagnostic messages during generation. |
targets[]
| Field | Type | Default | Description |
| ------------------ | --------- | ------- | ------------------------------------------------------------------------ |
| name | string | — | "angular", "vue", or "react". |
| enabled | boolean | — | Enable or disable this target. |
| outputDir | string | — | Target-specific output directory. |
| httpClient | string | varies | HTTP client to use (e.g. "angular-http-client", "axios", "fetch"). |
| generateTests | boolean | false | Generate unit test stubs (Angular). |
| generateHooks | boolean | false | Generate query/mutation hooks (React). |
| queryLibrary | string | — | Query library for hooks (e.g. "tanstack-query"). |
| basePathVariable | string | — | Variable name for the API base URL (e.g. "environment.apiUrlBase"). |
| basePathImport | string | — | Import path for the base URL variable. |
Target Reference
Angular
Generates Angular services with HttpClient, typed Observable<T> responses, @Injectable decorators, and a reusable base HTTP service.
{
"name": "angular",
"enabled": true,
"outputDir": "./src/app/api",
"httpClient": "angular-http-client",
"generateTests": true, // generate .spec.ts stubs
}Generated structure:
src/app/api/
├── models/
│ ├── user.model.ts
│ ├── order.model.ts
│ └── index.ts
├── services/
│ ├── base-api.service.ts
│ ├── users.service.ts
│ ├── users.service.spec.ts
│ └── orders.service.tsVue
Generates pure TypeScript services for Vue with a configurable HTTP client (axios or fetch).
{
"name": "vue",
"enabled": true,
"outputDir": "./src/api",
"httpClient": "axios",
}Generated structure:
src/api/
├── models/
│ ├── user.model.ts
│ └── index.ts
├── http/
│ └── http-client.ts
└── services/
├── users.service.ts
└── orders.service.tsReact
Generates async client functions and optional TanStack Query hooks.
{
"name": "react",
"enabled": true,
"outputDir": "./src/api",
"httpClient": "fetch",
"generateHooks": true,
"queryLibrary": "tanstack-query",
}Generated structure:
src/api/
├── models/
│ ├── user.model.ts
│ └── index.ts
├── http/
│ └── http-client.ts
├── clients/
│ ├── users-client.ts
│ └── orders-client.ts
└── hooks/
├── use-users.hook.ts
└── use-orders.hook.tsArchitecture
ClientForge follows a pipeline architecture that cleanly separates parsing, normalization, and code generation:
┌─────────────────────────────────────────────────────────┐
│ CLI Layer │
│ init · generate · validate · inspect │
└────────────────────────┬────────────────────────────────┘
│
┌────────────────────────▼────────────────────────────────┐
│ Core Pipeline │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Config Loader │ │ Doc Loader │ │ Version │ │
│ │ │ │ (JSON/YAML/ │ │ Detector │ │
│ │ │ │ URL/local) │ │ │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ │ ┌──────▼───────┐ │ │
│ │ │ Ref Resolver │◄────────┘ │
│ │ └──────┬───────┘ │
│ │ │ │
│ │ ┌────────────▼────────────┐ │
│ │ │ Spec Normalizer │ │
│ │ │ (Swagger 2 / OA 3.0 / │ │
│ │ │ OA 3.1 → unified IR) │ │
│ │ └────────────┬────────────┘ │
│ │ │ │
│ ┌──────▼─────────────────▼──────────────────────────┐ │
│ │ Intermediate Representation (IR) │ │
│ │ ApiContract · SchemaDefinition · TypeDefinition │ │
│ │ OperationDefinition · ParameterDefinition · ... │ │
│ └───────────────────────┬───────────────────────────┘ │
│ │ │
│ ┌───────────────────────▼───────────────────────────┐ │
│ │ Generation Pipeline │ │
│ │ NamingStrategy · NameConflictResolver │ │
│ │ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │
│ │ │ Angular │ │ Vue │ │ React │ │ │
│ │ │ Target │ │ Target │ │ Target │ │ │
│ │ └────┬─────┘ └────┬─────┘ └─────┬────────┘ │ │
│ │ └──────────────┼──────────────┘ │ │
│ └──────────────────────┼────────────────────────────┘ │
│ │ │
│ ┌──────────────────────▼────────────────────────────┐ │
│ │ File Writer + Region Preserver │ │
│ └───────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘Key Design Principles
Intermediate Representation (IR) — The OpenAPI document is never used directly by generators. It is first normalized into a framework-agnostic IR (
ApiContract), regardless of whether the input is Swagger 2.0, OpenAPI 3.0, or 3.1.Target-based generation — Each framework (Angular, Vue, React) is a self-contained
TargetGeneratorplugin. New targets can be added by implementing a single interface.Naming strategy — A centralized
NamingStrategydrives all file names, class names, method names, and conflict resolution. Custom naming strategies can be plugged in.Region preservation — Generated files can use
/* <auto-generated NAME> */markers to protect hand-written code from being overwritten on regeneration.Diagnostics — Issues found during parsing, normalization, and generation are collected as structured
DiagnosticMessageobjects rather than throwing errors.
Project Structure
src/
├── cli/ # CLI entry point and commands
│ ├── index.ts # Commander setup
│ └── commands/
│ ├── init.command.ts
│ ├── generate.command.ts
│ ├── validate.command.ts
│ └── inspect.command.ts
├── core/
│ ├── config/ # Config loading, defaults, validation, types
│ ├── diagnostics/ # Diagnostic collector and types
│ ├── document/ # Document loader, version detector, ref resolver
│ ├── ir/ # Intermediate Representation types
│ ├── naming/ # Naming strategy, conflict resolution, reserved words
│ ├── normalize/ # Swagger 2 / OA 3.0 / OA 3.1 normalizers + type resolver
│ ├── pipeline/ # Generation context, pipeline orchestrator, result types
│ └── render/ # File artifact, writer, region preserver
├── plugins/
│ └── target/ # TargetGenerator interface
├── shared/ # Shared renderers (type-renderer, service-helpers)
├── targets/
│ ├── angular/ # Angular target + renderers
│ ├── vue/ # Vue target + renderers
│ └── react/ # React target + renderers
└── utils/ # Path and string utilitiesAdding a New Target
- Create a new directory under
src/targets/<name>/. - Implement the
TargetGeneratorinterface:
import type { FileArtifact } from "../../core/render/file-artifact.js";
import type { GenerationContext } from "../../core/pipeline/generation-context.js";
import type { TargetGenerator } from "../../plugins/target/target.interface.js";
export class MyFrameworkTarget implements TargetGenerator {
readonly name = "my-framework";
generate(context: GenerationContext): FileArtifact[] {
// Access context.contract (IR), context.config, context.naming, etc.
// Return an array of FileArtifact objects to be written to disk.
return [];
}
}- Register the target in the pipeline's
TARGET_REGISTRYinsrc/core/pipeline/generation-pipeline.ts.
Programmatic API
ClientForge can be used as a library in addition to the CLI:
import {
loadConfig,
loadDocument,
detectVersion,
normalizeDocument,
runPipeline,
writeArtifacts,
} from "clientforge";
// 1. Load config (reads clientforge.config.json)
const config = loadConfig();
// 2. Load the OpenAPI document
const document = await loadDocument(config.input.source);
// 3. Detect spec version
const version = detectVersion(document); // "swagger2" | "openapi3.0" | "openapi3.1"
// 4. Normalize to the Intermediate Representation
const contract = normalizeDocument(document);
// 5. Run the generation pipeline (without writing to disk)
const result = runPipeline(contract, config, { write: false });
// 6. Inspect artifacts
for (const artifact of result.artifacts) {
console.log(artifact.path, artifact.kind);
}
// 7. Optionally write to disk
writeArtifacts(result.artifacts, process.cwd());Exported Types
All IR types are exported for custom integrations:
ApiContract,ApiServer,OpenApiVersionOperationDefinition,ParameterDefinition,RequestBodyDefinition,ResponseDefinitionSchemaDefinition,TypeDefinition,PropertyDefinitionClientForgeConfig,TargetConfig,NamingConfigFileArtifact,GenerationContext,GenerationResultNamingStrategy,DefaultNamingStrategy
Region Preservation
When features.regionPreservation is enabled, ClientForge uses auto-generated markers to protect your custom code:
/* <auto-generated imports> */
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
/* </auto-generated imports> */
// Your custom imports here — these are preserved on regeneration
/* <auto-generated methods> */
getUsers(): Observable<User[]> {
return this.http.get<User[]>(`${this.baseUrl}/users`);
}
/* </auto-generated methods> */
// Your custom methods here — these are preserved on regenerationOn subsequent runs, only content inside the <auto-generated> markers is updated. Everything outside is preserved.
OpenAPI Support
| Feature | Swagger 2.0 | OpenAPI 3.0 | OpenAPI 3.1 |
| ------------------------ | :---------: | :---------: | :---------: |
| JSON input | ✅ | ✅ | ✅ |
| YAML input | ✅ | ✅ | ✅ |
| URL source | ✅ | ✅ | ✅ |
| Local file source | ✅ | ✅ | ✅ |
| $ref resolution | ✅ | ✅ | ✅ |
| allOf | ✅ | ✅ | ✅ |
| oneOf | — | ✅ | ✅ |
| anyOf | — | ✅ | ✅ |
| discriminator | — | ✅ | ✅ |
| nullable | — | ✅ | ✅ |
| additionalProperties | ✅ | ✅ | ✅ |
| readOnly / writeOnly | ✅ | ✅ | ✅ |
| Security schemes | ✅ | ✅ | ✅ |
| Multiple servers | — | ✅ | ✅ |
| multipart/form-data | ✅ | ✅ | ✅ |
Contributing
Contributions are welcome! Here's how to get started:
Prerequisites
- Node.js >= 18
- npm, pnpm, or yarn
Setup
git clone https://github.com/your-username/clientforge.git
cd clientforge
npm installDevelopment
# Build
npm run build
# Watch mode
npm run dev
# Run tests
npm test
# Run tests in watch mode
npm run test:watch
# Run tests with coverage
npm run test:coverage
# Type-check
npm run lintTest Structure
test/
├── fixtures/openapi/ # OpenAPI spec fixtures for tests
├── unit/ # Unit tests for individual modules
│ ├── naming-strategy.test.ts
│ ├── type-resolver.test.ts
│ ├── normalizers.test.ts
│ ├── region-preserver.test.ts
│ └── ...
└── integration/
└── pipeline.test.ts # End-to-end pipeline integration testsGuidelines
- Write tests. Every new feature or bug fix should include tests.
- Use the IR. Never access the raw OpenAPI document in generators — always work through the Intermediate Representation.
- No hardcoded opinions. Generated base services should not embed authentication, routing, or app-specific logic.
- Follow existing patterns. Look at the Angular/Vue/React target implementations for reference.
