Summary and table of contents
Now that we’ve built the GitLab CI, we’re starting to talk a little bit more about the back end. This chapter introduces you to the basics of using Nestjs, a Spring-like Angular Nodejs framework. Why do I want to send this article, mainly because the official NestJS document has a lot of things that are not very clear, examples are few and incomplete, so I plan to get a more comprehensive interpretation, to build a more complete back-end application, Tide! start
Important reminder! : Please do not copy the article. It is recommended that you read the whole article first and understand the whole picture before you practice.Copy the code
Uniform return body
As we review our current application, it is not difficult to find that we have a confusing return format on the return body Res, some of which are this and some of which are that. It’s time for a change. Let’s use Interceptor to transform them
-
Create a new interceptor to handle it
We assume that you already know something about Interceptor, but if you don't, please head over to my next articleCopy the code
// http-req.interceptor.ts
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
export interface Response<T> {
data: T;
}
@Injectable(a)export class HttpReqTransformInterceptor<T>
implements NestInterceptor<T.Response<T>>
{
intercept(
context: ExecutionContext,
next: CallHandler,
): Observable<Response<T>> {
return next
.handle()
.pipe(map((data) = > ({ data, code: 200.msg: ' '.success: true })));
// We are very simple return body unified packaging, do not copy me, I am here just for writing, the specific return code is what. And other things that you have to judge and design your own logic.}}Copy the code
-
How to use
Use it just like any other InterceptorCopy the code
@ApiTags('the Tag related')
@ApiBearerAuth(a)@Controller('tag')
@UseInterceptors(new HttpReqTransformInterceptor<any> ())// the unified return body
export class TagController {
constructor(private readonly tagService: TagService) {}
@UseGuards(AuthGuard('jwt'))
@Get('/tags')
async getAll() {
const value = await this.tagService.getAll();
return value;
}
@UseGuards(AuthGuard('local'))
@Post(a)async createTag(@Body() tagInfo: Tag) {
const value = await this.tagService.create(tagInfo);
return value;
}
@Put('/:id')
async updateTag(@Param() params: InterParams, @Body() tagInfo: Tag) {
const value = await this.tagService.updateById(params.id, tagInfo);
return value;
}
@Delete('/:id')
async deleteTag(@Param() params: InterParams) {
const value = this.tagService.deleteById(params.id);
returnvalue; }}Copy the code
You will end up with a return like this
{
"data": []."code": 200."msg": ""."success": true
}
Copy the code
-
What if there is a conflict with the Dto? What if authentication conflicts?
A careful partner. The above problem has been found, that is, if I fail the verification or my application sends an error π βοΈ BUG, then you will get the following body
// We deliberately do not pass normal validation information
{
"statusCode": 401."error": "Unauthorized"."msg": "Client Error"
}
// Write a program bug
{
"statusCode": 500."msg": "Service Error: Error: xxx"
}
// We are looking forward to it
{
"data": []."code": 200."msg": ""."success": true
}
// The solution here is to kill the creator of the exception! Or you can kill yourself by changing to the same interface type as Nest's built-in save interface
// The first thing we need to do is process all the exception filters to make them look the way we want them to look
// We changed all exception filters to a uniform return data structure
//AllExceptionsFilter
response.status(status).json({
code: status,
message: exception.message,
data: null.success: false}); }// HttpExceptionFilter
response.status(status).json({
code: status,
message: exception.message,
data: null.success: false});// You also need to describe it when you return, but you don't have to, because the filter takes care of it
// It is a filter. The reason for this is very simple, because at the top of the global Filter error processing is at fillter so it has to be at the same time
// Handle the relationship between Filter and Interceptor
// Add your special flag if there are errors in your code
throw new UnauthorizedException('Your account has been logged in at another location, please log in again');
Copy the code
Upload a file
Uploading files is no doubt a very common feature, so let’s take a look at how to do it in Nest. π€ͺ)
- MulterModule module
This is a @nestjs/ platform-Express provided by Nestjs for handling files. It is very simple to use
MulterModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
storage: diskStorage({ // Configure storage
destination: join(__dirname, '.. /.. /.. / '.'/upload'), // The location to store
filename: (req, file, cb) = > { // File name processing
const filename = `${randomUUID()}.${file.mimetype.split('/') [1]}`;
return cb(null, filename); }})}),inject: [ConfigService],
}),
Copy the code
- FileInterceptor UploadedFile decorator
We can’t handle global logic on a single page, we need a path to accept arguments
// FileInterceptor is a decorator imported from @nestjs/ platform-Express, which is used to handle single files π
@UseInterceptors(FileInterceptor('file'))
@Post('upload')
uploadFile(@Body() body: any.@UploadedFile() file: Express.Multer.File) {
return {
file: file.filename,
path: file.path,
We only return the relative path of the file,
// In order to allow external access you need to spell the domian address of the service deployment here,
size: file.size,
};
}
// This is a very simple file to upload, using the form-data format to upload the file
@UseInterceptors(FilesInterceptor('files'))
@Post('uploads')
uploadFiles(
@Body() body: any.@UploadedFiles() files: Array<Express.Multer.File>,
) {
return files.map((item) = > ({
name: item.fieldname,
path: item.path,
size: item.size,
}));
}
Copy the code
- Third-party upload ali cloud OSS
Sometimes we don’t just need to upload to our own server, we also need to upload to a third party OSS. In Nest we can integrate Aliyun’s OSS-SDK to do this. Here I will not elaborate on the operation of Ali Cloud OSS you can directly go to see their official documents help.aliyun.com/document_de… We need your verification credentials, with which you can operate their OSS, and Ali cloud OSS also provides Nodejs SDK integration as follows, and for whether to save the stream locally or in memory actually have their own processing solutions, HERE I save them locally, later with Job to regularly clean these files
import { HttpService } from '@nestjs/axios';
import {
Body,
Controller,
Get,
Post,
Render,
UploadedFile,
UploadedFiles,
UseInterceptors,
Req,
Res,
Param,
} from '@nestjs/common';
import { Request } from 'express';
import { FileInterceptor, FilesInterceptor } from '@nestjs/platform-express';
import * as OSS from 'ali-oss';
import multer, { diskStorage } from 'multer';
import path, { join, normalize } from 'path';
import { randomUUID } from 'crypto';
@Controller('files')
export class FilesController {
oss: OSS;
constructor(private readonly httpService: HttpService) {
this.oss = new OSS({
region: 'oss-cn-beijing'.accessKeyId: 'yourID'.accessKeySecret: 'yourKey'.bucket: 'yourBucket'}); }@UseInterceptors(FileInterceptor('file'))
@Post('upload')
uploadFile(@Body() body: any.@UploadedFile() file: Express.Multer.File) {
return {
file: file.filename,
path: file.path,
size: file.size, // The path should be combined with the main static directory
};
}
@UseInterceptors(FilesInterceptor('files'))
@Post('uploads')
uploadFiles(
@Body() body: any.@UploadedFiles() files: Array<Express.Multer.File>,
) {
return files.map((item) = > ({
name: item.fieldname,
path: item.path,
size: item.size,
}));
}
@Post('upload-oss')
@UseInterceptors(
FileInterceptor('file', {
storage: diskStorage({
destination: join(__dirname, '.. /.. /.. / '.'/upload-oos'),
filename: (req, file, cb) = > {
const filename = `${randomUUID()}.${file.mimetype.split('/') [1]}`;
return cb(null, filename); }})}),)async oos(@UploadedFile() file: Express.Multer.File) {
// When we upload we run you upload it into memory and then send it to a third party but that's not good,
// If you store too many files or not too many files, your machine will break down, so we recommend that you save the files to a certain
// Create a temporary directory, and then call a third party to upload it
// It is easy to upload and download files
const value = await this.oss.put(file.filename, normalize(file.path));
return value;
}
// Enabling oss downloads requires temporary validation
@Get('upload-oss/:file')
async getOSSFile(@Param() params: { file: string }) {
// When we upload we run you upload it into memory and then send it to a third party but that's not good,
// If you store too many files or not too many files, your machine will break down, so we recommend that you save the files to a certain
// Create a temporary directory, and then call a third party to upload it
// It is easy to upload and download files
const value = this.oss.signatureUrl(params.file, {
expires: 3600});return {
url: value,
};
}
Copy the code
- So that’s how you store the file so how do you access the file?
We need to use express.static to do this
// main.ts
// Configure the file access folder as a static directory to achieve direct access to the following files
const rootDir = join(__dirname, '.. ');
app.use('/static', express.static(join(rootDir, '/upload')));
// app.use('/static', express.static(join(rootDir, '/upload'))); // Multiple configurations are allowed
Copy the code
Forward requests
Request forwarding is relatively simpleCopy the code
// module-- omit some code (if you don't know what I'm writing, you must have read my previous articles) -- httpmodule. register({timeout: 5000.maxRedirects: 5,}).// httpService is a built-in nest axios module that needs to be injected into the module before being used πΆπΆπΆ
@Get('httpUser')
async getIpAddress() {
const value = await this.httpService
.get('https://api.gmit.vip/Api/UserInfo? format=json')
.toPromise();
return { ...value.data };
}
Copy the code
Regular Job
In Nest, you need to delete unnecessary upload files and dispose of logs
The theoretical knowledge
-
First, you need to understand the concept of job and some common knowledge
Scheduled tasks allow you to schedule a single execution at a specified date/time, at a certain interval, or after a certain time. A job a current/non-scheduled work item that can be repeated. We have derived a set of rules and syntax for its timing, which in the Linux world is often implemented through operating system level cron packages and so on.
The name of the | meaning |
---|---|
* * * * * * | In a second |
45 * * * * * | Forty-five seconds per minute |
_ 10 _ * * * | Every hour, starting at the 10th minute |
0 _/30 9-17 _ * * | Every 30 minutes between 9 a.m. and 5 p.m |
0, 30, 11 * * 1-5 | Monday to Friday 11:30 a.m |
-
So let’s see how do we declare job in Nest
In Nestjs we use the @nestjs/schedule library which encapsulates node-cron underneath,
// The simplest job (it will automatically execute every 45s after your app starts)
// Don't forget to inject before using
import { Module } from '@nestjs/common';
import { ScheduleModule } from '@nestjs/schedule';
@Module({
imports: [ScheduleModule.forRoot()],
})
export class AppModule {}
// Start a service to use it
@Injectable(a)export class TasksService {
@Cron('45 * * * * *')
handleCron() {
console.log('666')}}// It also has a lot of tricks such as defining intervals like timers. To define delays, you can call the decorator: "@timeout (5000)"
@Interval(10000)
handleInterval() {
this.logger.debug('Called every 10 seconds');
}
Copy the code
- How do I run it?
We mentioned that this is automatic, so is it possible to start and stop it manually? Parse to reveal
// Run manually
@Cron('30 * * * * *', {
name: 'notifications',})handleTimeout() {
console.log('66666');
}
// Perform manual scheduling tests in a Controller /service
constructor(
private schedulerRegistry: SchedulerRegistry
) {}
@Get('/job')
async stopJob(@Param() params: { start: boolean }) {
const job = this.schedulerRegistry.getCronJob('notifications');
/ / this. SchedulerRegistry more detailed please go to the official documentation https://docs.nestjs.cn/7/techniques?id=%e5%ae%9a%e6%97%b6%e4%bb%bb%e5%8a%a1 operation
if (params.start) {
job.start();
console.log(job.lastDate());
} else{ job.stop(); }}Copy the code
Practice guidelines
For simplicity we have set only one job (which is used to clean up files that are not uploaded to OSS)Copy the code
// You need to inject it into module before using it
import { Module } from '@nestjs/common';
import { JobService } from './job.service';
// This module is dedicated to handling jobs
@Module({
imports:[ ScheduleModule.forRoot() ]
providers: [].controllers: [].exports: [JobService],
})
export class JobModule {}
import { Injectable } from '@nestjs/common';
import { Cron, CronExpression, Interval, Timeout } from '@nestjs/schedule';
import * as fs from 'fs';
import { join } from 'path';
// Clear the log directory and locally uploaded files OSS temporary files
@Injectable(a)export class JobService {
emptyDir = (fileUrl) = > {
const files = fs.readdirSync(fileUrl); // Read the folder
files.forEach(function (file) {
const stats = fs.statSync(fileUrl + '/' + file);
if (stats.isDirectory()) {
this.emptyDir(fileUrl + '/' + file);
} else {
fs.unlinkSync(fileUrl + '/'+ file); }}); };// Execute once every night at 11pm
@Cron(CronExpression.EVERY_DAY_AT_11PM)
handleCron() {
// Delete OSS files and log files
const OSSRootDir = join(__dirname, '.. /.. /.. /upload-oos');
// Logs are generally saved instead of deleted. Note that this is just a simple example
const accesslogDir = join(__dirname, '.. /.. /.. /logs/access');
const appOutDir = join(__dirname, '.. /.. /.. /logs/app-out');
const errorsDir = join(__dirname, '.. /.. /.. /logs/errors');
this.emptyDir(OSSRootDir);
this.emptyDir(accesslogDir);
this.emptyDir(appOutDir);
this.emptyDir(errorsDir); }}Copy the code
On the swagger
Swagger is very simple, direct code on the official document here docs.nestjs.cn/7/recipes?i… Although it’s pretty simple in the documentation, I’m going to record my pit
- Added to the main
// Build the Swagger document
const options = new DocumentBuilder()
.setTitle('Base-Http-example')
.addBearerAuth()
.setDescription('A complete HttpNodejs service')
.setVersion('1.0')
.addTag('Http')
.build();
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup('api', app, document);
Copy the code
- Go to Controller and bind
@ApiTags('the User related')
@Controller('user')
@UseInterceptors(new HttpReqTransformInterceptor<any> ())// the unified return body
export class UserController {... Leaving out a lot of code, Swagger usage is officially described in great detail. There are all kinds and there are no pits.Copy the code
- I’m going to write the parameter description in the Dto, so I’m going to write the Model
export class UserInfoDTO {
@ApiProperty({
description: 'name'.default: Users' 1 ',})@IsNotEmpty({ message: 'User name cannot be empty' })
username: string;
@ApiProperty(a)@IsNotEmpty({ message: 'Password cannot be empty' })
password: string;
@ApiProperty(a)@IsNotEmpty({ message: 'Update creation time Required' })
@IsNumber(a)update_time: number;
@ApiProperty(a)@IsNotEmpty({ message: 'Creation time Required' })
create_time: number;
@ApiProperty(a)@IsNotEmpty({ message: 'Status Required' })
state: number;
}
Copy the code
The above are the most basic usage, if you still want to have more SAO qi operation please go to the official documentation
Use Redis to do single sign-up
And we’re going to show you how to use it. Redis does single sign-on
- The first is to install Redis
This should be for everybody is relatively simple, can directly go to the novice tutorial www.runoob.com/redis/redis…
- We implement a Module ourselves
Implement three files in moduels: cache controller Module and service (actually we only need service)
@Controller('cache')
export class CacheController {}
@Module({
imports: [].controllers: [CacheController],
providers: [CacheService],
exports: [CacheService],
})
export class CacheModule {}
import { Injectable } from '@nestjs/common';
import RedisC, { Redis } from 'ioredis';
@Injectable(a)// The current version is simply a set value to clear the value, after starting the microservice, this place is not written like this
export class CacheService {
redisClient: Redis;
// Start with the simplest version, producing only one link instance
constructor() {
this.redisClient = new RedisC({
port: 6379.// Redis port
host: '192.168.101.10'.// Redis host
family: 4.// 4 (IPv4) or 6 (IPv6)
password: ' '.db: 0}); }// Write a few quick ways to set up Redis
/ * * *@Description: encapsulates the redis cache setting method *@param Key {String} Key value *@param Value {String} Key value *@param Seconds {Number} Expiration time *@return: Promise<any>
*/
public async set(key: string.value: any, seconds? :number) :Promise<any> {
value = JSON.stringify(value);
if(! seconds) {await this.redisClient.set(key, value);
} else {
await this.redisClient.set(key, value, 'EX', seconds); }}/ * * *@Description: sets the value * in the Redis cache@param key {String}
*/
public async get(key: string) :Promise<any> {
const data = await this.redisClient.get(key);
if (data) return data;
return null;
}
/ * * *@DescriptionDelete redis cache data based on key *@param key {String}
* @return: * /
public async del(key: string) :Promise<any> {
return await this.redisClient.del(key);
}
/ * * *@Description: Clear redis cache *@param {type}
* @return: * /
public async flushall(): Promise<any> {
return await this.redisClient.flushall(); }}Copy the code
- Now that we have Redis and basic operations on it, how do we implement single sign-on
The main logic is that the token is stored in redis when the token is issued. The key is the user ID. If the same user ID is used to log in next time, the value of the key in the original REDis is changed to a new one. It’s assumed that it’s logged in somewhere else, and it’s forcibly logged out locally
// Make changes in several JWT files
// /src/moduels/auth/ath.service.ts
async certificate(user: User) {
const payload = {
username: user.username,
sub: user.id,
};
console.log('JWT Validation - Step 3: Process JWT visa ');
try {
const token = this.jwtService.sign(payload);
// Store the token in redis. If the user logs in again, the value will be updated
// If the original key is not found, the user is forced to log out. Therefore, the single sign-on function is complete, and the expiration time is consistent with the token
await this.CacheService.set(
`user-token-${user.id}-${user.username}`,
token,
60 * 60 * 8,);return {
code: 200.data: {
token,
},
msg: 'Login succeeded'}; }catch (error) {
return {
code: 600.msg: 'Wrong account or password'}; }}// The JWT and local logic should also be changed
@Injectable(a)export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authService: AuthService) {
super(a); }async validate(username: string.password: string) :Promise<any> {
// The local local policy has little effect on JWT,
console.log('You have to call me ------------');
const user = await this.authService.validateUser(username, password);
if(! user) {throw new UnauthorizedException();
}
returnuser; }}@Injectable(a)export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private readonly CacheService: CacheService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false.secretOrKey: jwtConstants.secret,
passReqToCallback: true}); }// JWT validation -step 4: called by the guard
async validate(req: Request, payload: any) {
const originToken = ExtractJwt.fromAuthHeaderAsBearerToken()(req);
// Only after verification can you come here
// console.log(' JWT validation -step 4: called by guard ');
const cacheToken = await this.CacheService.get(
`user-token-${payload.sub}-${payload.username}`,);// Single sign-on authentication
if(cacheToken ! = =JSON.stringify(originToken)) {
throw new UnauthorizedException('Your account has been logged in at another location, please log in again');
}
return {
username: payload.username, }; }}Copy the code
So this is done β ! The key that you set expires in Redis, and we can actually optimize this code to pull out the redis link and pass the class instead of using the new. You can do that yourself. Also π feel free to share your implementation in the comments section for the benefit of the party hahaha π
How to do microservices? How is the communication architecture designed?
Using Nestjs we will implement a very easy microservice, communication can use MQ or directly drop, or its RPC scheme, but I did not introduce other responsible things, just implement the direct drop
The theoretical knowledge
- What does the used party need to prepare?
Nest microservices relies on a library, @nestjs/ Microservices, which makes it very easy to create microservices applications. Now let’s start another project, (very simple, we don’t write anything, just register a service). Note that we use the simplest TCP to communicate with Nest. Microservices listen for messages over TCP.
The values of options are as follows
host |
Connection host name |
---|---|
port |
End connections |
retryAttempts |
Total number of connection attempts |
retryDelay |
Connection retry delay (MS) |
import { NestFactory } from '@nestjs/core';
import { Transport, MicroserviceOptions } from '@nestjs/microservices';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
transport: Transport.TCP,
options: {
host: '192.168.101.2'.port: 3333,}}); app.listen(); } bootstrap();// Then we go to controller to write something
@Controller(a)export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
// Mode 2 is event-based (return is not required)
@EventPattern('math:log')
async handleUserCreated(text: Record<string, unknown>) {
// business logic
console.log(text, 'Event-based transport');
}
// Pattern 1 based on request-response (request comes, request returns)
@MessagePattern('math:wordcount')
wordCount(text: string): { [key: string] :number } {
return this.appService.calculateWordCount(text); }}Copy the code
- What is a pattern
Patterns are ways in which messages are identified between microservices. They can be either request-response (the default) or event-based, as described in the code you have seen above
- What can users do to disable this service?
// Inject the same into module
@Module({
imports: [
ClientsModule.register([
{
name: 'NEST_MICRO'.transport: Transport.TCP,
options: {
host: '192.168.101.2'.port: 3001,},},]), -----// Inject it where you will use it
constructor(
private readonly userService: UserService,
@Inject('NEST_MICRO') private client: ClientProxy,
) {}
// Enable a microservice
@Post('/math/wordcount')
async wordCount(@Body() { text }: { text: string }) {
// The first pattern requests a response
const value2 = await this.client.send('math:wordcount', text).toPromise();
// Second mode event
await this.client.emit('math:log', text).toPromise();
return value2;
}
Copy the code
- So which model do I fit into?
In general Kafka or NATS are more in line with the event pattern (when you just want to publish events and don’t want to wait βοΈ)
Practice guidelines
// Start by creating a new project in main.
import { NestFactory } from '@nestjs/core';
import { Transport, MicroserviceOptions } from '@nestjs/microservices';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
transport: Transport.TCP,
options: {
host: '192.168.101.2'.port: 3333,}}); app.listen(); } bootstrap();// Write in its controller (discarded)
import { Controller, Get } from '@nestjs/common';
import { EventPattern, MessagePattern } from '@nestjs/microservices';
import { AppService } from './app.service';
@Controller(a)export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
// Mode 2 is event-based (return is not required)
@EventPattern('math:log')
async handleUserCreated(text: Record<string, unknown>) {
// business logic
console.log(text, 'Event-based transport');
}
// Pattern 1 based on request-response (request comes, request returns)
@MessagePattern('math:wordcount')
wordCount(text: string): { [key: string] :number } {
return this.appService.calculateWordCount(text); }}// Global injection in our drop-side AppModule (drop-side)
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { ScheduleModule } from '@nestjs/schedule';
import App_globalConfig from './config/configuration';
import DatabaseConfig from './config/database';
import { AppService } from './app.service';
import { ArticleModule } from './modules/article/article.module';
import { TagModule } from './modules/tag/tag.module';
import { UserModule } from './modules/user/user.module';
import { AuthModule } from './modules/auth/auth.module';
import { FilesModule } from './modules/files/files.module';
import { JobModule } from './modules/job/job.module';
import { CacheModule } from './modules/cache/cache.module';
import { ClientsModule, Transport } from '@nestjs/microservices'; // Register a client for data transfer to the microservice
@Module({
imports: [
ClientsModule.register([ // You can also read config with registrAsync
{
name: 'NEST_MICRO'.transport: Transport.TCP,
options: {
host: '192.168.101.2'.port: 3001,
},
},
]),
ScheduleModule.forRoot(),
ConfigModule.forRoot({
isGlobal: true.load: [App_globalConfig, DatabaseConfig],
}),
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => {
return {
type: 'mysql'.host: configService.get('database.host'),
port: Number(DatabaseConfig().port),
username: DatabaseConfig().username,
password: DatabaseConfig().password,
database: DatabaseConfig().database,
entities: [__dirname + '/**/*.entity{.ts,.js}'].// Scan the.entity.ts or.entity.js files in this project
synchronize: true}; },inject: [ConfigService],
}),
UserModule,
TagModule,
ArticleModule,
AuthModule,
FilesModule,
JobModule,
CacheModule,
],
providers: [AppService],
})
export class AppModule {}
// use in userModule
export class UserController {
constructor(
private readonly userService: UserService,
@Inject('NEST_MICRO') private client: ClientProxy,
) {}
// Enable a personal microservice
@Post('/math/wordcount')
async wordCount(@Body() { text }: { text: string }) {
const value2 = await this.client.send('math:wordcount', text).toPromise();
await this.client.emit('math:log', text).toPromise();
return value2;
}
Copy the code
How does Nest work?
If you pull my code after you build, then what? How do you deploy and maintain it? ππ here we will open discussion to the end, hey hey here is just a simple talk, if you like to see, I will improve the whole project later
About builds and runtimes
In fact Nest says that a runtime framework needs to start with node_module. It’s a mistake to just build something and expect it to run like a front end. They changed nest’s Webpack configuration to include all of these dependent libraries in the build, which I think is fine. However, you may have to deal with more bugs and uncertainties this way, so I recommend not doing this, just take the node_module out and run
On the operational
In fact, I hope to describe nodeJS deployment in the industry, take our company (Newegg) for example, our deployment method is to use PM2 to manage it, the same PM2 based function we also completed automatic restart and other functions. See their documentation for details
Pm2. Fenxianglu. Cn/docs/advanc…
K8s and Docker
K8s and Docker deployment is relatively simple, I give an example of Docker deployment, in my previous several articles have explained how to use Docker to build an image, here do not repeat, the same use of the previous document in gitlab you can fully achieve their own workflow.
About nginx’s back-end reverse proxy,
In fact, this is easy to do in a single machine, docker or K8s need to be reworked, my solution is to assume nginx on the host and map to the container’s prot. (Nginx is very simple to use)
reference
NestJS official documentation
TypeOrm official document
Github address of this project