OpenAPI 3.x code generator with a plugin-style architecture supporting TypeScript, React, and Python FastAPI.
OpenAPI 3.2 shipped but most generators haven't caught up. When you need to glue a frontend to a backend during a POC, you don't want to fight a generator that produces bloated code requiring heavy post-processing.
oag focuses on simplicity: one config file, one command, clean output.
- Parses OpenAPI 3.x specs with full
$refresolution - Plugin-style architecture: enable only the generators you need
- TypeScript/Node client with zero runtime dependencies
- React/SWR hooks for queries, mutations, and SSE streaming
- Python FastAPI server with Pydantic v2 models
- First-class Server-Sent Events support via
AsyncGenerator(TS) andStreamingResponse(Python) - Test generation — pytest tests for FastAPI, vitest tests for TypeScript/React (opt-out via
scaffold.test_runner: false) - Scaffolds Biome + tsdown configuration for TypeScript projects, Ruff for Python
- Configurable naming strategies and operation aliases
- Three layout modes per generator: bundled, modular, or split
Install from crates.io:
cargo install oag-cliOr build from source:
git clone https://github.com/urmzd/openapi-generator.git
cd openapi-generator
cargo install --path crates/oag-cliInitialize a config file:
oag initThis creates .urmzd.oag.yaml in the current directory:
# oag configuration — https://github.com/urmzd/openapi-generator
input: openapi.yaml
naming:
strategy: use_operation_id # use_operation_id | use_route_based
aliases: {}
# createChatCompletion: chat # operationId → custom name
# listModels: models
generators:
node-client:
output: src/generated/node
layout: modular # bundled | modular | split
# split_by: tag # operation | tag | route (only for split layout)
# base_url: https://api.example.com
# no_jsdoc: false
# source_dir: src # subdirectory for source files ("src", "lib", or "" for root)
scaffold:
# package_name: my-api-client
# repository: https://github.com/you/your-repo
# existing_repo: false # set to true to skip all scaffold files (package.json, tsconfig, etc.)
formatter: biome # biome | false
test_runner: vitest # vitest | false
bundler: tsdown # tsdown | false
# react-swr-client:
# output: src/generated/react
# layout: modular
# scaffold:
# formatter: biome
# test_runner: vitest
# bundler: tsdown
# fastapi-server:
# output: src/generated/server
# layout: modular
# scaffold:
# formatter: ruff # ruff | false
# test_runner: pytest # pytest | falseGenerate code:
oag generateThis will generate code for all configured generators. You can override the input spec:
oag generate -i other-spec.yamlNote: The old config format (with target, output, output_options, and client fields) is still supported for backward compatibility and automatically converted.
| Command | Description |
|---|---|
generate |
Generate code from an OpenAPI spec |
validate |
Validate an OpenAPI spec and report errors |
inspect |
Dump the parsed intermediate representation (YAML or JSON) |
init |
Create a .urmzd.oag.yaml config file |
completions |
Generate shell completions (bash, zsh, fish, etc.) |
Run oag <command> --help for detailed usage.
All options are set in .urmzd.oag.yaml. The CLI supports -i/--input to override the input spec path.
| Key | Type | Default | Description |
|---|---|---|---|
input |
string |
openapi.yaml |
Path to the OpenAPI spec (YAML or JSON) |
naming.strategy |
string |
use_operation_id |
How to derive function names: use_operation_id or use_route_based |
naming.aliases |
map |
{} |
Map of operationId to custom name overrides |
The generators map configures which generators to run and their options. Each generator has its own output directory and settings.
Available generators:
node-client— TypeScript/Node API client (zero dependencies)react-swr-client— React/SWR hooks (extends node-client)fastapi-server— Python FastAPI server stubs with Pydantic v2 models
| Key | Type | Default | Description |
|---|---|---|---|
output |
string |
required | Output directory for this generator |
layout |
string |
modular |
Layout mode: bundled (single file), modular (separate files per concern), or split (separate files per operation group) |
split_by |
string |
tag |
Only for split layout: operation, tag, or route |
base_url |
string |
(from spec servers) | Override the API base URL (TypeScript generators only) |
no_jsdoc |
bool |
false |
Disable JSDoc comments (TypeScript generators only) |
source_dir |
string |
"src" |
Subdirectory for generated source files — set to "" to place files at the output root (TypeScript generators only) |
scaffold.package_name |
string |
(from spec title) | Custom package name (TypeScript: npm, Python: pyproject.toml) |
scaffold.repository |
string |
Repository URL for package metadata | |
scaffold.formatter |
string or false |
biome (TS) / ruff (Python) |
Code formatter — set to false to disable |
scaffold.test_runner |
string or false |
vitest (TS) / pytest (Python) |
Test runner — set to false to disable test generation |
scaffold.bundler |
string or false |
tsdown |
Bundler config (TypeScript only) — set to false to disable |
scaffold.existing_repo |
bool |
false |
Set to true to skip all scaffold files (package.json, tsconfig, biome, tsdown) and only emit a root index.ts re-export |
- bundled — Everything in a single file (e.g.,
src/index.tsormain.py) - modular — Separate files per concern (e.g.,
src/types.ts,src/client.ts,src/sse.ts,src/index.ts) - split — Separate files per operation group (e.g.,
src/pets.ts,src/users.ts,src/orders.ts)
For TypeScript generators, source files are placed in a src/ subdirectory by default (configurable via source_dir). This matches the scaffold's tsconfig.json (rootDir, include) and tsdown.config.ts (entry) — all of which adapt automatically to the configured source_dir. Set source_dir: "" to place files directly at the output root. Scaffold files (package.json, tsconfig.json, biome.json, tsdown.config.ts) always remain at the output root.
When using split layout, specify split_by:
operation— One file per operationtag— One file per OpenAPI tag (default)route— One file per route prefix
The old config format (with target, output, output_options, and client fields) is still supported and automatically converted to the new format.
oag-cli --> [oag-node-client, oag-react-swr-client, oag-fastapi-server] --> oag-core
The workspace uses a plugin-style architecture with five crates:
| Crate | Role |
|---|---|
oag-core |
OpenAPI parser, intermediate representation, transform pipeline, and CodeGenerator trait |
oag-node-client |
TypeScript/Node API client generator (zero dependencies) |
oag-react-swr-client |
React/SWR hooks generator (extends node-client) |
oag-fastapi-server |
Python FastAPI server generator with Pydantic v2 models |
oag-cli |
Command-line interface that orchestrates all generators |
oag-core defines the CodeGenerator trait:
pub trait CodeGenerator {
fn id(&self) -> config::GeneratorId;
fn generate(
&self,
ir: &ir::IrSpec,
config: &config::GeneratorConfig,
) -> Result<Vec<GeneratedFile>, GeneratorError>;
}Each generator implements this trait with a unique ID (node-client, react-swr-client, or fastapi-server). The CLI loops over the configured generators in .urmzd.oag.yaml and invokes each one.
Working examples with generated output are in the examples/ directory:
petstore— Node client and React client generated from the Petstore 3.2 specsse-chat— Node client and React hooks with SSE streaming for a chat API
Each example has its own .urmzd.oag.yaml configuring generators with separate output directories (e.g. generated/node and generated/react).
Regenerate them with:
just examples