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:
parent
e6f8c54d49
commit
0456cd57f4
|
@ -79,7 +79,7 @@ module.exports = {
|
||||||
'consistent'
|
'consistent'
|
||||||
],
|
],
|
||||||
'indent': [
|
'indent': [
|
||||||
'warn',
|
'off',
|
||||||
2
|
2
|
||||||
],
|
],
|
||||||
'linebreak-style': 'warn',
|
'linebreak-style': 'warn',
|
||||||
|
|
60
package.json
60
package.json
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "sppbe-gas-meter",
|
"name": "nestjs-boilerplate",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
|
@ -21,46 +21,50 @@
|
||||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nestjs/common": "^8.4.0",
|
"@nestjs/axios": "^0.0.8",
|
||||||
"@nestjs/config": "^1.2.0",
|
"@nestjs/common": "^8.4.7",
|
||||||
"@nestjs/core": "^8.4.0",
|
"@nestjs/config": "^2.1.0",
|
||||||
|
"@nestjs/core": "^8.4.7",
|
||||||
"@nestjs/mapped-types": "*",
|
"@nestjs/mapped-types": "*",
|
||||||
"@nestjs/platform-express": "^8.4.0",
|
"@nestjs/platform-express": "^8.4.7",
|
||||||
"@nestjs/platform-fastify": "^8.4.0",
|
"@nestjs/platform-fastify": "^8.4.7",
|
||||||
"@nestjs/typeorm": "^8.0.3",
|
"@nestjs/swagger": "^5.2.1",
|
||||||
|
"@nestjs/terminus": "^8.0.8",
|
||||||
|
"@nestjs/typeorm": "^8.1.4",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.13.2",
|
"class-validator": "^0.13.2",
|
||||||
"joi": "^17.6.0",
|
"joi": "^17.6.0",
|
||||||
"nestjs-pino": "^2.5.0",
|
"nestjs-pino": "^2.6.0",
|
||||||
"pg": "^8.7.3",
|
"pg": "^8.7.3",
|
||||||
"pino-http": "^6.6.0",
|
"pino-http": "^8.0.1",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"rxjs": "^7.5.5",
|
"rxjs": "^7.5.5",
|
||||||
"typeorm": "^0.2.45",
|
"swagger-ui-express": "^4.4.0",
|
||||||
"typeorm-naming-strategies": "^3.0.0"
|
"typeorm": "^0.3.6",
|
||||||
|
"typeorm-naming-strategies": "^4.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nestjs/cli": "^8.2.2",
|
"@nestjs/cli": "^8.2.8",
|
||||||
"@nestjs/schematics": "^8.0.8",
|
"@nestjs/schematics": "^8.0.11",
|
||||||
"@nestjs/testing": "^8.4.0",
|
"@nestjs/testing": "^8.4.7",
|
||||||
"@types/express": "^4.17.13",
|
"@types/express": "^4.17.13",
|
||||||
"@types/jest": "^27.4.1",
|
"@types/jest": "^28.1.3",
|
||||||
"@types/node": "^17.0.21",
|
"@types/node": "^18.0.0",
|
||||||
"@types/supertest": "^2.0.11",
|
"@types/supertest": "^2.0.12",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.14.0",
|
"@typescript-eslint/eslint-plugin": "^5.29.0",
|
||||||
"@typescript-eslint/parser": "^5.14.0",
|
"@typescript-eslint/parser": "^5.29.0",
|
||||||
"eslint": "^8.10.0",
|
"eslint": "^8.18.0",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
"jest": "^27.5.1",
|
"jest": "^28.1.1",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.7.1",
|
||||||
"supertest": "^6.2.2",
|
"supertest": "^6.2.3",
|
||||||
"ts-jest": "^27.1.3",
|
"ts-jest": "^28.0.5",
|
||||||
"ts-loader": "^9.2.8",
|
"ts-loader": "^9.3.1",
|
||||||
"ts-node": "^10.7.0",
|
"ts-node": "^10.8.1",
|
||||||
"tsconfig-paths": "^3.13.0",
|
"tsconfig-paths": "^4.0.0",
|
||||||
"typescript": "^4.6.2"
|
"typescript": "^4.7.4"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"moduleFileExtensions": [
|
"moduleFileExtensions": [
|
||||||
|
|
|
@ -5,11 +5,40 @@ import * as Joi from 'joi';
|
||||||
import { UsersModule } from './users/users.module';
|
import { UsersModule } from './users/users.module';
|
||||||
import { SnakeNamingStrategy } from 'typeorm-naming-strategies';
|
import { SnakeNamingStrategy } from 'typeorm-naming-strategies';
|
||||||
import { LoggerModule } from 'nestjs-pino';
|
import { LoggerModule } from 'nestjs-pino';
|
||||||
|
import { HealthModule } from './health/health.module';
|
||||||
import configuration from './config/configuration';
|
import configuration from './config/configuration';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
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({
|
ConfigModule.forRoot({
|
||||||
load: [configuration],
|
load: [configuration],
|
||||||
validationSchema: Joi.object({
|
validationSchema: Joi.object({
|
||||||
|
@ -38,13 +67,14 @@ import configuration from './config/configuration';
|
||||||
entities: [],
|
entities: [],
|
||||||
synchronize: true,
|
synchronize: true,
|
||||||
autoLoadEntities: true,
|
autoLoadEntities: true,
|
||||||
logging: true,
|
logging: false,
|
||||||
namingStrategy: new SnakeNamingStrategy(),
|
namingStrategy: new SnakeNamingStrategy(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
inject: [ConfigService],
|
inject: [ConfigService],
|
||||||
}),
|
}),
|
||||||
UsersModule,
|
UsersModule,
|
||||||
|
HealthModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
|
18
src/health/health.controller.spec.ts
Normal file
18
src/health/health.controller.spec.ts
Normal 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();
|
||||||
|
});
|
||||||
|
});
|
32
src/health/health.controller.ts
Normal file
32
src/health/health.controller.ts
Normal 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');
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
10
src/health/health.module.ts
Normal file
10
src/health/health.module.ts
Normal 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 {}
|
42
src/main.ts
42
src/main.ts
|
@ -1,22 +1,19 @@
|
||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import {
|
|
||||||
FastifyAdapter,
|
|
||||||
NestFastifyApplication,
|
|
||||||
} from '@nestjs/platform-fastify';
|
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
import { ValidationPipe } from '@nestjs/common';
|
import { ValidationPipe } from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { Logger } from 'nestjs-pino';
|
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() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create<NestFastifyApplication>(
|
const app = await NestFactory.create<NestExpressApplication>(AppModule);
|
||||||
AppModule,
|
|
||||||
new FastifyAdapter(),
|
|
||||||
{ bufferLogs: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
const logger = app.get(Logger);
|
const logger = app.get(Logger);
|
||||||
|
|
||||||
|
app.disable('x-powered-by');
|
||||||
|
app.use(CorrelationIdMiddleware());
|
||||||
app.useLogger(logger);
|
app.useLogger(logger);
|
||||||
app.enableCors();
|
app.enableCors();
|
||||||
app.useGlobalPipes(
|
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 configService = app.get<ConfigService>(ConfigService);
|
||||||
const port = configService.get<number>('port');
|
const port = configService.get<number>('port');
|
||||||
|
|
||||||
await app.listen(port, '0.0.0.0', (error, address) => {
|
const hostname = '0.0.0.0';
|
||||||
if (error) {
|
|
||||||
logger.error(error);
|
await app.listen(port, hostname, () => {
|
||||||
process.exit(1);
|
logger.log(`Server listening on ${hostname}:${port}`);
|
||||||
} else {
|
// if (error) {
|
||||||
logger.log(`Server listening on ${address}`);
|
// logger.error(error);
|
||||||
}
|
// process.exit(1);
|
||||||
|
// } else {
|
||||||
|
// logger.log(`Server listening on ${address}`);
|
||||||
|
// }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,11 @@ export class UsersService {
|
||||||
async create(createUserDto: CreateUserDto) {
|
async create(createUserDto: CreateUserDto) {
|
||||||
const result = await this.usersRepository.insert(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() {
|
findAll() {
|
||||||
|
@ -24,7 +28,11 @@ export class UsersService {
|
||||||
|
|
||||||
async findOne(id: string) {
|
async findOne(id: string) {
|
||||||
try {
|
try {
|
||||||
return await this.usersRepository.findOneOrFail(id);
|
return await this.usersRepository.findOneOrFail({
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof EntityNotFoundError) {
|
if (e instanceof EntityNotFoundError) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
|
@ -42,7 +50,11 @@ export class UsersService {
|
||||||
|
|
||||||
async update(id: string, updateUserDto: UpdateUserDto) {
|
async update(id: string, updateUserDto: UpdateUserDto) {
|
||||||
try {
|
try {
|
||||||
await this.usersRepository.findOneOrFail(id);
|
await this.usersRepository.findOneOrFail({
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof EntityNotFoundError) {
|
if (e instanceof EntityNotFoundError) {
|
||||||
throw new HttpException(
|
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) {
|
async remove(id: string) {
|
||||||
try {
|
try {
|
||||||
await this.usersRepository.findOneOrFail(id);
|
await this.usersRepository.findOneOrFail({
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof EntityNotFoundError) {
|
if (e instanceof EntityNotFoundError) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
|
|
12
src/utils/correlation-id.middleware.ts
Normal file
12
src/utils/correlation-id.middleware.ts
Normal 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();
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user