feat: update boilerplate

add swagger
add correlation id
add health check for internet connectivity and database
update to latest nestjs version
remove clutter from http log
This commit is contained in:
Hasta Ragil Saputra 2022-06-24 17:13:05 +07:00
parent e6f8c54d49
commit 0456cd57f4
10 changed files with 1486 additions and 1540 deletions

View File

@ -79,7 +79,7 @@ module.exports = {
'consistent'
],
'indent': [
'warn',
'off',
2
],
'linebreak-style': 'warn',

View File

@ -1,5 +1,5 @@
{
"name": "sppbe-gas-meter",
"name": "nestjs-boilerplate",
"version": "0.0.1",
"description": "",
"author": "",
@ -21,46 +21,50 @@
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@nestjs/common": "^8.4.0",
"@nestjs/config": "^1.2.0",
"@nestjs/core": "^8.4.0",
"@nestjs/axios": "^0.0.8",
"@nestjs/common": "^8.4.7",
"@nestjs/config": "^2.1.0",
"@nestjs/core": "^8.4.7",
"@nestjs/mapped-types": "*",
"@nestjs/platform-express": "^8.4.0",
"@nestjs/platform-fastify": "^8.4.0",
"@nestjs/typeorm": "^8.0.3",
"@nestjs/platform-express": "^8.4.7",
"@nestjs/platform-fastify": "^8.4.7",
"@nestjs/swagger": "^5.2.1",
"@nestjs/terminus": "^8.0.8",
"@nestjs/typeorm": "^8.1.4",
"class-transformer": "^0.5.1",
"class-validator": "^0.13.2",
"joi": "^17.6.0",
"nestjs-pino": "^2.5.0",
"nestjs-pino": "^2.6.0",
"pg": "^8.7.3",
"pino-http": "^6.6.0",
"pino-http": "^8.0.1",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.5.5",
"typeorm": "^0.2.45",
"typeorm-naming-strategies": "^3.0.0"
"swagger-ui-express": "^4.4.0",
"typeorm": "^0.3.6",
"typeorm-naming-strategies": "^4.1.0"
},
"devDependencies": {
"@nestjs/cli": "^8.2.2",
"@nestjs/schematics": "^8.0.8",
"@nestjs/testing": "^8.4.0",
"@nestjs/cli": "^8.2.8",
"@nestjs/schematics": "^8.0.11",
"@nestjs/testing": "^8.4.7",
"@types/express": "^4.17.13",
"@types/jest": "^27.4.1",
"@types/node": "^17.0.21",
"@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^5.14.0",
"@typescript-eslint/parser": "^5.14.0",
"eslint": "^8.10.0",
"@types/jest": "^28.1.3",
"@types/node": "^18.0.0",
"@types/supertest": "^2.0.12",
"@typescript-eslint/eslint-plugin": "^5.29.0",
"@typescript-eslint/parser": "^5.29.0",
"eslint": "^8.18.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "^27.5.1",
"prettier": "^2.5.1",
"supertest": "^6.2.2",
"ts-jest": "^27.1.3",
"ts-loader": "^9.2.8",
"ts-node": "^10.7.0",
"tsconfig-paths": "^3.13.0",
"typescript": "^4.6.2"
"jest": "^28.1.1",
"prettier": "^2.7.1",
"supertest": "^6.2.3",
"ts-jest": "^28.0.5",
"ts-loader": "^9.3.1",
"ts-node": "^10.8.1",
"tsconfig-paths": "^4.0.0",
"typescript": "^4.7.4"
},
"jest": {
"moduleFileExtensions": [

View File

@ -5,11 +5,40 @@ import * as Joi from 'joi';
import { UsersModule } from './users/users.module';
import { SnakeNamingStrategy } from 'typeorm-naming-strategies';
import { LoggerModule } from 'nestjs-pino';
import { HealthModule } from './health/health.module';
import configuration from './config/configuration';
@Module({
imports: [
LoggerModule.forRoot(),
LoggerModule.forRoot({
pinoHttp: {
genReqId: (req) => {
return req['x-correlation-id'];
},
redact: {
paths: [
'req.headers.authorization',
'req.headers["user-agent"]',
'req.headers.accept',
'req.headers["accept-encoding"]',
'req.headers["accept-language"]',
'req.headers.host',
'req.headers.connection',
'req.headers.cookie',
'req.headers["sec-ch-ua"]',
'req.headers["sec-ch-ua-mobile"]',
'req.headers["sec-ch-ua-platform"]',
'req.headers["upgrade-insecure-requests"]',
'req.headers["sec-fetch-site"]',
'req.headers["sec-fetch-mode"]',
'req.headers["sec-fetch-user"]',
'req.headers["sec-fetch-dest"]',
'req.headers["if-none-match"]',
],
remove: true,
},
},
}),
ConfigModule.forRoot({
load: [configuration],
validationSchema: Joi.object({
@ -38,13 +67,14 @@ import configuration from './config/configuration';
entities: [],
synchronize: true,
autoLoadEntities: true,
logging: true,
logging: false,
namingStrategy: new SnakeNamingStrategy(),
};
},
inject: [ConfigService],
}),
UsersModule,
HealthModule,
],
})
export class AppModule {}

View File

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { HealthController } from './health.controller';
describe('HealthController', () => {
let controller: HealthController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [HealthController],
}).compile();
controller = module.get<HealthController>(HealthController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@ -0,0 +1,32 @@
import { Controller, Get } from '@nestjs/common';
import {
HealthCheck,
HealthCheckService,
HttpHealthIndicator,
TypeOrmHealthIndicator,
} from '@nestjs/terminus';
@Controller('health')
export class HealthController {
constructor(
private health: HealthCheckService,
private http: HttpHealthIndicator,
private db: TypeOrmHealthIndicator,
) {}
@Get()
@HealthCheck()
check() {
return this.health.check([
() => {
return this.http.pingCheck(
'internet-connectivity',
'https://www.google.com',
);
},
() => {
return this.db.pingCheck('database');
},
]);
}
}

View File

@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { TerminusModule } from '@nestjs/terminus';
import { HealthController } from './health.controller';
import { HttpModule } from '@nestjs/axios';
@Module({
imports: [TerminusModule, HttpModule],
controllers: [HealthController],
})
export class HealthModule {}

View File

@ -1,22 +1,19 @@
import { NestFactory } from '@nestjs/core';
import {
FastifyAdapter,
NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Logger } from 'nestjs-pino';
import { CorrelationIdMiddleware } from './utils/correlation-id.middleware';
import { NestExpressApplication } from '@nestjs/platform-express';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
async function bootstrap() {
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter(),
{ bufferLogs: true },
);
const app = await NestFactory.create<NestExpressApplication>(AppModule);
const logger = app.get(Logger);
app.disable('x-powered-by');
app.use(CorrelationIdMiddleware());
app.useLogger(logger);
app.enableCors();
app.useGlobalPipes(
@ -25,16 +22,29 @@ async function bootstrap() {
}),
);
const config = new DocumentBuilder()
.setTitle('API Docs')
.setDescription('API description')
.setVersion('1.0')
.addTag('apidocs')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);
const configService = app.get<ConfigService>(ConfigService);
const port = configService.get<number>('port');
await app.listen(port, '0.0.0.0', (error, address) => {
if (error) {
logger.error(error);
process.exit(1);
} else {
logger.log(`Server listening on ${address}`);
}
const hostname = '0.0.0.0';
await app.listen(port, hostname, () => {
logger.log(`Server listening on ${hostname}:${port}`);
// if (error) {
// logger.error(error);
// process.exit(1);
// } else {
// logger.log(`Server listening on ${address}`);
// }
});
}

View File

@ -15,7 +15,11 @@ export class UsersService {
async create(createUserDto: CreateUserDto) {
const result = await this.usersRepository.insert(createUserDto);
return this.usersRepository.findOneOrFail(result.identifiers[0].id);
return this.usersRepository.findOneOrFail({
where: {
id: result.identifiers[0].id,
},
});
}
findAll() {
@ -24,7 +28,11 @@ export class UsersService {
async findOne(id: string) {
try {
return await this.usersRepository.findOneOrFail(id);
return await this.usersRepository.findOneOrFail({
where: {
id,
},
});
} catch (e) {
if (e instanceof EntityNotFoundError) {
throw new HttpException(
@ -42,7 +50,11 @@ export class UsersService {
async update(id: string, updateUserDto: UpdateUserDto) {
try {
await this.usersRepository.findOneOrFail(id);
await this.usersRepository.findOneOrFail({
where: {
id,
},
});
} catch (e) {
if (e instanceof EntityNotFoundError) {
throw new HttpException(
@ -57,14 +69,22 @@ export class UsersService {
}
}
const result = await this.usersRepository.update(id, updateUserDto);
await this.usersRepository.update(id, updateUserDto);
return this.usersRepository.findOneOrFail(id);
return this.usersRepository.findOneOrFail({
where: {
id,
},
});
}
async remove(id: string) {
try {
await this.usersRepository.findOneOrFail(id);
await this.usersRepository.findOneOrFail({
where: {
id,
},
});
} catch (e) {
if (e instanceof EntityNotFoundError) {
throw new HttpException(

View File

@ -0,0 +1,12 @@
import * as uuid from 'uuid';
export function CorrelationIdMiddleware() {
return (req, res, next: () => void) => {
const correlationHeader = req.headers['x-correlation-id'] || uuid.v4();
// eslint-disable-next-line no-param-reassign
req.headers['x-correlation-id'] = correlationHeader;
res.header('X-Correlation-Id', correlationHeader);
next();
};
}

2784
yarn.lock

File diff suppressed because it is too large Load Diff