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'
|
||||
],
|
||||
'indent': [
|
||||
'warn',
|
||||
'off',
|
||||
2
|
||||
],
|
||||
'linebreak-style': 'warn',
|
||||
|
|
60
package.json
60
package.json
|
@ -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": [
|
||||
|
|
|
@ -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 {}
|
||||
|
|
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 {
|
||||
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}`);
|
||||
// }
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
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