MVC
Nest 使用 Express 库,因此有关 Express 中的 MVC(模型 - 视图 - 控制器)模式的每个教程都与 Nest 相关。首先,我们来克隆一个 Nest starter 项目:
$ git clone https://github.com/nestjs/typescript-starter.git project
$ cd project
$ npm install
$ npm run start
为了创建一个简单的MVC应用程序,我们必须安装一个模板引擎:
$ npm install --save jade
我选择了,jade因为它是目前最受欢迎的引擎,但个人而言,我更喜欢Mustache。安装过程完成后,我们需要使用以下代码配置快速实例:
main.ts
import * as express from 'express';
import * as path from 'path';
import { NestFactory } from '@nestjs/core';
import { ApplicationModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(ApplicationModule);
app.use(express.static(path.join(__dirname, 'public')));
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
await app.listen(3000);
}
bootstrap();
我们明确表示该 public 目录将用于存储静态文件, views 将包含模板,并且 jade 应使用模板引擎来呈现 HTML 输出。
现在,让我们在该文件夹内创建一个 views 目录和一个 index.jade 模板。在模板内部,我们要打印一张 message 从控制器传来的信息:
index.jade
html
head
body
p= message
然后, 打开 app.controller 文件, 并用下面的代码替换 root() 方法:
app.controller.ts
@Get()
root(@Res() res) {
res.render('index', { message: 'Hello world!' });
}
事实上,当 Nest 检测到 @Res() 装饰器时,它会注入 response 对象。在这里了解更多关于它的能力。
就这样。在应用程序运行时,打开浏览器访问 http://localhost:3000/ 你应该看到这个 Hello world! 消息。
SQL
为了减少开始与数据库进行连接所需的样板, Nest 提供了随时可用的 @nestjs/typeorm 软件包。我们选择了 TypeORM, 因为它绝对是 Node.js 中可用的最成熟的对象关系映射器 (ORM)。由于它是用TypeScript编写的,所以它在Nest框架下运行得非常好。
首先,我们需要安装所有必需的依赖关系:
$ npm install --save @nestjs/typeorm typeorm mysql
?> 在本章中,我们将使用MySQL数据库,但TypeORM提供了许多不同的支持,如PostgreSQL,SQLite甚至MongoDB(NoSQL)。
一旦安装完成,我们可以将其 TypeOrmModule 导入到根目录中 ApplicationModule 。
app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',
database: 'test',
entities: [__dirname + '/../**/*.entity{.ts,.js}'],
synchronize: true,
}),
],
})
export class ApplicationModule {}
forRoot() 方法接受与 TypeORM 包中的 createConnection() 相同的配置对象。此外, 我们可以在项目根目录中创建一个 ormconfig.json 文件, 而不是将任何内容传递给它。
ormconfig.json
{
"type": "mysql",
"host": "localhost",
"port": 3306,
"username": "root",
"password": "root",
"database": "test",
"entities": ["src/**/**.entity{.ts,.js}"],
"synchronize": true
}
现在我们可以简单地将圆括号留空:
app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [TypeOrmModule.forRoot()],
})
export class ApplicationModule {}
之后,Connection 和 EntityManager 将可用于注入整个项目(无需导入任何其他模块),例如以这种方式:
app.module.ts
import { Connection } from 'typeorm';
@Module({
imports: [TypeOrmModule.forRoot(), PhotoModule],
})
export class ApplicationModule {
constructor(private readonly connection: Connection) {}
}
存储库模式
该TypeORM支持库的设计模式,使每个实体都有自己的仓库。这些存储库可以从数据库连接中获取。
首先,我们至少需要一个实体。我们将重用 Photo 官方文档中的实体。
photo/photo.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class Photo {
@PrimaryGeneratedColumn()
id: number;
@Column({ length: 500 })
name: string;
@Column('text')
description: string;
@Column()
filename: string;
@Column('int')
views: number;
@Column()
isPublished: boolean;
}
该 Photo 实体属于该 photo 目录。这个目录代表了 PhotoModule。这是你决定在哪里保留你的模型文件。从我的观点来看,最好的方法是将它们放在他们的域中, 放在相应的模块目录中。
让我们看看 PhotoModule:
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { PhotoService } from './photo.service';
import { PhotoController } from './photo.controller';
import { Photo } from './photo.entity';
@Module({
imports: [TypeOrmModule.forFeature([Photo])],
components: [PhotoService],
controllers: [PhotoController],
})
export class PhotoModule {}
此模块使用 forFeature() 方法定义应在当前范围内注册的存储库。
现在, 我们可以使用 @InjectRepository() 修饰器向 PhotoService 注入 PhotoRepository:
photo/photo.service.ts
import { Component, Inject } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Photo } from './photo.entity';
@Component()
export class PhotoService {
constructor(
@InjectRepository(Photo)
private readonly photoRepository: Repository<Photo>,
) {}
async findAll(): Promise<Photo[]> {
return await this.photoRepository.find();
}
}
就这样。完整的源代码在这里可用。
?> 不要忘记将 PhotoModule 导入根 ApplicationModule。
MongoDB
有2种处理 MongoDB 的方法。您可以使用 TypeORM 提供 MongoDB 支持 或者 Mongoose 这是最流行的 MongoDB 对象建模工具。如果你想留在 TypeORM, 你可以按照这些步骤。否则, 我们将使用一个专门的 @nestjs/mongoose 包。
首先, 我们需要安装所有必需的依赖项:
$ npm install --save @nestjs/mongoose mongoose
安装过程完成后, 我们可以将 MongooseModule 导入到根 ApplicationModule 中。
app.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
@Module({
imports: [MongooseModule.forRoot('mongodb://localhost/nest')],
})
export class ApplicationModule {}
forRoot() 方法接受与来自 Mongoose 包的 mongoose.connect() 相同的配置对象。
模块注入
在Mongoose, 一切都是从一个模式派生。让我们定义 CatSchema:
cats/schemas/cat.schema.ts
import * as mongoose from 'mongoose';
export const CatSchema = new mongoose.Schema({
name: String,
age: Number,
breed: String,
});
CatsSchema 属于 cats 名录。此目录表示 CatsModule。这是您决定将您的架构文件保存到的位置。从我的角度来看, 最好的方法是将它们几乎放在他们的域中, 放在相应的模块目录中。
让我们看看 CatsModule:
cats/cats.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
import { CatSchema } from './schemas/cat.schema';
@Module({
imports: [MongooseModule.forFeature([{ name: 'Cat', schema: CatSchema }])],
controllers: [CatsController],
components: [CatsService],
})
export class CatsModule {}
此模块使用 forFeature () 方法定义应在当前范围内注册的模型。
现在, 我们可以使用 @InjectModel() 修饰器向 CatsService 注入 CatModel:
cats/cats.service.ts
import { Model } from 'mongoose';
import { Component } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Cat } from './interfaces/cat.interface';
import { CreateCatDto } from './dto/create-cat.dto';
import { CatSchema } from './schemas/cat.schema';
@Component()
export class CatsService {
constructor(@InjectModel(CatSchema) private readonly catModel: Model<Cat>) {}
async create(createCatDto: CreateCatDto): Promise<Cat> {
const createdCat = new this.catModel(createCatDto);
return await createdCat.save();
}
async findAll(): Promise<Cat[]> {
return await this.catModel.find().exec();
}
}
就这样。完整的源代码在这里可用。
?> 不要忘记将 CatsModule 导入根 ApplicationModule。
身份验证(Passport)
Passport 是最受欢迎的认证库,几乎全球所有的 node.js 开发人员都知道,并且已经在很多生产应用程序中使用。将此工具与 Nest 框架集成非常简单。为了演示目的,我将设置 Passport-jwt 策略。
要使用这个库,我们必须安装所有必需的依赖关系:
$ npm install --save passport passport-jwt jsonwebtoken
首先,我们要创建一个 AuthService。该类将包含2种方法,(1)使用假用户创建令牌,(2)从解码的 JWT(硬编码true)中验证签名用户。
auth.service.ts
import * as jwt from 'jsonwebtoken';
import { Component } from '@nestjs/common';
@Component()
export class AuthService {
async createToken() {
const expiresIn = 60 * 60, secretOrKey = 'secret';
const user = { email: 'thisis@example.com' };
const token = jwt.sign(user, secretOrKey, { expiresIn });
return {
expires_in: expiresIn,
access_token: token,
};
}
async validateUser(signedUser): Promise<boolean> {
// put some validation logic here
// for example query user by id / email / username
return true;
}
}
?> 在最佳情况下,jwt 对象和令牌配置(密钥和过期时间)应作为自定义组件提供并通过构造函数注入。
Passport 使用策略的概念来认证请求。在本章中,我们将扩展 passport-jwt 包提供的策略 JwtStrategy:
jwt.strategy.ts
import * as passport from 'passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { Component, Inject } from '@nestjs/common';
import { AuthService } from '../auth.service';
@Component()
export class JwtStrategy extends Strategy {
constructor(private readonly authService: AuthService) {
super(
{
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
passReqToCallback: true,
secretOrKey: 'secret',
},
async (req, payload, next) => await this.verify(req, payload, next)
);
passport.use(this);
}
public async verify(req, payload, done) {
const isValid = await this.authService.validateUser(payload);
if (!isValid) {
return done('Unauthorized', false);
}
done(null, payload);
}
}
JwtStrategy 使用 AuthService 来验证有效负载 (已签名的用户)。当有效载荷有效时, 该请求可以由路由处理程序处理。否则, 用户将收到 401 Unauthorized 响应。
最后一步是创建一个 AuthModule:
auth.module.ts
import * as passport from 'passport';
import {
Module,
NestModule,
MiddlewaresConsumer,
RequestMethod,
} from '@nestjs/common';
import { AuthService } from './auth.service';
import { JwtStrategy } from './passport/jwt.strategy';
import { AuthController } from './auth.controller';
@Module({
components: [AuthService, JwtStrategy],
controllers: [AuthController],
})
export class AuthModule implements NestModule {
public configure(consumer: MiddlewaresConsumer) {
consumer
.apply(passport.authenticate('jwt', { session: false }))
.forRoutes({ path: '/auth/authorized', method: RequestMethod.ALL });
}
}
技巧是提供一个 JwtStrategy 作为组件, 并在实例创建后立即设置策略 (在构造函数内)。此外, 我们还将功能中间件绑定到/auth/authorized 的路由 (仅用于测试目的)。
完整的源代码在这里可用。