Skip to content

Latest commit

 

History

History
290 lines (246 loc) · 6.75 KB

README.md

File metadata and controls

290 lines (246 loc) · 6.75 KB

Nest Logo

What is boilerplate code?

Boilerplate code is computer language text that you can reuse with little or no alteration in several different contexts.

Description

This is a boilerplate using NestJS, a popular NodeJS framework, reducing time to write CRUD method by generating code.

Installation

$ git clone https://github.com/duyanhitbe/nestjs-boilerplate.git

$ cd nestjs-boilerplate

Now, you can choose kind of ORM you want to use.

  • Typeorm for SQL (typeorm)
  • Mongoose for MongoDB (mongoose)

And, you can choose what type of API (Application Programming Interface):

  • RESTful (rest)
  • GraphQL (graphql)

Example:

$ git checkout rest/typeorm

$ pnpm install

# Install pnpm if you haven't
$ npm i -g pnpm

Environment

$ cp .env.example .env

Then replace your owner config

Running

# dev mode
$ pnpm start:dev

# prod mode
$ pnpm build
$ pnpm start:prod

# pm2
$ pm2 start ecosystem.config.js

Testing

# Unit test
$ pnpm test

# End-to-End test
$ pnpm test:e2e

Validate environment config

Using joi package to validate env variable. Example: src/modules/config/config.module.ts

@Module({
	imports: [
		NestConfigModule.forRoot({
			isGlobal: true,
			envFilePath: '.env',
			validationSchema: Joi.object({
				NODE_ENV: Joi.string().required(), //<- Must be a string and required
				PORT: Joi.number().required() //<- Must be a number and required
			})
		})
	]
})
export class ConfigModule {}

Generating

Strong of this boilerplate code that you can generate CRUD module quickly with one command.

$ make crud name=example

? What transport layer do you use? (Use arrow keys)
❯ REST API #<-- Choose this one if you are using Restful
  GraphQL (code first) #<-- Choose this one if you are using GraphQL
  GraphQL (schema first) 
  Microservice (non-HTTP) 
  WebSockets

? Would you like to generate CRUD entry points? (Y/n) #<--Yes or Y

Migration

If you're using TypeORM for your project, you can create or generate migration to run for SQL.

generate-migrations

$ make generate-migration name=migration_name

create-migration

$ make create-migration name=migration_name

When you run one of two command abort, it will generate a new file in src/modules/database/migrations. Let's import it to DatabaseModule

@Module({
	imports: [
		TypeOrmModule.forRootAsync({
			inject: [ConfigService],
			useFactory: (configService: ConfigService) => ({
				migrations: [
		                  User1706412751363, 
		                  Book1707635652785
		                  NewMigrationFile //<-- Right here
		                ]
			})
		})
	]
})
export class DatabaseModule {}

run-migration

Now, you can run that migration by

$ make run-migration

Or just start the server, migration will run automatically

revert-migration

$ make revert-migration

JsonWebToken

Base on package @nestjs/jwt, you can inject JwtService through its interface. Example:

import { Injectable } from '@nestjs/common';
import { IJwtService } from '@modules/jwt';

@Injectable()
export class FooService {
	constructor(private readonly jwtService: IJwtService) {}
	
	async login(username: string, password: string) {
		//Validate user
		...
		return this.jwtService.sign({ username });
	}
}

Cache

Using redis from ioredis package to improve your application

import { Injectable } from '@nestjs/common';
import { IRedisService } from '@modules/redis';

@Injectable()
export class BarService {
	constructor(private readonly redisService: IRedisService) {}
	
	async getHugeData() {
		//Get data from cache
		const key = 'HUGE_DATA';
		const cachedData = await this.redisService.get(key);
		if (cachedData) {
			return cachedData;
		}
		
		const expireTime = 60 * 60; //1m
		const veryHugeData = getData();
		//Cache data
		this.redisService.set(key, veryHugeData, expireTime);
		return veryHugeData;
	}
}

Schedule

Using @nestjs/schedule you can run multiple cron-job in CronService

import { Injectable, Logger } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';

@Injectable()
export class CronService {
	private readonly logger = new Logger(CronService.name);
	
	/* When the number of second is 45, this method will be executed */
	@Cron('45 * * * * *') 
	helloWorld() {
		console.log('Hello World');
	}
}

Check this link to more information

I18N

Base on package nestjs-i18n to translate multiple language. Let's check it out

src/modules/i18n/i18n.module.ts

@Module({
	imports: [
		I18nModule.forRoot({
			// Default language
			fallbackLanguage: Language.VI,
			// Load JSON file
			loaderOptions: {
				path: join(__dirname, '/translations/'),
				watch: true
			},
			// Resolver that I18nModule can verify which language do you want to use?
			// In this case, that is pass "x-lang" into header
			resolvers: [new HeaderResolver(['x-lang'])], 
			// Path to generate ts file
			typesOutputPath: join(
				__dirname,
				'/../../../../src/modules/i18n/generated/i18n.generated.ts'
			)
		})
	]
})
export class I18NModule {}

src/modules/i18n/translations/vi/errors.json

{
    "WRONG_PASSWORD": "Wrong password"
}

Example:

import { Injectable } from '@nestjs/common';
import { I18nService } from 'nestjs-i18n';

@Injectable()
export class CronService {
	constructor(private readonly i18n: I18nService) {}
	
	getHello(): string {
		return this.i18n.t('test.WRONG_PASSWORD');
	}
}

Validation with class-validator

import { IsString, IsNotEmpty } from '@common'; // Import from '@common' not 'class-validator'

class CreateUserDto {
  @IsString()
  @IsNotEmpty()
  name!: string;
}

src/common/decorators/validation.decorator.ts

import { translate } from '@app/modules/i18n/i18n.helper';
import { applyDecorators } from '@nestjs/common';
import * as validator from 'class-validator';

export const IsString = (validationOptions?: validator.ValidationOptions) =>
	applyDecorators(
		validator.IsString({ ...validationOptions, message: translate('validation.IS_STRING') }) //Automatically translate in validation.json
	);

export const IsNotEmpty = (validationOptions?: validator.ValidationOptions) =>
	applyDecorators(
		validator.IsNotEmpty({
			...validationOptions,
			message: translate('validation.IS_NOT_EMPTY')
		})
	);

Stay in touch