From Monolith to Plugin Architecture: Refactoring nx-plugin-openapi

5 min read
#angular #nx

Introduction

What started as a single Nx plugin for generating API clients from OpenAPI specifications has evolved into a flexible, extensible plugin ecosystem. This article documents the architectural refactoring that transformed @lambda-solutions/nx-plugin-openapi from a tightly-coupled monolith into a modular plugin-based system supporting multiple code generators.

The refactoring introduced three new packages:

PackagePurpose
@nx-plugin-openapi/corePlugin infrastructure, executor, auto-installation
@nx-plugin-openapi/plugin-openapiOpenAPI Generator integration
@nx-plugin-openapi/plugin-hey-apihey-api/openapi-ts integration

Why the Refactoring?

The original @lambda-solutions/nx-plugin-openapi was built around a single code generator. While functional, it had limitations:

  • Tight coupling - The executor directly spawned OpenAPI Generator CLI
  • Hard-coded options - 30+ generator-specific options baked into the schema
  • No extensibility - Using a different generator meant forking the project

Users increasingly wanted alternatives like hey-api for modern TypeScript generation with better type safety and tree-shaking.

The New Architecture

┌─────────────────────────────────────────────────────────────┐
│                   @nx-plugin-openapi/core                    │
│       Executor, Plugin Loader, Auto-Installation             │
└─────────────────────────────────────────────────────────────┘

          ┌───────────────────┼───────────────────┐
          ▼                   ▼                   ▼
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│  plugin-openapi  │ │  plugin-hey-api  │ │  Custom Plugins  │
│  (OpenAPI Gen)   │ │  (hey-api)       │ │  (Your own!)     │
└──────────────────┘ └──────────────────┘ └──────────────────┘

The core package provides the Nx executor and handles plugin loading. Each plugin wraps a specific code generator. Users select which plugin to use via configuration.

Configuration Changes

Before (Legacy)

{
  "targets": {
    "generate-api": {
      "executor": "@lambda-solutions/nx-plugin-openapi:generate-api",
      "options": {
        "inputSpec": "apps/api/openapi.yaml",
        "outputPath": "libs/api-client/src",
        "configFile": "openapi-config.json",
        "apiNameSuffix": "Client"
      }
    }
  }
}

After (Plugin Architecture)

{
  "targets": {
    "generate-api": {
      "executor": "@nx-plugin-openapi/core:generate-api",
      "options": {
        "generator": "openapi-tools",
        "inputSpec": "apps/api/openapi.yaml",
        "outputPath": "libs/api-client/src",
        "generatorOptions": {
          "configFile": "openapi-config.json",
          "apiNameSuffix": "Client"
        }
      }
    }
  }
}

Key differences:

  • New generator field selects which plugin to use
  • Generator-specific options moved to generatorOptions
  • Same structure works with any plugin

Switching to hey-api

{
  "targets": {
    "generate-api": {
      "executor": "@nx-plugin-openapi/core:generate-api",
      "options": {
        "generator": "hey-api",
        "inputSpec": "apps/api/openapi.yaml",
        "outputPath": "libs/api-client/src",
        "generatorOptions": {
          "client": "fetch",
          "plugins": ["@hey-api/schemas"]
        }
      }
    }
  }
}

Key Features

Auto-Installation

Missing plugins are automatically installed when needed:

$ nx run my-lib:generate-api

Plugin '@nx-plugin-openapi/plugin-openapi' is not installed.
Would you like to install it using npm? (y/n) y
Installing @nx-plugin-openapi/plugin-openapi using npm...
Successfully installed @nx-plugin-openapi/plugin-openapi
Starting OpenAPI code generation...

This only happens in interactive terminals, not in CI environments.

Multiple Specifications

Both plugins support generating from multiple specs in one target:

{
  "inputSpec": {
    "users-api": "specs/users.yaml",
    "products-api": "specs/products.yaml"
  },
  "outputPath": "libs/api-clients/src"
}

Each service generates to its own subdirectory.

Choosing a Generator

GeneratorBest For
openapi-toolsAngular services, multi-language support, mature ecosystem
hey-apiModern TypeScript, excellent type safety, tree-shaking, fetch/axios clients

You can use different generators for different projects in the same monorepo.

Git History

The refactoring happened over 40+ commits starting September 11, 2025:

CommitDescription
f70ff9dInitial plugin architecture
c55282cAdded hey-api plugin
54c150fIntegrated auto-installer
d1fde23Updated documentation

The legacy @lambda-solutions/nx-plugin-openapi remains functional for backward compatibility.

Creating Custom Plugins

The architecture supports custom plugins for any OpenAPI code generator. Plugins implement a simple interface with a name and generate() method. See the Creating Custom Plugins Guide for details.

Lessons Learned

  1. Design for extension - The plugin interface is minimal: only name and generate() required
  2. Graceful degradation - Auto-install skipped in CI, fallbacks for local dev
  3. Backward compatibility - Legacy package still works, no forced migration
  4. Document the contract - Clear interfaces enable community contributions

Conclusion

The refactoring transformed a single-purpose tool into a flexible ecosystem. Teams can now choose the generator that fits their needs—or build their own—while benefiting from shared Nx integration.


Resources: