Customers who trust us
Jun 24, 2021
The hardest and most tedious part of starting a new project has always been setting up the initial structure. The initial setup takes a lot of time and most things are more or less the same in most projects.
Let’s imagine we have an application that needs to be developed and should include user management (roles, authentication), a dashboard/landing page that displays some data/resources, maybe translations for multi-language support and much more.
Why do all of this from scratch when we can make a template that we can further modify according to our needs?
To answer this question, we came up with the idea of having a core template. In fact, this is how Firestarter was born. Firestarter is a monorepo template that uses NestJS for the back end, React (with Material-UI) for the front end and MongoDB as the database. It is meant to be the jump-start that every project needs. It comes with all the basic things we need out of the box such as user authentication and management, media upload and management, auditing and more.
Firestarter is not just an ordinary template, it comes with a generator for full routes (back end and front end) or just endpoints with pagination, locales and workspace already set up. To provide this, it uses hygen, a scalable code generator that lets us write templates in EJS and create our own generators, which is always helpful.
To generate a new API endpoint we just need to run the following command:
hygen api new
After executing it, you’ll be prompted to provide additional info like the entity's name associated with the endpoint, the fields of the entity and what optional features we want to use such as pagination and locales. On successful completion, it will generate a new module and import it into the app module.
There is another command to generate a page with a front-end route and a back-end endpoint using a model we define:
hygen page new
As previously mentioned, this command will ask to provide the name of the associated entity, the fields of the entity and the optional features. After that, it will generate the basic code to get us started.
When we want to define a generator we need to create a certain folder structure in the _templates folder at the root of the project. The folder structure will correspond to the command we will enter when we want to generate something.
For example, if we want the command to be:
hygen api new
the folder structure needs to resemble the following tree:
_templates
└── api
└── new
The generator consists of multiple templates. Each template corresponds to a file that will be created. The templates use EJS and consist of two parts:
Here is an example of a template:
---
to: src/app.module.ts
inject: true
skip: 'import { <%= name %>Module } from'
after: '// import line - do not remove or edit'
---
import { <%= name %>Module } from './<%= h.changeCase.headerCase(name) %>/<%= h.changeCase.headerCase(name) %>.module';
We can see that the header is separated with three dashes (—) and defines a couple of parameters. The parameters serve as rules for the generator when generating the code. For example, the to parameter defines the destination of the file, the after parameter defines where the body should be put in that file if it exists, and so on. Also, we can define a prompt that can ask for user input before generating. This can be used to give a name to the entity we are creating or define some kind of structure. The prompt is defined in a file called prompt.js in the same folder as the templates.
Here is an example of a prompt:
module.exports = [
{
type: "input",
name: "name",
message: "Enter a new entity name:"
},
{
type: "list",
name: "model",
message: "Please enter a data model:"
},
{
type: "multiselect",
name: "plugins",
message: "Select plugins",
choices: [
{ name: "locales", message: "Locales", value: "locales" },
{ name: "paginate", message: "Pagination", value: "paginate" }
]
}
];
As stated in the example above we can define inputs of different types. In the example, we defined three different inputs that will be available to us in the template as a property with the value name we provided here. The first is a normal text input that will be a string, the second is a list that will become an array (must be separated with**,**) and the third one is a ‘multi select’ that will return an array of the names of the items we selected.
The back-end application uses NestJS, Mongoose ODM and MongoDB and the front end uses React set up with TypeScript.
Other technologies we use are the following:
The template provides many features out of the box such as:
The back end uses NestJS and follows the framework philosophy. The architecture is heavily inspired by Angular. Organizing our code into distinct functional modules helps manage the development of complex applications and designs for achieving reusability. Modules encapsulate all the logic about a given domain.
Let’s say we need to implement user-related logic. We can create a UserModule that will contain UserService, UserController and a UserSchema. We can also leverage the fact that we use TypeScript by creating a DTO and class for this, which we can later use for strict type-checking. In the end, every entity that covers a certain domain has its own module with the following structure:
user
├── classes
│ └── user.class.ts
├── dto
│ └── user.dto.ts
├── user.module.ts
├── user.controller.ts
├── user.service.ts
└── user.schema.ts
Front end structure
The front end uses React. The application is set up in the following way:
src
├── components
│ └── Navbar
│ ├── Navbar.scss
│ └── Navbar.tsx
├── containers
│ └── User
│ ├── UserTypes.ts
│ ├── UserActions.ts
│ ├── UserReducer.ts
│ ├── User.tsx
│ ├── UserView.tsx
│ └── User.scss
├── layouts
│ └── AdminLayout
│ ├── AdminLayout.scss
│ └── AdminLayout.tsx
├── routes
│ └── AdminRoutes.ts
├── repository
│ └── user.ts
├── services
│ └── permissions.ts
├── theme
│ ├── main.scss
│ └── theme.ts
├── reducers.ts
├── store.ts
└── index.tsx
Now we will cover the structure including all the features listed in the previous tree:
Summary
Clients want the product to be developed in the shortest amount of time with the features they envisioned. With this application, we can deliver a product much faster because we don’t have to reinvent the wheel every time we start a new project.
Tanja Zlatanovska
Customers who trust us
Velimir Graorkoski
Velimir Graorkoski
Tanja Zlatanovska