🔧 Creating Custom Generators

Step-by-step guide to creating new generators.


Prerequisites


Step 1: Scaffold the Generator

1
2
3
4
5
6
# Using Just (recommended)
just generator-new payment-service --type=service

# Or direct Nx command
pnpm exec nx g @nx/plugin:generator payment-service \
  --project=generators

Step 2: Define the Schema

Edit generators/payment-service/schema.json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
  "$schema": "http://json-schema.org/schema",
  "id": "PaymentServiceGenerator",
  "title": "Payment Service Generator",
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "description": "Name of the service",
      "$default": { "$source": "argv", "index": 0 }
    },
    "includeTests": {
      "type": "boolean",
      "description": "Include test files",
      "default": true
    },
    "database": {
      "type": "string",
      "enum": ["postgres", "mongodb", "none"],
      "default": "postgres"
    }
  },
  "required": ["name"]
}

Step 3: Create TypeScript Types

Edit generators/payment-service/schema.d.ts:

1
2
3
4
5
export interface PaymentServiceGeneratorSchema {
  name: string;
  includeTests?: boolean;
  database?: 'postgres' | 'mongodb' | 'none';
}

Step 4: Implement the Generator

Edit generators/payment-service/generator.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import {
  Tree,
  formatFiles,
  generateFiles,
  names,
  joinPathFragments,
} from '@nx/devkit';
import { PaymentServiceGeneratorSchema } from './schema';

export default async function paymentServiceGenerator(
  tree: Tree,
  options: PaymentServiceGeneratorSchema
) {
  const projectRoot = joinPathFragments('apps', options.name);
  const templateOptions = {
    ...options,
    ...names(options.name),
    tmpl: '',
  };

  generateFiles(
    tree,
    joinPathFragments(__dirname, './files'),
    projectRoot,
    templateOptions
  );

  await formatFiles(tree);
  
  return () => {
    console.log(`✅ Generated payment service: ${options.name}`);
  };
}

Step 5: Create Template Files

1
2
3
4
5
6
7
generators/payment-service/files/
├── src/
│   ├── index.ts__tmpl__
│   └── __fileName__.service.ts__tmpl__
├── __tests__/
│   └── __fileName__.service.spec.ts__tmpl__
└── package.json__tmpl__

Example template __fileName__.service.ts__tmpl__:

1
2
3
4
5
6
7
8
9
export class <%= className %>Service {
  constructor() {
    console.log('<%= name %> service initialized');
  }

  async processPayment(amount: number): Promise<void> {
    // TODO: Implement payment processing
  }
}

Step 6: Register the Generator

Edit generators/generators.json:

1
2
3
4
5
6
7
8
9
{
  "generators": {
    "payment-service": {
      "factory": "./src/generators/payment-service/generator",
      "schema": "./src/generators/payment-service/schema.json",
      "description": "Creates a new payment service"
    }
  }
}

Step 7: Test the Generator

1
2
3
4
5
# Dry run first
pnpm exec nx g ./generators:payment-service my-payments --dry-run

# Execute
pnpm exec nx g ./generators:payment-service my-payments

Step 8: Write Unit Tests

1
2
3
4
5
6
7
8
9
10
11
12
13
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import generator from './generator';

describe('payment-service generator', () => {
  it('should create service files', async () => {
    const tree = createTreeWithEmptyWorkspace();
    
    await generator(tree, { name: 'test-payment' });
    
    expect(tree.exists('apps/test-payment/src/index.ts')).toBeTruthy();
    expect(tree.exists('apps/test-payment/src/test-payment.service.ts')).toBeTruthy();
  });
});

Checklist