For you who are using Nestj as a project for the first time (three articles: writing api & uploading pictures)
1. Module
For example, we wrote before user
controller (controller)
and services (service)
they in fact should be regarded as a whole, such as a place to introduce user
related operations, direct introduction the User's module can, we now come to re-generate a
module
try Right.
nest g module modules/users
nest g controller modules/users
nest g service modules/users
2. Various request methods
To obtain the parameters of different request methods, you need to use nest
provided by 060cd960295f71. For example, get
requests such explicit parameter transfers need to be processed @Query
get
import { Controller, Get, Query} from '@nestjs/common';
// ...
@Get()
getList(@Query() query) {
return query;
}
post
import { Controller, Post, Body } from '@nestjs/common';
// ...
@Post('create')
create(@Body() body) {
return body
}
Line id
import { Controller, Get, Param } from '@nestjs/common';
// ...
@Get(':id')
getUser(@Param('id') id): string {
return id;
}
3. Public path
It is necessary to set the prefix of the api path, we set it to /api/v1
, which is /share/src/main.ts
.
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('api/v1'); // 这里这里
await app.listen(3000);
}
bootstrap();
Four. Decorator
In some cases, we may need to preprocess the parameters. For example, the api parameter of the request list must contain page
and pageSize
and the minimum is 1.
We decorator
folder src
file plus the paging.decorator.ts
file.
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const Paging = createParamDecorator(
(data: string, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const query = request.query;
if (!query.page || query.page < 1) {
query.page = 1
}
if (!query.pageSize || query.pageSize < 1) {
query.pageSize = 1
}
return query
},
);
We modify the original getList
method, replace @Query() query
with @Paging() query
and view the results;
@Get()
getList(@Paging() query) {
return query;
}
5. Pipelines
The pipeline converts the input data into the required data output, verifies the input data, if the verification is successful, continues to pass, and the verification fails, an exception is thrown. The pipeline can handle more extensive things than the decorator, such as the decorator mentioned above. It is for the parameters, while the pipeline is for the entire request.
We use a pipeline to verify page and pageSize. The new pipe
folder under paging.pipe.ts
. The contents of the file are as follows.
import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common';
@Injectable()
export class PagingPipe implements PipeTransform {
transform(query: any, metadata: ArgumentMetadata) {
if (metadata.type === 'query') {
if (!query.page || query.page < 1) {
query.page = 1
}
if (!query.pageSize || query.pageSize < 1) {
query.pageSize = 1
}
}
return query;
}
}
Here you need to use metadata to determine which kind of requester is to do the processing, and his usage is as follows:
import { Controller, Get, Query, UsePipes } from '@nestjs/common';
@Controller('users')
export class UsersController {
@Get()
@UsePipes(new PagingPipe())
getList(@Query() query) {
return query;
}
}
- Need to rely on
UsePipes
decorator. - Act on the overall request, not for a certain parameter.
- Multiple pipelines can be set, for example,
@UsePipes(new PagingPipe(), new PagingPipe2())
from left to right.
Six. Middleware
This is an old friend, I won't introduce it much, just introduce the configuration method, create a middleware
folder in the src directory, the following is the global.middleware.ts
as follows:
export default function (req, res, next) {
console.log(`全局函数式: 进入`);
next();
console.log(`全局函数式: 退出`);
};
Global use
Use directly in main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import globalMiddleware from './middleware/global.middleware';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('api/v1');
app.use(globalMiddleware)
await app.listen(3000);
}
bootstrap();
Partial use, only when the route is /users
take effect
Create users.middleware.ts
file with the following content:
import { Injectable, NestMiddleware } from '@nestjs/common';
@Injectable()
export class UsersMiddleware implements NestMiddleware {
use(req: any, res: any, next: () => void) {
console.log('进入users中间件')
next();
console.log('走出users中间件')
}
}
Make the following changes in the /share/src/app.module.ts
import { Module, MiddlewareConsumer } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { GitlabController } from './modules/gitlab/gitlab.controller';
import { GitlabService } from './modules/gitlab/gitlab.service';
import { UsersModule } from './modules/users/users.module';
import { UsersMiddleware } from './middleware/users.middleware';
@Module({
imports: [UsersModule],
controllers: [AppController, GitlabController],
providers: [AppService, GitlabService],
})
export class AppModule {
// 此处定义中间件
configure(consumer: MiddlewareConsumer) {
consumer
.apply(UsersMiddleware)
.forRoutes('/users*');
}
}
- If you
main.ts
, it can only be middleware in function mode. consumer
can be spliced behind.apply
.- If
.forRoutes('/users*');
written as.forRoutes('users*');
, an error will be reported. - In
forRoutes
directly inside configurationforRoutes('*')
is a global key intermediate, and without having to middleware function.
Seven. Guard
The guard is very similar to the middle key. It can be executed before the request is processed. The difference between the guard and the middle key is that the middleware calls next but does not know what to execute next, but the guard can know what to execute next, and the guard is generally It is used to verify permissions.
Next, we will use jwd to make a simple and easy to understand verification, so it is easy to understand, so the simplified version of 160cd960296410 on the official website.
Installation dependencies
yarn add @nestjs/jwt
Generate module
nest g module modules/auth
nest g controller modules/auth
nest g service modules/auth
In share/src/modules/auth/auth.controller.ts
, configure the interface that generates the token.
import { Controller, Get, Response } from '@nestjs/common';
import { AuthService } from './auth.service'
@Controller('auth')
export class AuthController {
constructor(
private readonly authService: AuthService
) { }
@Get()
getToken(@Response() res) {
return this.authService.getToken(res);
}
}
Define jwt
in /share/src/modules/auth/auth.module.ts
, such as expiration time.
import { Module } from '@nestjs/common';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { JwtModule } from '@nestjs/jwt';
@Module({
imports: [
JwtModule.register({
secret: 'secretKey',
signOptions: { expiresIn: `${60 * 60 * 24 * 10}s` },
}),
],
controllers: [AuthController],
providers: [AuthService],
exports: [AuthService], // 这里需要注意, 因为后面会全局使用所以要导出一下
})
export class AuthModule { }
Define the method of token generation in /share/src/modules/auth/auth.service.ts
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class AuthService {
constructor(
private readonly jwtService: JwtService
) { }
getToken(res) {
res.setHeader("token", this.jwtService.sign({
id: 'dadscxciweneiu122',
name: "金毛"
}))
res.send()
}
}
- The
jwtService
above and it is better not to return it directly in the parameter, but to put it in the header. - The object in sig is the data to be encrypted. We can put some user id in it.
exports: [AuthService]
is very important, because it will be used in the guard.
Guard configuration
/share/src/guard/token.guard.ts
import { CanActivate, ExecutionContext, Injectable, HttpException } from '@nestjs/common';
import { Observable } from 'rxjs';
import { Reflector } from '@nestjs/core';
import { Inject } from '@nestjs/common';
@Injectable()
export class TokenGuard implements CanActivate {
constructor(
private readonly reflector: Reflector,
@Inject('AuthService') private readonly authService,
) { }
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
try {
const user = this.authService.testToken(request)
request.user = user;
return true
} catch (error) {
throw new HttpException({
status: 401,
error: '身份验证失败',
}, 401);
}
}
}
- The example here is that after we parse the token, we will look up the user information according to the userId in the token, and then assign the user information to the request, so that each operation can use this user information for verification and other operations.
HttpException
an error type. If the verification fails, we will report 401 directly.
To use guards globally, /share/src/app.module.ts
adds the following changes:
import { APP_GUARD } from '@nestjs/core';
import { TokenGuard } from './guard/token.guard';
// ...
@Module({
imports: [UsersModule, AuthModule],
controllers: [AppController, GitlabController],
providers: [AppService, GitlabService,
{
provide: APP_GUARD,
useClass: TokenGuard,
}]
})
8. Detect token
Of course, the generated token needs to be verified, which is the implementation of the testToken
/share/src/modules/auth/auth.service.ts
:
testToken(req) {
const token = req.headers.token;
return this.jwtService.verify(token)
// 后续链接数据库后会查出user信息返回出去
}
Nine. No need to verify token for setting
Many APIs do not need to restrict the user to the login state, so we need to set a decorator that makes the request without verification. For example, the operation of obtaining the token does not need to verify the identity. The usage is as shown in the figure below.
/share/src/guard/noauth.ts
import { SetMetadata } from '@nestjs/common';
export const NoAuth = () => SetMetadata('no-auth', true);
SetMetadata
setsmetadata, metadata is the data used to describe the data, which can be understood as explaining what the requested data is.
- 'no-auth' is set to true.
/share/src/guard/token.guard.ts
the canActivate
method which determines the increase
import { CanActivate, ExecutionContext, Injectable, HttpException } from '@nestjs/common';
import { Observable } from 'rxjs';
import { Reflector } from '@nestjs/core';
import { Inject } from '@nestjs/common';
@Injectable()
export class TokenGuard implements CanActivate {
constructor(
private readonly reflector: Reflector,
@Inject('AuthService') private readonly authService,
) { }
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
// const headers = request.headers;
const noAuth =
this.reflector.get<boolean>('no-auth', context.getHandler());
if (noAuth) {
return true;
}
// 未授权
try {
const user = this.authService.testToken(request)
request.user = user;
return true
} catch (error) {
throw new HttpException({
status: 401,
error: '身份验证失败',
}, 401);
}
}
}
- Take out
no-auth
reflection. context.getHandler()
returns[Function: testToken]
which is the method in the service we executed.- You can also use
context.getClass()
return value is[class AuthController]
, the controller where our current request is located. For insurance, it can be written as follows:
const noAuth = this.reflector.get<boolean>('no-auth', context.getClass()) || this.reflector.get<boolean>('no-auth', context.getHandler());
10. Picture upload & display
Browse pictures
Create a folder /share/public
and put a picture in it.
Add the following code to the main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { NestExpressApplication } from '@nestjs/platform-express'
import globalMiddleware from './middleware/global.middleware';
async function bootstrap() {
// const app = await NestFactory.create(AppModule); // 这个改动了
const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.setGlobalPrefix('api/v1');
app.use(globalMiddleware);
app.useStaticAssets('public', {
prefix: '/static' // 一定不可以省略 '/'
});
await app.listen(3000);
}
bootstrap();
Let's look at the effect:
upload image
Get the file directly, and then store it in the form of a stream. I won’t talk about it here. Let’s directly demonstrate the use of a decorator to realize the function of receiving pictures and uploading.
import { Controller, BadRequestException, Post UploadedFile, UseInterceptors } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { join } from 'path';
import multer = require('multer');
@Controller('user')
export class UserController {
@Post('img:import')
@UseInterceptors(
FileInterceptor('file', {
storage: multer.diskStorage({
destination: function (req, file, cb) {
// cb(null, join(process.cwd(), 'upload'));
cb(null, join(process.cwd(), 'public'));
},
filename: function (req, file, cb) {
const unique = `${Date.now()}${Math.round(Math.random() * 1e9)}`;
const imgPath = `${unique}.${file.mimetype.split('/')[1]}`;
cb(null, imgPath);
},
}),
limits: {
fileSize: 1024 * 1024,
},
fileFilter(req, file, cb) {
if (file.mimetype !== 'image/jpeg' && file.mimetype !== 'image/png') {
throw new BadRequestException(`只支持jpg, png格式`);
}
cb(null, true);
},
}),
)
async coverImport(@UploadedFile() file) {
return { url: `/static/${file.filename}` };
}
}
- Set the storage path in
destination
filename
Here is the name of the file, don't forget to add the suffix.limits
is the size.fileFilter
does some filtering operations and throws errors.- Finally, the relative path is returned to the client.
- This way of writing is also not beautiful, so it is not necessary to use this way.
end.
The next article is typeorm
operate the database. I will share many real cases that are not shown on the official website. Some practical problems caused me a lot of trouble at the time. I hope to make progress with you.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。