DEV Community

Cover image for Why I built a Clean Architecture Code Generator for NestJS
Maher Ben abdessalem
Maher Ben abdessalem

Posted on

Why I built a Clean Architecture Code Generator for NestJS

TL;DR: I built a Python CLI that generates enterprise NestJS (CQRS/DDD) and React backends from YAML. It's open-source on PyPI. Here is how I architected it.
You can check out the source code on GitHub or install the package directly from PyPI.
For Nest.js

GitHub logo maher1719 / Nestjs-clean-architecture-crud-modules-generator

πŸš€ Python CLI to auto-generate strict Clean Architecture, DDD, and CQRS modules for NestJS from YAML. Swagger & TypeORM ready.

PyPI version Python 3.10+ License: AGPL v3

πŸ—οΈ NestJS Clean Architecture CRUD Generator

Stop writing boilerplate. Generate production-grade, strictly-typed NestJS modules following Clean Architecture, Domain-Driven Design (DDD), and CQRS from a single YAML file.

Define your entity, run one command, and get a fully structured module with domain logic, CQRS handlers, TypeORM persistence, DTO validation, Swagger documentation, and REST controllers.


✨ Features

Feature Description
πŸ›οΈ Clean Architecture Strict domain / application / infrastructure / presentation layering.
⚑ CQRS Separate Commands & Queries with dedicated Handlers.
πŸ”— All 4 Relations many-to-one, one-to-many, one-to-one, many-to-many with proper FK ownership.
🧩 Aggregates (DDD) Parent validation, cascade enforcement, and auto-generated nested read routes.
πŸ”„ PUT vs PATCH Strict separation between full Replace and partial Update.
πŸ“Š Production Lists Built-in Pagination, Whitelisted Filtering, and Sorting (v3).
πŸ“„ Swagger & Validation Auto-generated class-validator DTOs and @nestjs/swagger decorators.
πŸ—‚οΈ Module Manifest .generator.manifest.json tracks every generated module for easy
…

Why choose Clean Architecture

Let's be honest, clean architecture is a good choice when you want to make maintainable, scalable, and predictable software. It has separation of concerns, modularity, and an onion-layered architecture so that one can extend the project's tools and infrastructure. It is just good for not spaghettifying your software and locking it into dependencies you cannot remove without breaking the whole project.

However, it comes with a verbose and tiresome setup. For each entity you make, you need to make many different folders and files with certain exact names. A CRUD-heavy app will probably need more setup time, effort, and verification of folder and file names than the actual code without clean architecture. Also, even with a complex enterprise app, we could have at least ten or more entities, each one coming with its own setup of, again, folders and files, and when you repeat that, you will discover you just most of the time need to copy, paste, and change names...

That is a major hurdle: you spent a great amount of time on setup more than needed and testing the suitability of what you did, giving complex logic and new features less and less time.

However, if we repeat and copy-paste most of the CRUD setup, why not automate it? Automating the boring and tiresome hurdle, ensuring that, with careful design of the automation, it is predictable and the same across the different entities; That will mean fewer bugs: fix one, fix for all, homogeneous, all automated, are the same; no ninja or lazy special code for just one case that is hard to debug or maintain; all team members who generate will have the same structure of code... And most of all, more time to focus on the complex, fun things.

Since I started working with clean architecture, I was impressed by how ordered and clean it is, but I was tired of repetitive setup. That's why I saw the opportunity to automate the CRUD operations with code that will generate entities, their CRUD operations, and relationships.

Building the NestJS Automation

I first made a NestJS Clean Architecture code generator that handles the creation of entities with their folders and files. then I made a template of files and, inside them, the variable that need to change (e.g entity name, entity properties...), and the content of each file, since it is the same structure for each file type; only known variables need to change. It was really a blessing.
here the template tree:

.
β”œβ”€β”€ application
β”‚Β Β  β”œβ”€β”€ commands
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ create-entity
β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ create-entity.command.ts.tpl
β”‚Β Β  β”‚Β Β  β”‚Β Β  └── create-entity.handler.ts.tpl
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ delete-entity
β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ delete-entity.command.ts.tpl
β”‚Β Β  β”‚Β Β  β”‚Β Β  └── delete-entity.handler.ts.tpl
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ replace-entity
β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ replace-entity.command.ts.tpl
β”‚Β Β  β”‚Β Β  β”‚Β Β  └── replace-entity.handler.ts.tpl
β”‚Β Β  β”‚Β Β  └── update-entity
β”‚Β Β  β”‚Β Β      β”œβ”€β”€ update-entity.command.ts.tpl
β”‚Β Β  β”‚Β Β      └── update-entity.handler.ts.tpl
β”‚Β Β  └── queries
β”‚Β Β      β”œβ”€β”€ get-entity
β”‚Β Β      β”‚Β Β  β”œβ”€β”€ get-entity.handler.ts.tpl
β”‚Β Β      β”‚Β Β  └── get-entity.query.ts.tpl
β”‚Β Β      └── list-entity
β”‚Β Β          β”œβ”€β”€ list-entity.handler.ts.tpl
β”‚Β Β          └── list-entity.query.ts.tpl
β”œβ”€β”€ domain
β”‚Β Β  β”œβ”€β”€ entities
β”‚Β Β  β”‚Β Β  └── entity.entity.ts.tpl
β”‚Β Β  └── repositories
β”‚Β Β      β”œβ”€β”€ entity.repository.ts.tpl
β”‚Β Β      └── list-options.ts.tpl
β”œβ”€β”€ infrastructure
β”‚Β Β  └── persistence
β”‚Β Β      β”œβ”€β”€ mappers
β”‚Β Β      β”‚Β Β  └── entity.persistence-mapper.ts.tpl
β”‚Β Β      └── typeorm
β”‚Β Β          β”œβ”€β”€ entity.orm-entity.ts.tpl
β”‚Β Β          └── entity.typeorm-repository.ts.tpl
β”œβ”€β”€ module.ts.tpl
└── presentation
    β”œβ”€β”€ controllers
    β”‚Β Β  β”œβ”€β”€ entity.controller.ts.tpl
    β”‚Β Β  └── nested-list.controller.ts.tpl
    └── dto
        β”œβ”€β”€ create-entity.dto.ts.tpl
        β”œβ”€β”€ list-entity.dto.ts.tpl
        β”œβ”€β”€ replace-entity.dto.ts.tpl
        └── update-entity.dto.ts.tpl

20 directories, 25 files
Enter fullscreen mode Exit fullscreen mode

That is 20 directories and 25 files generated! And also 45 times you click "new folder" or "new file" and write the name! Imagine you have at least 10 entities; it will be 450 times. If we take 30 seconds to set up a single entity for each new file or folder, we need at least 22.5 minutes; with 10 entities, that's at least 3 hours 45 minutes just for basic setup! And 20 entities will be 7.5 hours, an entire workday of non-stop, flawless creation, without even writing any code. That alone is the most tedious.

Also, for the generator to know what entities and their properties, I made it read Ya YAMLfile so we can keep track of what we will generate or update it easily. Here is an example of a comment entity:

# modules/comment.yml
module: comments
parent: posts                 
entity:
  name: Comment
  table: comments
  fields:
    - name: text
      type: string
    - name: postId
      type: uuid
      filterable: true 
    - name: authorId
      type: uuid
      filterable: true  #For sorting
relations:
  # This is the AGGREGATE parent (must be cascade + non-nullable)
  - name: post
    type: many-to-one
    target: Post
    targetModule: posts
    foreignKey: postId
    nullable: false
    onDelete: cascade

  # This is just an association (also gets a nested GET, but not the "parent")
  - name: author
    type: many-to-one
    target: User
    targetModule: users
    foreignKey: authorId
    nullable: false
    onDelete: cascade

operations:
  - create
  - list
  - get
  - update
  - delete
Enter fullscreen mode Exit fullscreen mode

Here we have an example of an entity specification file: the name of the module, the parent name when it has aggregation, then the entity name and its database table name, its fields, its relationships, and its operations...

We have enough information to make CRUD modules and write the files content. Also, when we delete, what will happen to other related entities? It follows database operations.

For the generator itself, it is written in Python; here is the Python file tree

generator/
β”œβ”€β”€ structure/
β”‚   β”œβ”€β”€ cli.py                 # Entry point (single + bulk)
β”‚   β”œβ”€β”€ config_loader.py       # YAML loading + aggregate validation
β”‚   β”œβ”€β”€ generator.py           # Orchestrates file writing
β”‚   β”œβ”€β”€ context.py             # Builds the template context
β”‚   β”œβ”€β”€ output_mapper.py       # Template β†’ output path mapping
β”‚   β”œβ”€β”€ manifest.py            # .generator.manifest.json upsert
β”‚   β”œβ”€β”€ app_module_updater.py  # Idempotent app.module.ts registration
β”‚   β”œβ”€β”€ fields/                # Field builder package
β”‚   β”‚   β”œβ”€β”€ normalization.py   #   normalize fields/relations
β”‚   β”‚   β”œβ”€β”€ types.py           #   ts_type, typeorm_type
β”‚   β”‚   β”œβ”€β”€ domain.py          #   entity/command/handler builders
β”‚   β”‚   β”œβ”€β”€ dto.py             #   DTO + validator + swagger builders
β”‚   β”‚   β”œβ”€β”€ orm.py             #   ORM columns + imports
β”‚   β”‚   β”œβ”€β”€ mapping.py         #   domain ↔ ORM field mapping
β”‚   β”‚   └── registry.py        #   build_field_context orchestrator
β”‚   └── relations/             # Relation builders
β”‚       β”œβ”€β”€ many_to_one.py
β”‚       β”œβ”€β”€ one_to_many.py
β”‚       β”œβ”€β”€ one_to_one.py
β”‚       β”œβ”€β”€ many_to_many.py
β”‚       └── registry.py        # relation dispatcher
└── templates/                 # .tpl template sources
Enter fullscreen mode Exit fullscreen mode

With that, it will handle folder and file creation and code generation, support relations, and handle pagination and sorting.

For the command to generate the modules, either specific ones or bulk generation, it will skip files that have already been generated, or you could force generation with --force:

# Generate a single module
python -m generator.structure.cli modules/comment.yml --src src/

# Bulk generate an entire folder
python -m generator.structure.cli modules/ --src src/ --force 
Enter fullscreen mode Exit fullscreen mode

And with that, you could have the full project CRUD generated, ready for migration and already with Swagger!

Disclaimer

While all this is good on day one, it is recommended to add any business custom logic outside the folders generated, so you can preserve your custom work!

Also, automated testing files for generated code are not yet implemented, since I prioritized building first, but everything works with manual testing.

What next?

I also made a React frontend counterpart of the generator that accords with our backend generator! I will write the next as an article!

Conclusion

Clean Architecture is very powerful, yet its setup overshadows its long-term benefits; with this generator, we could bypass its disadvantages to our advantage.

If you're a CTO or engineering manager who values strict DDD and automated developer experience, I'm currently open to remote backend/full-stack roles. The generator is open-source on PyPI β€” try it out, and if it saves your team hours, let's talk.

What do you think? What architecture do you use most in your project, and how do you handle the least favourite parts? Share with us in a comment so we can benefit each other!

Top comments (0)