Speeding Up Development with Nx Generators
Tags: nxmonorepo, typeorm, nestjs
Published: 2023-06-15 18:21:23

---
title: Speeding Up Development with Nx Generators
published: true
date: 2023-06-15 18:05:11 UTC
tags: NxMonorepo,TypeORM,NestJS
canonical_url: https://thefullstack.engineer/speeding-up-development-with-nx-generators/
---

 ![Speeding Up Development with Nx Generators](https://thefullstack.engineer/content/images/2023/06/Speeding-Up-Development.png)

In today's fast-paced software development landscape, efficiency and automation are paramount. With the advent of monorepos and the growing complexity of projects, tools like Nx have emerged as powerful allies for managing codebases efficiently. It's no secret that I'm a massive advocate of Nx, and I recently started taking advantage of custom Nx plugins to streamline development efforts. I created [@nx-fullstack](https://github.com/nxfullstack?ref=thefullstack.engineer) packages to share some of these, but I wanted to share a generator I made for a personal project I'm working on.

If you want to skip the reading and see the code behind this article, it's available in a gist [here](https://gist.github.com/wgd3/e86c6ebcf72fdb9f19582c4192a2f376?ref=dev.to)

## Project Structure

My project aims to provide an API and a web application to track fitness data and log measurements such as body weight, calories consumed, etc. I knew this would not be a small codebase, so I wanted to ensure my repository structure was clean and logically defined. In the past, I've embraced design patterns such as Domain Driven, Hexagonal, and Onion designs. While my current repository structure doesn't fully conform to any of these ideas, I settled on a pattern that seems to work for me:

```shell
├── libs
│ ├── server
│ │ ├── core
│ │ │ ├── application-services
│ │ │ ├── domain
│ │ │ └── domain-services
│ │ ├── infrastructure
│ │ ├── shell
│ │ ├── ui-cli
│ │ ├── ui-rest
│ │ ├── util-config
│ │ └── util-testing
│ ├── shared
│ │ ├── domain
```

## Automated Entity Creation

One downside of this structure is that for each "entity" (most of which represent a single table in the database), the following needs to be created:

- Shared interface defining the core and required properties when creating a new instance
- An abstract class that acts as an interface to the entity's "repository."
- A NestJS service that uses a repository to manipulate the entity
- An actual entity definition for TypeORM
- An implementation of the abstract repository base class
- A NestJS controller that exposes CRUD endpoints and uses the associated service to perform operations

The above requirements result in a lot of boilerplate code, and after the first five repetitions of this sequence, I decided to devote development time to a generator instead. In addition to creating the above code, this generator's goal was to update barrel file exports and add imports to NestJS modules.

## Generating A Custom Plugin

Nx generators have to be part of an Nx Plugin, which will be an additional library in the repository that doesn't belong to a specific application "domain."

```shell
# install the Nx package needed for plugin development
$ yarn add -D @nx/plugin

# generate a new plugin library to which the generator will be added
$ nx generate @nx/plugin:plugin CrudEntityCreator \
--importPath=@myapp/plugins/crud-entity-creator

# generate a generator
$ nx generate @nx/plugin:generator typeorm-entity-creator \
--project=plugins-crud-entity-creator \
--description='Generates all needed files for new TypeORM entites'
```

There was no intention of making this a publishable library, and as such, you'll see that I've hardcoded almost every file path in the templated files. I want to update this to _make_ it publishable and adaptable for other projects, but we'll save it for a future article.

## Creating File Templates

Each bullet point above references a class or interface, and each one requires a dedicated file. The templates are relatively simple, thanks to a standardized naming scheme. For instance, almost every interface in the repository follows the naming pattern `I<ModelName>`. Templating an interface that belongs to a user looks like this:

```typescript
import {IBaseModel} from './base.model';
import {IUserModel} from './user.model';

export interface I<%=className%>Relations {
    user?: IUserModel;
}

export interface I<%= className %> extends IBaseModel {
    userId: string;
}

export type ICreate<%= className %> = Omit<I<%=className%>, keyof IBaseModel>;
export type IUpdate<%= className %> = Partial<ICreate<%= className %>>;
```

All templates are located under the `files` directory, next to the generator code:

```shell
$ tree libs/plugins/crud-entity-creator/src/generators/typeorm-entity/files

libs/plugins/crud-entity-creator/src/generators/typeorm-entity/files
└── libs
    ├── server
    │   ├── core
    │   │   ├── application-services
    │   │   │   └── src
    │   │   │       └── lib
    │   │   │           └── __fileName__.service.ts.template
    │   │   └── domain-services
    │   │       └── src
    │   │           └── lib
    │   │               └── repositories
    │   │                   └── __fileName__.repository.ts.template
    │   ├── infrastructure
    │   │   └── src
    │   │       └── lib
    │   │           ├── entities
    │   │           │   └── __fileName__.orm-entity.ts.template
    │   │           └── repositories
    │   │               └── __fileName__.orm-repository-adapter.ts.template
    │   └── ui-rest
    │       └── src
    │           └── lib
    │               ├── controllers
    │               │   └── __fileName__.controller.ts.template
    │               └── dtos
    │                   └── create-__fileName__.dto.ts.template
    └── shared
        └── domain
            └── src
                └── lib
                    └── models
                        └── __fileName__.model.ts.template

25 directories, 7 files

```

The `className`  and `fileName` references come from the generator code, where the `names` utility from `@nx/devkit` creates variations of a passed string. The generator at this point is very straightforward:

```typescript
export async function typeormEntityGenerator(
  tree: Tree,
  options: TypeormEntityGeneratorSchema
) {
  const nameVariants = names(options.entityName);
  generateFiles(tree, path.join(__dirname, 'files'), '', { ...nameVariants });

  updateSourceFiles(tree, updates);
  await formatFiles(tree);
}

export default typeormEntityGenerator;
```

For every template found under the `files` directory, render the template and save it to the filesystem.

## Updating Exports and Imports

Templating files is easy, but programmatically updating Typescript files is a little more challenging. Files such as `shell.module.ts` and `db.module.ts` have array variables that reference our entities and their scaffolding:

```typescript
const entities: EntityClassOrSchema[] = [
  // all database entities get declared here
];
const typeormModule = TypeOrmModule.forFeature(entities);
```
_libs/server/shell/src/lib/db.module.ts_

I needed a way to programmatically say, "In this file, find this specific array and add an element to it." Fortunately, I found an existing library for this: [ts-morph](https://github.com/dsherret/ts-morph?ref=thefullstack.engineer). It's a "TypeScript Compiler API wrapper" which offers a way to manipulate Typescript code natively instead of directly accessing/parsing lines in a file.

`ts-morph` made adding exports extremely easy:

```typescript
    const updates: FileUpdates = {
    ['libs/shared/domain/src/lib/models/index.ts']: (
      sourceFile: SourceFile
    ) => {
      sourceFile.addExportDeclaration({
        moduleSpecifier: `./${nameVariants.fileName}.model`,
      });
    }
    }
```

**Note:**`FileUpdates` is not part of `ts-morph`, but is a helper type that relies on `SourceFile` from `ts-morph`. See the section at the end of the article for more on this.

Updating arrays, however, proved a bit more troublesome. Here's a snippet of the code needed to update `shell.module.ts`:

```typescript
    ['libs/server/shell/src/lib/server-shell.module.ts']: (
      sourceFile: SourceFile
    ) => {
      // make sure our application service is imported
      sourceFile.addImportDeclaration({
        moduleSpecifier: `@myapp/server/core/application-services`,
        namedImports: [`${nameVariants.className}Service`],
      });
      
      // attempt to find the definition of the applicationServices array
      const serviceArray = sourceFile
        .getDescendantsOfKind(SyntaxKind.ArrayLiteralExpression)
        .find(
          (n) =>
            n.getText().includes('Service') &&
            !n.getText().includes('RepositoryAdapter')
        )
        .asKind(SyntaxKind.ArrayLiteralExpression);
        
      // add the reference for our application service to the array
      serviceArray.addElement(`${nameVariants.className}Service`);
    }
```

## Using The Generator

After a few hours of learning about `ts-morph` and testing my generator, it was time to put it to use. Here's the output from the CLI:

```shell
> nx g @myapp/plugins/crud-entity-creator:TypeormEntity UserProfile

> NX Generating @myapp/plugins/crud-entity-creator:TypeormEntity

CREATE libs/server/core/application-services/src/lib/user-profile.service.ts
CREATE libs/server/core/domain-services/src/lib/repositories/user-profile.repository.ts
CREATE libs/server/infrastructure/src/lib/entities/user-profile.orm-entity.ts
CREATE libs/server/infrastructure/src/lib/repositories/user-profile.orm-repository-adapter.ts
CREATE libs/server/ui-rest/src/lib/controllers/user-profile.controller.ts
CREATE libs/server/ui-rest/src/lib/dtos/create-user-profile.dto.ts
CREATE libs/shared/domain/src/lib/models/user-profile.model.ts
UPDATE libs/shared/domain/src/lib/models/index.ts
UPDATE libs/server/core/domain-services/src/lib/repositories/index.ts
UPDATE libs/server/core/application-services/src/index.ts
UPDATE libs/server/infrastructure/src/lib/entities/index.ts
UPDATE libs/server/infrastructure/src/lib/repositories/index.ts
UPDATE libs/server/shell/src/lib/db.module.ts
UPDATE libs/server/shell/src/lib/server-shell.module.ts
UPDATE libs/server/ui-rest/src/lib/server-ui-rest.module.ts
```

And the output from the ORM entity template:

```typescript
import { Column, Entity } from 'typeorm';

import { IUserProfile } from '@myapp/shared/domain';

import { BaseOrmEntity } from './base.orm-entity';

@Entity('UserProfile')
export class UserProfileOrmEntity
  extends BaseOrmEntity
  implements IUserProfile
{
  @Column({
    type: String,
  })
  userId!: string;
}

```

## Summary

As I reflect on my journey with custom Nx generators in my monorepo project, I am amazed at the automation and efficiency they have brought to my development workflow. While the specifics of my tool may be unique to my project, I encourage you to draw inspiration from this experience and explore the vast possibilities of custom Nx generators in your own software development endeavors. By embracing this powerful tool, you can unlock new productivity levels, streamline repetitive tasks, and pave the way for a more efficient and enjoyable coding experience. Let my journey be your catalyst for innovation and exploration in your projects.

## Acknowledgments 

- [NX - How To Write a Generator](https://ngserve.io/nx-how-to-write-a-generator/?ref=thefullstack.engineer)
- Nx documentation on [Creating Plugins](https://nx.dev/packages/plugin/generators/plugin?ref=thefullstack.engineer)
- An excellent example repository (and documentation!) for Domain Driven Design: [sairyss/domain-driven-hexagon](https://github.com/Sairyss/domain-driven-hexagon/tree/master?ref=thefullstack.engineer#architecture)