Many popular projects and companies have greatly implemented Microservices architecture. One of its benefits is that we can create a service with a single responsibility for specific business logic. So whenever the business has a new feature, we can create a new service loosely coupled with other microservices.
Another benefit of microservices is that we can deploy them independently. It means we can use whatever languages or frameworks that a service sees fit. But in practices, companies usually define a default language/framework for their microservices. This strategy enables engineers to build a service quickly, and it is useful to increase productivity and maintainability with limited engineer resources.
When the need for new services is high, engineers will have to write many boilerplate codes of the same framework. Therefore, we can create a code generator to reduce repetition, so engineers can focus only on developing microservice's business logic. We can also establish a code convention by defining the folder structure and design pattern within its template and workflow with a code generator.
Getting started with Yeoman
npm install -g yo
After you’ve installed it, you can run Yeoman by the command
yo and it will list all the globally installed generators. For example, you can try a generator I have built to generate a microservice with Express.js and Typescript by cloning the repo below, then run
npm link in the directory,
A small generator for Express.js microservices with Typescript and PostgreSQL.
Now you can run the generator with
yo ts-express. And the prompts below will be shown to ask for your customization.
The process will then continue to writing boilerplate files from templates and installing dependencies until the message below is displayed.
Creating a new generator
A Yeoman generator is simply a Node.js module. To create a generator, you have to create a new node project by running
npm init and set the name with
generator- prefix (e.g.
A generator requires Yeoman dependency, which you can install by running
npm install yeoman-generator. Yeoman will automatically detect the main generator and sub-generators if you follow the folder structures below.
package.jsonis the NPM manifest file for the generator project.
generators/app/index.jsis where the main generator workflow is written. It can be executed by running
generators/route/index.jsis where the sub-generator workflow is written. It can be executed by running
In the generator, you should create a class that extends the Yeoman base generator. Then we can define the tasks by creating methods inside the class.
Each task will run in sequence according to the order in which it is written. You can name your methods anything, or you can follow the reserved Yeoman method names.
Yeoman use these method names to keep task priority in sequence; the names in order are
end. If your method names don’t match the reserved names, they will run under the
default task priority.
💡 Pro Tip
Name a method with underscore prefix (
_helper_method) to create a helper method which won’t be listed as a task
Interacting with users
The Yeoman generator we are building is a CLI app, so users should interact or define their customization via terminal. Yeoman provides three approaches for user interaction; they are:
- Arguments (
yo microservice [argument])
Arguments are passed directly after the generator command. To receive an argument, we must call
this.argument(<name>, <options>)in the class constructor. Then we can get the data from
- Options (
yo microservice --[option])
Options are passed as command-line flags. Similar to arguments, we must call
this.option(<name>, <options>)in the constructor. Then it will be accessible from
Prompts are a series of questions asked to the user in sequence. It is the most recommended approach for user interaction as it has a better user experience and supports a conditional question. It also supports multiple question types like radio, checkbox, etc., as it is based on Inquirer.js.
We can call
this.prompt()which receives an array of prompts as an argument. It is asynchronous, and we should call it in the
promptingtask. When the user has finished answering the prompts, the method will return the answers object.
Here is an example of using argument, option, and prompt methods to receive the
name of the microservice.
💡 Pro Tip
To give better experience for the user, prompts can display multiple types of question, conditional question, and answer validation. You can follow the documentation here.
Configuring user customization data
After we received the user’s customization data, we might need to process and configure them before using them further in other tasks. Common tasks to be handled in the configuration step:
- Create new variables based on user data to be passed to template files.
- Combine user data from arguments, options, and answers.
- Save user answers and config data in Yeoman config file (
.yo-rc.json) to be used for future execution or from other generators/sub-generators.
We can code these tasks in the
configuring method like below.
With this method, a
.yo-rc.json file will be generated inside the project, which will look like this.
Creating template files
In our generator project, we can put template files inside a generator or sub-generator
templates directory like below.
│ │ ├───index.js
│ │ └───package.json
A template file can be any file with any extension. Besides boilerplate codes, we can also put project config and manifest files as templates.
Yeoman uses EJS for its templating syntax. With EJS, we can compile a JS code in a template. So we can pass variables or have conditional writing in the template. Here is an example of EJS usage in a
.env file template, which is a microservice environment variables file.
In the file above, we can easily write a value from a variable with EJS syntax
<%=. We can also see that the database config block (line 9–16) will only be written when
hasDb variable resolve to
💡 Pro Tip
We can use
<%_ _%>instead of
<% %>to clear out whitespace (empty line) around it.
Generating boilerplate from templates
For this step, we can finally generate the microservice boilerplate from our template files. We only need to copy the files from the templates directory into the target directory while passing variables that we have received and configured from the user. To do this, we can code inside the
writing task priority like below.
As we can see, we are using Yeoman’s built-in method to copy template files
this.fs.copyTpl that receive three params: source path, target path, and template variables. Yeoman also provide path resolver functions to get the current generator’s templates path (
this.templatePath) and its target path (
Setting up the generated microservice for running
After generating the microservice boilerplate project, we can install the dependencies needed to start running in this step. From what I learned, we have two different approaches to manage dependencies of the generated microservice,
- Install dependencies with exact versions pre-defined by the generator. With this approach, we can create the
package.jsonfile as a template where all the dependencies are defined. So we will only call
- Install dependencies with the latest versions every time the generator runs. To do this, we can pass a list of dependency names to be passed to
this.npmInstallfunction as a param.
Using the second approach, you can see the example of implementing it in the
install task below.
With the example above, we can differentiate dependencies into dev dependencies to be installed separately. We can also code the
end task to print a message to the user indicating the generator process has successfully ended.
Creating and composing a sub-generator
Now that we have finished creating the main microservice generator, we can create a sub-generator. It is useful to generate an app component in an existing project like routes, data model, service, etc.
We can also create a sub-generator to make the generator more modularized, as it can be composed with the main generator. Here is an example of a sub-generator module for creating a route/endpoint in a microservice.
As we can see, the sub-generator module has the same concept as the main generator. It has user interaction, configuring data, and copying template tasks. Therefore we can combine it with our main generator to run their tasks in the same order. To do this, we only need to call
this.composeWith()function in the
initializing task like below.
💡 Pro Tip
You can use Yeoman config file
.yo-rc.jsonto combine data from both generators.
I hope now you can try to create your own microservice generator with Yeoman. I am sure it worth the extra effort to help increase the velocity of your team.
Feel free to contribute to my example repository. You can also ask me anything in there or via comments in this article.