Publishing as an npm Package โ
By default ng-openapi generates source files for use inside an existing Angular project. When several applications consume the same API โ or the API's team wants to ship the client rather than the spec โ generate the client as its own npm package instead: build it once in CI, publish it, and let applications npm install it.
Overview โ
Setting the package option adds the files an Angular library needs to the output directory:
petstore-client/
โโโ package.json โ name, version, description, dependencies, build script
โโโ ng-package.json โ ng-packagr project file
โโโ tsconfig.json โ library compiler settings
โโโ README.md โ build, publish and usage instructions for this client
โโโ .gitignore
โโโ index.ts โ the library's public API (unchanged)
โโโ models/
โโโ services/
โโโ โฆNothing about the client code itself changes; the same index.ts, models, services, tokens and providers are generated, and the package's public API is that root index.ts.
1. Configure โ
// openapi.config.ts
import { defineConfig } from "ng-openapi";
export default defineConfig({
input: "./petstore.yaml",
output: "./petstore-client",
clientName: "Petstore",
options: {
dateType: "Date",
enumStyle: "union",
},
package: {
name: "@acme/petstore-client",
// Falls back to the spec's info.version when unset; must be bare
// MAJOR.MINOR.PATCH, so a "v1.2.3" tag needs its prefix stripped
version: process.env["PKG_VERSION"]?.replace(/^v/, ""),
repository: "https://github.com/acme/petstore-client",
packageJson: {
license: "MIT",
publishConfig: { access: "restricted" },
},
},
});name is the only required field. The generated package.json declares peerDependencies on exactly the packages the generated code imports (@angular/core, @angular/common, rxjs, plus zod when the Zod plugin is configured), and devDependencies on the ng-packagr toolchain matching the Angular major in use โ see the package reference for how that major is chosen and when to pin it with angularVersion.
2. Generate and build โ
npx ng-openapi -c openapi.config.ts
cd petstore-client
npm install
npm run buildnpm run build runs ng-packagr, which compiles the library in the Angular Package Format into dist/: an ES module bundle, type declarations, the README and a package.json with the build-only fields stripped.
3. Publish โ
cd dist
npm publishPublishing from dist/ is what ng-packagr expects โ the source directory's package.json carries devDependencies and scripts that do not belong in the published package. For a private registry, set publishRegistry in the config and ng-openapi writes it to publishConfig.registry, so npm publish needs no extra flags.
4. Consume โ
In any Angular application:
npm install @acme/petstore-client// app.config.ts
import { ApplicationConfig } from "@angular/core";
import { provideHttpClient } from "@angular/common/http";
import { providePetstoreClient } from "@acme/petstore-client";
export const appConfig: ApplicationConfig = {
providers: [provideHttpClient(), providePetstoreClient({ basePath: "https://api.acme.com" })],
};Services, models and tokens are imported from the package exactly as they would be from a generated directory โ the Angular integration guide applies unchanged.
In CI โ
A typical pipeline regenerates the client from the current spec, builds it and publishes it with a version derived from the pipeline:
- run: npm ci
- run: npx ng-openapi -c openapi.config.ts
env:
PKG_VERSION: ${{ github.ref_name }} # "v1.2.3" on a tag; the config strips the "v"
# npm install, not npm ci: the generated directory has no lockfile
- run: npm install && npm run build
working-directory: petstore-client
- run: npm publish
working-directory: petstore-client/dist
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}Two things to keep in mind:
- The generator picks the Angular major from the
@angular/coreinstalled where it runs. In a job that has no Angular installed, setpackage.angularVersionexplicitly so the package targets the Angular your applications use, not a guessed default. - Every scaffold file is regenerated on each run. Put customizations in the config's
packageJsonoverride rather than editing the generatedpackage.json, or they are lost on the next generation. - Give the package a directory of its own. The scaffold refuses to overwrite a
package.json,tsconfig.json,README.md,ng-package.jsonor.gitignoreit did not generate (it recognizes its own by a stamp inpackage.json), sooutput: "."in a repository with its ownpackage.jsonfails with anOutputConflictErrorrather than replacing it.
What is not generated โ
- A
LICENSEfile. SetpackageJson.licensefor the SPDX identifier npm reads; drop aLICENSEfile into the output directory yourself if you want the text shipped โ ng-packagr copies it intodist/. - A
package-lock.json. Runnpm installin the generated directory (notnpm ci); commit the lockfile it produces if you want reproducible builds.