Generate Microservices Quickly With Yeoman

A quick guide to creating a Yeoman generator for building microservices with an example of Express.js and Typescript app

Sena Aji Buwana
The Startup

--

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

Yeoman is a scaffolding tool that will help you kickstart new projects with any tech stacks. It is a Node.js module, and you can install it from npm:

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,

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 withgenerator- prefix (e.g. generator-microservice).

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.json
└───generators/
├───app/
│ └───index.js
└───route/
└───index.js
  • package.json is the NPM manifest file for the generator project.
  • generators/app/index.js is where the main generator workflow is written. It can be executed by running yo microservice.
  • generators/route/index.js is where the sub-generator workflow is written. It can be executed by running yo microservice:route.

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 initializing, prompting, configuring, default, writing, conflicts, install, and 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:

  1. 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 this.options[name].
  2. 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 this.options[name].
  3. Prompts
    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 prompting task. 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.

{
"generator-microservice": {
"name": "Example",
"title": "ms-example"
}
}

Creating template files

In our generator project, we can put template files inside a generator or sub-generator templates directory like below.

└───generators/
├───app/
│ ├───templates/
│ │ ├───index.js
│ │ └───package.json
│ └───index.js

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 true.

💡 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 (this.destinationPath).

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,

  1. Install dependencies with exact versions pre-defined by the generator. With this approach, we can create the package.json file as a template where all the dependencies are defined. So we will only call this.npmInstall() in the install task.
  2. 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.npmInstall function 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.json to 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.

References

--

--

Sena Aji Buwana
The Startup

Full-stack engineer at @jeniusconnect | Self-taught programmer | Always a beginner in React Native, Next.js, Node.js, GraphQL, and Typescript.