How To Make Starting A Project Fun Again… With Fire!.png

How To Make Starting A Project Fun Again… With Fire!

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.

Code generator

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.

Defining custom generators

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:

  • header – for the rules for the generator such as the destination of the file;
  • body – for the code that will be generated in the file.

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.

Technologies and features

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:

  • Back end
  • mongoose-autopopulate – for automatically aggregating referenced fields 
  • mongoose-paginate-v2 – for paginated responses
  • multer – for handling multipart/form-data, which is primarily used for file uploading
  • nodemailer – for sending emails
  • stripe – for payments
  • Front end
  • redux – for state management
  • moment – for working with time/date
  • axios – for API calls
  • react-hook-form – for working with forms
  • react-dropzone – for file uploads

The template provides many features out of the box such as:

  • user management
  • role management
  • settings management
  • media uploads
  • payments with Stripe
  • audits
  • locale support
  • workspaces
  • … and many other things.

Back end structure

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:

  • components – contains the building blocks for the UI of the application. It contains the buttons, datepickers, tables, header, inputs, dropdowns and many more essential components;
  • containers – responsible to fetch the data, organize it and render the corresponding sub-components; all the state management with Redux is defined here because the container is responsible for managing the state;
  • layouts – components that result in some form of a static DOM element; might not need to be updated frequently; used for routing us to the corresponding container;
  • routes – for defining which container corresponds to which route; done for every layout;
  • repository – for defining the API calls according to the actions from the containers; separated by the domain they cover (for example, in user.ts we define all the calls for user-related actions);
  • services – for defining our helper functions; separated by the domain they cover (for example, the helpers for managing permissions will be stored in permissions.ts);
  • theme – consisting of two files (the theme.ts file that contains the theme definition for the Material-UI library and the main.scss file for defining the styles that are not covered by Material-UI or for some overrides that we want to integrate);
  • reducers.ts – this is where we register all our reducers from the containers;
  • store.ts – for Redux configuration;

index.tsx – application root.

How To Make Starting A Project Fun Again… With Fire! 2.png

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

Jun 24, 2021

Category

Article

Technologies

JavaScriptReactNestjs

Let's talk.

Name
Email
Phone
By submitting this, you agree to our Terms and Conditions & Privacy Policy