npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@seothos/openapi-generator

v0.0.5

Published

Generate Angular code from an OpenAPI specification: TypeScript types, HTTP services, [NgRx Signal Store](https://ngrx.io/guide/signals) stores, signal forms, and ready-to-use Angular Material components — plus an optional throwaway test app to preview ev

Readme

@seothos/openapi-generator

Generate Angular code from an OpenAPI specification: TypeScript types, HTTP services, NgRx Signal Store stores, signal forms, and ready-to-use Angular Material components — plus an optional throwaway test app to preview everything.

You point it at a spec (a URL or an inline JSON object) and a config file, run one command, and get a folder of typed, idiomatic Angular code.

Requirements

The generated code targets a modern Angular app:

  • Angular 21+ (@angular/core, @angular/common/http, @angular/forms/signals)
  • @ngrx/signals 21+ — used by the generated stores
  • @angular/material — used by the generated form components
  • luxon — only if you set dateType: 'DateTime' (dates become DateTime)

@angular/core and @ngrx/signals are declared as peer dependencies, so in a normal Angular workspace they're already installed.

Install

npm install --save-dev @seothos/openapi-generator
# or: pnpm add -D @seothos/openapi-generator
# or: yarn add -D @seothos/openapi-generator

Quick start

1. Create a config file

The generator reads a config file that default-exports a config object built with createConfig. Use a .mjs (or .js) file so Node can run it without any extra tooling.

Loading the spec straight from a running server:

// openapi.config.mjs
import { createConfig } from '@seothos/openapi-generator/generator';

export default createConfig({
  url: 'https://api.example.com/openapi.json',
  outputDir: './src/app/generated',
});

Or from a local spec file:

// openapi.config.mjs
import { readFileSync } from 'node:fs';
import { createConfig } from '@seothos/openapi-generator/generator';

const spec = JSON.parse(readFileSync('./openapi.json', 'utf-8'));

export default createConfig({
  json: spec,
  outputDir: './src/app/generated',
});

TypeScript config

Prefer a typed config? Use openapi.config.ts — you get autocompletion and type-checking on every option, and you can import the config enums:

// openapi.config.ts
import { readFileSync } from 'node:fs';
import {
  createConfig,
  TypeGroupingMode,
} from '@seothos/openapi-generator/generator';

const spec = JSON.parse(readFileSync('./openapi.json', 'utf-8'));

export default createConfig({
  json: spec,
  outputDir: './src/app/generated',
  typeGroupingMode: TypeGroupingMode.NO_GROUPING,
  dateType: 'DateTime',
  fileHeader: `import { DateTime } from 'luxon';`,
});

Node can't import .ts directly, so run it through a TypeScript loader such as tsx:

npx tsx ./node_modules/@seothos/openapi-generator/bin.js openapi.config.ts

2. Run the generator

# .mjs / .js config — no extra tooling
npx openapi-generate openapi.config.mjs

# .ts config — via a TypeScript loader
npx tsx ./node_modules/@seothos/openapi-generator/bin.js openapi.config.ts

That's it — the generated code lands in outputDir.

Add it to your package.json so it's repeatable:

{
  "scripts": {
    "generate:api": "openapi-generate openapi.config.mjs",
    "generate:api:ts": "tsx ./node_modules/@seothos/openapi-generator/bin.js openapi.config.ts"
  }
}

CLI

openapi-generate <config-file-path> [--verbose | --timings | --quiet]

| Flag | Output | | ------------ | ---------------------------------------------------------------- | | (none) | Basic progress messages and a completion summary. | | --verbose | Per-step [START]/[DONE] logs with durations. | | --timings | Timing summary only (⏱️ <step>: XXms) — useful for profiling. | | --quiet | Errors only — useful in CI. |

What gets generated

Inside outputDir you'll get (each can be toggled off — see below):

| Output | Description | | -------------- | ----------------------------------------------------------------------- | | types | types.gen.ts interfaces, plus defaults.ts with default-value seeds. | | endpoints | Typed endpoint metadata grouped by tag. | | services | HttpClient-based services, one method per operation. | | stores | NgRx Signal Store per resource, with call-state and entity helpers. | | state | Shared state contracts used by the stores. | | forms | Signal-forms classes with validation derived from the schema. | | components | Standalone Angular Material form components wired to the stores. | | schemas | Component schema metadata. | | test-app | An optional standalone app that routes to every generated component. |

A top-level index.ts re-exports everything, and runtime helpers (withEntityCollection, CallState, …) are imported from this package.

Using the generated code

Provide the API base URL

Generated services build request URLs from the API_ENV injection token. Provide it once in your app config:

// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideHttpClient } from '@angular/common/http';
import { API_ENV } from '@seothos/openapi-generator';

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(),
    { provide: API_ENV, useValue: { production: false, apiUrl: 'https://api.example.com' } },
  ],
};

Use a generated store or component

Names are derived from the operation and tag, so the exact symbols depend on your spec. For an operation like POST /account-holders you'd get something like:

import { Component, inject } from '@angular/core';
import {
  PostCreateAccountHolderComponent,
  AccountHoldersStore,
} from './generated';

@Component({
  selector: 'app-root',
  imports: [PostCreateAccountHolderComponent],
  template: `
    <app-post-create-account-holder-component
      [showSnackbar]="false"
      (submitSuccess)="onCreated($event)"
    />
  `,
})
export class App {
  protected readonly store = inject(AccountHoldersStore);

  onCreated(result: unknown) {
    console.log('created', result);
  }
}

Generated components emit submitSuccess, submitError, and formCancel outputs, and expose a showSnackbar input (default true) so you can suppress the built-in Material snackbars and drive your own notifications instead.

Extending a component with your own template

The generated component ships with a default Material template, but the form plumbing lives in the TypeScript class — formInstance, the onSubmit() / onCancel() handlers, isSubmitting(), the hasError() / getErrorMessage() helpers, the injected store, and the inputs/outputs. Those members are protected, so you can subclass the generated component and supply your own template while reusing all of that logic. Nothing needs to be reimplemented — you only swap the markup.

import { Component } from '@angular/core';
import { FormField } from '@angular/forms/signals';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatButtonModule } from '@angular/material/button';
import { PostCreateAccountHolderComponent } from './generated';

@Component({
  selector: 'app-account-holder-form',
  imports: [FormField, MatFormFieldModule, MatInputModule, MatButtonModule],
  // Your markup, bound to the inherited (protected) members:
  template: `
    <form (ngSubmit)="onSubmit()">
      <mat-form-field>
        <mat-label>Name</mat-label>
        <input matInput [formField]="formInstance.form.name" />
        @if (hasError('name')) {
          <mat-error>{{ getErrorMessage('name') }}</mat-error>
        }
      </mat-form-field>

      <!-- …the rest of your fields… -->

      <button mat-raised-button type="submit" [disabled]="isSubmitting()">
        Save
      </button>
    </form>
  `,
})
export class AccountHolderForm extends PostCreateAccountHolderComponent {}

Because you're subclassing, the initialValue / showSnackbar inputs and the submitSuccess / submitError / formCancel outputs are inherited too — use the component exactly like the generated one, just with your markup.

Configuration reference

Pass these to createConfig. Only outputDir and one of url / json are required; everything else has a sensible default.

Source (required — pick one)

| Option | Type | Description | | ------ | -------------- | -------------------------------------------- | | url | string | Fetch the OpenAPI spec from this URL. | | json | OpenApiSpec | Use an already-loaded spec object. |

Common options

| Option | Type | Default | Description | | ------------------------ | --------- | ----------------------------- | -------------------------------------------------------------------------------------------- | | outputDir | string | (required) | Directory the generated code is written to. | | clearDirectory | boolean | false | Empty outputDir before writing. | | dateType | string | 'string' | TS type for date/date-time fields, e.g. 'DateTime' (luxon) or 'Date'. | | fileHeader | string | '' | Text prepended to generated files — handy for imports your dateType needs. | | useReadOnlyArrays | boolean | true | Emit readonly T[] for array properties. | | typeGroupingMode | enum | NO_GROUPING | How generated types are grouped into files. | | servicePrefixPathMatch | string | '' | Endpoints whose path contains this string go into a separate, prefixed service (e.g. externalExternalCustomersService). | | devtools | boolean | false | Compose withDevtools() into each store for Redux DevTools visibility. | | runtimePackage | string | '@seothos/openapi-generator'| Package the generated code imports runtime helpers from. Change only if you re-publish them. |

Output toggles

All write* options default to true; set to false to skip that output.

writeTypes, writeEndpoints, writeServices, writeStores, writeState, writeForms, writeComponents, writeComponentSchemas, writeTypeMapping, writeTestApp.

export default createConfig({
  url: 'https://api.example.com/openapi.json',
  outputDir: './src/app/generated',
  dateType: 'DateTime',
  fileHeader: `import { DateTime } from 'luxon';`,
  writeTestApp: false, // skip the preview app
});

Generated test app

When writeTestApp is enabled (the default), the generator also fills in a standalone Angular app that routes to every generated form component — a quick way to click through all your forms without wiring anything up yourself. It writes app.routes.ts, app.ts, app.html, app.config.ts, and styles, with one route per operation that has a request body, plus a fallback report route listing any schema fields it couldn't map to a control.

The generator does not scaffold the Angular app for you — it only fills an app that already exists. Create one first (nx, the Angular CLI, etc.), then re-run the generator. If the target directory isn't there, the test app step is skipped with a hint instead of writing orphan files.

The routes import the generated components by name, so the test app needs to resolve them through generatedPackage — set this to whatever specifier your generated library is exposed as.

| Option | Type | Default | Description | | ----------------- | --------- | ----------------------------------- | --------------------------------------------------------------------------- | | writeTestApp | boolean | true | Generate the test app (skipped if the target app dir doesn't exist). | | testAppDir | string | sibling generated-test-app/src/app| The app's src/app directory. Defaults to a path derived from outputDir. | | generatedPackage| string | '@frontend-toolbox/generated' | Import specifier the test app uses to import the generated components. |

export default createConfig({
  url: 'https://api.example.com/openapi.json',
  outputDir: './src/app/generated',
  writeTestApp: true,
  testAppDir: './apps/api-playground/src/app',
  generatedPackage: '@my-org/generated-api',
});

Programmatic use

The CLI is a thin wrapper around the exported main(). You can also run the whole pipeline yourself:

import { main } from '@seothos/openapi-generator/generator';

// reads process.argv: [node, script, <config-path>, ...flags]
await main();

@seothos/openapi-generator/generator also exports createConfig, the default generators/writers, and the config types, so you can swap in custom implementations of any stage (loader, type generator, output writers, …).