TypeNexus 是一个优秀的 API 封装和管理工具。它提供了一种清晰、轻量级的方式来捆绑 TypeORM + Express.js 功能,帮助您更快地构建应用程序,同时减少模板代码冗余和类型转换工作。
$ npm install typenexus
在您的项目的 tsconfig.json
文件中设置以下选项很重要:
{
"emitDecoratorMetadata": true,
"experimentalDecorators": true
}
在您的项目的 package.json
文件中设置以下选项很重要:
{
"type": "module"
}
import { TypeNexus } from 'typenexus';
(async () => {
const app = new TypeNexus();
await app.start();
// 在浏览器中打开 http://localhost:3000
})();
实体是一个映射到数据库表(或在使用 Postgres
时映射到集合)的类。您可以通过定义一个新的类并用 @Entity()
标记来创建实体:
./src/user.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typenexus';
// 或者:
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
username: string;
@Column({ select: false })
password: string;
@CreateDateColumn()
createAt: Date;
}
./src/user.controller.ts
import { TypeNexus, Controller, Param, Body, DataSource } from 'typenexus';
import { Get, Post, Put, Delete, Patch, Delete, Head } from 'typenexus';
import { User } from './user.entity.js';
@Controller('/api/users')
export class UserController {
constructor(@DSource() private dataSource: DataSource) {}
@Get() // => GET /api/users
public async getAll(): Promise<User[]> {
return this.dataSource.manager.find(User);
}
@Get('/:id') // => GET /api/users/:id
public async getById(@Param('id') id: string): Promise<User> {
return this.dataSource.manager.findOne(User, id);
}
@Post('/:id') // => POST /api/users/:id
public async modify(@Body() body: { name: string; }): Promise<{ name: string; }> {
return { name: body.name + '~~' }
}
@Put('/:id') // => PUT /api/users/:id
public async modify(@Param('id') id: string): Promise<{ uid: string; }> {
return { uid: id }
}
@Delete('/:id') // => DELETE /api/users/:id
public async modify(@Param('id') id: string): Promise<{ uid: string; }> {
return { uid: id }
}
@Patch('/:id') // => PATCH /api/users/:id
public async patch(): Promise<any> {
return { id: 12 }
}
@Head('/:id') // => HEAD /api/users/:id
public async head(): Promise<{ id: number; }> {
return { id: 12 }
}
}
这个类将在您的服务器框架 Express.js 中注册在方法装饰器中指定的路由。
./src/index.ts
import { TypeNexus } from 'typenexus';
import { UserController } from './user.controller.js';
;(async () => {
const app = new TypeNexus();
// ❶ 执行与数据库的连接。
await app.connect({
type: 'postgres',
host: process.env.HOST || 'localhost',
port: 5432,
username: process.env.DB_USER || 'postgres',
password: process.env.DB_PASS || 'wcjiang',
database: process.env.DB_NAME || 'typenexus-base',
synchronize: true,
logging: true,
entities: ['dist/entity/*.js'],
// 或者:
// entities: [User],
});
// ❷ 🚨 请务必在 `app.connect()` 后使用它。
app.controllers([UserController]);
// ❸ 监听连接。
await app.start();
})();
在浏览器中打开 http://localhost:3000/users。你会看到此操作会返回所有用户。如果你打开 http://localhost:3000/api/users/1
,你会看到此操作返回用户数据。
└── src
├── user.controller.ts
├── user.entity.ts
└── index.ts
DataSource
只有在设置了 DataSource 后,您才能与数据库进行交互。TypeORM 的 DataSource 包含了您的数据库连接设置,并根据您使用的 RDBMS 建立初始数据库连接或连接池。
import { TypeNexus } from 'typenexus';
import crypto from 'crypto';
import User from './entity/User.js'
const app = new TypeNexus(3000, { .... });
await app.connect();
// 您可以在此处使用 DataSource 示例。
// 🚨 请务必在 `app.connect()` 后使用它。
const repos = app.dataSource.getRepository(User);
// 检查是否存在管理员账户。
const adminUser = await repos.findOneBy({ username: 'wcj' });
if (!adminUser) {
const hashPassword = crypto.createHmac('sha256', '1234').digest('hex');
// 创建一个管理员账户。
const user = await repos.create({
username: 'wcj',
name: '管理员',
password: hashPassword,
});
await repos.save(user);
}
// 🚨 请务必在 `app.connect()` 后使用它。
app.controllers([UserController]);
await app.start();
使用 app.dataSource 来获取 DataSource 实例。
DataSourceOptions
dataSourceOptions
是在创建新的 DataSource
实例时传递的数据源配置。不同的 RDBMS 有它们自己特定的选项。
import { TypeNexus, TypeNexusOptions } from 'typenexus';
const options: TypeNexusOptions = {
dataSourceOptions: {
type: 'postgres',
host: process.env.POSTGRES_HOST || 'localhost',
port: 5432,
username: process.env.POSTGRES_USER || 'postgres',
password: process.env.POSTGRES_PASSWORD || 'wcjiang',
database: process.env.POSTGRES_DB || 'typenexus-base',
synchronize: true,
logging: true,
entities: ['dist/entity/*.js'],
// 或者:
// entities: [User],
},
}
;(async () => {
const app = new TypeNexus(3000, options);
await app.connect();
app.controllers([UserController]);
app.express.disable('x-powered-by');
await app.start();
})();
也可以在 app.connect()
方法内作为参数传递:
await app.connect({ ... });
Entity
?Entity
是一个映射到数据库表(或在使用 Postgres
时映射到集合)的类。您可以通过定义一个新的类并使用 @Entity()
标记来创建一个实体:
import { Entity, PrimaryGeneratedColumn, Column } from "typenexus"
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column()
firstName: string
@Column()
lastName: string
@Column()
isActive: boolean
}
这将创建以下数据库表:
+-------------+--------------+----------------------------+
| user |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| firstName | varchar(255) | |
| lastName | varchar(255) | |
| isActive | boolean | |
+-------------+--------------+----------------------------+
import { DataSourceOptions } from 'typeorm';
import { OptionsUrlencoded, OptionsJson, OptionsText, Options } from 'body-parser';
import { SessionOptions } from "express-session";
export interface TypeNexusOptions {
port?: number;
/** 全局路由前缀,例如 '/api'。 */
routePrefix?: string;
/** DataSourceOptions 是用于特定 DataSource 的设置和选项的接口。 */
dataSourceOptions?: DataSourceOptions;
/** 创建一个会话中间件 */
session?: SessionResult | SessionCallback;
/**
* 指示是否启用了默认的 TypeNexus 错误处理程序。
* 默认情况下启用。
*/
defaultErrorHandler?: boolean;
/**
* 指示 TypeNexus 是否应该运行在开发模式中。
*/
developmentMode?: boolean;
/** Node.js 的 body 解析中间件。 */
bodyParser?: {
/**
* 返回解析所有请求主体为字符串的中间件,仅查看 Content-Type 标头与类型选项匹配的请求。
*/
text?: OptionsText;
/**
* 返回将所有主体解析为缓冲区的中间件,仅查看 Content-Type 标头与类型选项匹配的请求。
*/
raw?: Options;
/**
* 返回仅解析 json 的中间件,仅查看 Content-Type 标头与类型选项匹配的请求。
*/
json?: false | OptionsJson;
/**
* 返回仅解析 urlencoded 格式的请求主体的中间件,并且仅查看 Content-Type 标头与类型选项匹配的请求。
* 用于解析 application/x-www-form-urlencoded 格式的请求主体。
* @default `{extended:false}`
*/
urlencoded?: false | OptionsUrlencoded;
};
/**
* 指示是否启用了跨域资源共享。
* 这需要安装额外的模块(express 的 cors)。
*/
cors?: boolean | CorsOptions;
/** Node.js 的压缩中间件。支持以下压缩编码:deflate | gzip */
compression?: false | CompressionOptions;
/** 默认设置 */
defaults?: {
/**
* 如果设置,所有空响应将默认返回指定的状态码
*/
nullResultCode?: number;
/**
* 如果设置,所有未定义的响应将默认返回指定的状态码
*/
undefinedResultCode?: number;
};
/**
* 用于每个请求检查用户授权角色的特殊函数。
* 必须返回 true 或解析为 boolean true 的 promise 才能授权成功。
*/
authorizationChecker?: (action: Action, roles: any[]) => Promise<boolean> | boolean;
/**
* 用于获取当前已授权用户的特殊函数。
*/
currentUserChecker?: (action: Action) => Promise<any> | any;
}
参数配置示例:
new TypeNexus(3000, { routePrefix: 'api' });
@Req()
装饰器注入了一个 Request 对象,而 @Res()
装饰器注入了一个 Response 对象。如果你安装了类型定义,你可以使用它们的类型:
import { Controller, Req, Res, Get } from 'typeorm';
import { Response, Request } from 'express';
@Controller()
export class UserController {
@Get('/users') // => GET /users
getAllUsers(@Req() request: Request, @Res() response: Response) {
return response.send('Hello response!');
}
@Get('/posts') // => GET /posts
getAllPosts(@Req() request: Request, @Res() response: Response) {
// 一些响应函数不会返回响应对象,
// 因此需要明确地返回它
response.redirect('/users');
return response;
}
}
你可以直接使用框架的请求和响应对象。如果你想自己处理响应,只需确保从动作中返回响应对象本身。
如果你想要给所有路由添加前缀,例如 /api
,你可以使用 routePrefix
选项:
import { TypeNexus } from 'typenexus';
import { UserController } from './controller/User.js';
;(async () => {
const app = new TypeNexus(3033);
// 🚨 请确保将其放在 `app.controllers()` 之前
app.routePrefix = '/api'
app.controllers([UserController]);
})();
你也可以通过在实例化 TypeNexus
时的参数中配置 routePrefix
来实现相同的效果:
const app = new TypeNexus(3033, {
routePrefix: '/api'
});
你可以为所有特定控制器的动作添加基本路由前缀:
import { Controller, Get } from 'typeorm';
@Controller('/api')
export class UserController {
@Get("/users/:id") // => GET /api/users/12
public async getOne() {}
@Get("/users") // => GET /api/users
public async getUsers() {}
// ...
}
@DSource()
装饰器注入了一个 DataSource 对象。
支持构造函数中的 @DSource()
装饰器
import { Controller, Get, DSource, DataSource } from 'typenexus';
import { Response, Request } from 'express';
import { User } from '../entity/User.js';
@Controller('/users')
export class UserController {
constructor(@DSource() private dataSource: DataSource) {}
@ContentType('application/json')
@Get() // => GET /users
public async getUsers(): Promise<User[]> {
return this.dataSource.manager.find(User);
}
}
支持参数中的 @DSource()
装饰器
import { Controller, Get, DSource, DataSource } from 'typenexus';
import { Response, Request } from 'express';
import { User } from '../entity/User.js';
@Controller('/users')
export class UserController {
@Get() // => GET /users
public async getUsers(@DSource() dataSource: DataSource): Promise<User[]> {
return dataSource.manager.find(User);
}
}
要注入请求体,使用 @Body
装饰器:
import { Controller, Post, Body } from 'typeorm';
type UserBody = { username: string; id: number; };
@Controller()
export class UserController {
@Post("/users") // => POST /users
saveUser(@Body() user: UserBody) {
// ...
}
}
要注入请求体参数,使用 @BodyParam
装饰器:
import { Controller, Post, BodyParam } from 'typeorm';
type UserBody = { username: string; id: number; };
@Controller()
export class UserController {
@Post("/users") // => POST /users
saveUser(@BodyParam("name") userName: string) {
// ...
}
}
要注入请求头参数,使用 @HeaderParam
装饰器:
import { Controller, Post, HeaderParam } from 'typeorm';
@Controller()
export class UserController {
@Post("/users")
saveUser(@HeaderParam("authorization") token: string) {
// ...
}
}
如果你想要注入所有的请求头参数,使用 @HeaderParams()
装饰器。
要注入查询参数,使用 @QueryParam
装饰器:
import { Controller, Get, QueryParam } from 'typeorm';
type UserBody = { username: string; id: number; };
@Controller()
export class UserController {
@Get("/users")
public async getUsers(@QueryParam("limit") limit: number) {
// ....
}
}
如果你想要注入所有的查询参数,使用 @QueryParams()
装饰器。
import { Controller, Get, QueryParams } from 'typeorm';
@Controller()
export class UserController {
@Get("/users")
public async getUsers(@QueryParams() query: any) {
// ....
}
}
你可以使用 @Param
装饰器在你的控制器动作中注入参数:
import { Controller, Get, Param } from 'typeorm';
@Controller()
export class UserController {
@Get("/users/:id")
getOne(@Param("id") id: string) {}
}
如果你想要注入所有的参数,使用 @Params()
装饰器。
要获取一个 Cookie 参数,使用 @CookieParam
装饰器:
import { Controller, Get, CookieParam, CookieParams } from 'typeorm';
@Controller()
export class UserController {
@Get("/users")
public async getUsers(@CookieParam("token") token: string) {
// ....
}
}
如果你想要注入所有的 Cookie 参数,使用 @CookieParams()
装饰器。
要注入会话值,使用 @SessionParam
装饰器:
@Get("/login")
savePost(@SessionParam("user") user: User, @Body() post: Post) {}
如果你想要注入主会话对象,使用 @Session()
而不带任何参数。
@Get("/login")
savePost(@Session() session: any, @Body() post: Post) {}
Express 使用 express-session
来处理会话,所以你首先必须手动安装它才能使用 @Session
装饰器。以下是配置 Session 的示例,你还需要为 Session 创建一个数据库表实体:
import { TypeNexus, TypeNexusOptions } from 'typenexus';
import { UserController } from './controller/User.js';
import { Session } from './entity/Session.js';
const options: TypeNexusOptions = {
// ...
dataSourceOptions: { ... },
session: {
secret: 'secret',
resave: false,
saveUninitialized: false,
repositoryTarget: Session,
typeormStore: {
cleanupLimit: 2,
// limitSubquery: false, // 如果使用 MariaDB。
ttl: 86400,
}
}
}
;(async () => {
const app = new TypeNexus(3001, options);
// ❶ 执行数据库连接。
await app.connect();
// 或者:
// await app.connect(options.dataSourceOptions);
// ❷ 🚨 请务必在 `app.connect()` 后使用。
app.controllers([UserController]);
// ❸ 监听连接。
await app.start();
})();
以下是 Session 的数据库表实体示例:
// ./entity/Session.js
import { Column, Entity, Index, PrimaryColumn, DeleteDateColumn } from 'typeorm';
import { ISession } from 'connect-typeorm';
@Entity()
export class Session implements ISession {
@Index()
@Column('bigint', { transformer: { from: Number, to: Number } })
public expiredAt = Date.now();
@PrimaryColumn('varchar', { length: 255 })
public id = '';
@DeleteDateColumn()
public destroyedAt?: Date;
@Column('text')
public json = '';
}
要注入上传的文件,使用 @UploadedFile
装饰器:
@Post("/file")
saveFile(@UploadedFile("fileName") file: Express.Multer.File) {}
要注入上传的多个文件,使用 @UploadedFiles
装饰器:
@Post("/files")
saveFiles(@UploadedFiles("fileName") file: Express.Multer.File[]) {}
你还可以以这种方式指定上传选项给 multer
:
import type { Options } from 'multer';
// 为了保持代码的整洁,最好将此函数提取到单独的文件中
const fileUploadOptions: () => Options = () => ({
storage: multerFn.diskStorage({
destination: (req, file, cb) => {
//...
},
filename: (req, file, cb) => {
//...
}
}),
fileFilter: (req, file, cb) => {
//...
},
limits: {
fieldNameSize: 255,
fileSize: 1024 * 1024 * 2
}
});
// 使用选项的方式:
@Post("/file")
saveFiles(@UploadedFile("fileName", fileUploadOptions) file: Express.Multer.File) {}
要注入所有上传的文件,请改用 @UploadedFiles
装饰器。typenexus 使用 multer
来处理文件上传。
你可以为任何动作设置 Location
头:
import { Controller, Get, Location } from 'typenexus';
@Controller()
export class UserController {
@Get('/users')
@Location("https://bing.com")
public async detail() {}
}
将响应的 Location HTTP 头设置为指定的路径参数。
你可以为任何动作设置 Redirect
头:
import { Controller, Get, Redirect } from 'typenexus';
@Controller()
export class UserController {
@Get('/users')
@Redirect("http://github.com")
public async detail() {}
}
你可以通过返回一个字符串值来覆盖 Redirect
头:
import { Controller, Get, Redirect } from 'typenexus';
@Controller()
export class UserController {
@Get('/users')
@Redirect("http://github.com")
public async detail() {
return "https://bing.com";
}
}
你可以使用模板来生成 Redirect
头:
import { Controller, Get, Redirect } from 'typenexus';
@Controller()
export class UserController {
@Get('/users')
@Redirect("http://github.com/:owner/:repo")
public async detail() {
return { owner: "jaywcjlove", repo: "typenexus" };
}
}
你可以为任何动作显式地设置返回的 HTTP 状态码:
import { Controller, Post, HttpCode } from 'typenexus';
@Controller()
export class UserController {
@Post('/users')
@HttpCode(201)
public async saveUser() {}
}
如果你的控制器返回 void
或 Promise<void>
或 undefined,它将抛出 404 错误。如果你想要阻止这种情况,你需要使用 @OnUndefined
装饰器来指定你想要返回的状态码。
import { Controller, Param, Delete, OnUndefined, DSource, DataSource } from 'typeorm';
import { User } from '../entity/User.js';
@Controller()
export class UserController {
constructor(@DSource() private dataSource: DataSource) {}
@Delete("/users/:id")
@OnUndefined(204)
async remove(@Param("id") id: string): Promise<void> {
return this.dataSource.manager.findOneBy(User, { id });
}
}
@OnUndefined
在你返回可能为 undefined 或不为 undefined 的对象时也很有用。在这个例子中,findOneBy
如果找不到具有给定 id 的用户,则返回 undefined。此操作将在未找到用户时返回 404
,如果找到用户则返回常规的 200
。
import { Controller, Param, Delete, OnUndefined, DSource, DataSource } from 'typeorm';
import { User } from '../entity/User.js';
@Controller()
export class UserController {
constructor(@DSource() private dataSource: DataSource) {}
@Delete("/users/:id")
@OnUndefined(404)
async remove(@Param("id") id: string): Promise<void> {
return this.dataSource.manager.findOneBy(User, { id });
}
}
你还可以指定当返回 undefined
时要使用的错误类:
import { HttpError } from 'typeorm';
export class UserNotFoundError extends HttpError {
constructor() {
super(404, 'User not found!');
}
}
import { Controller, Param, Delete, OnUndefined, DSource, DataSource } from 'typeorm';
import { User } from '../entity/User.js';
@Controller()
export class UserController {
constructor(@DSource() private dataSource: DataSource) {}
@Get("/users/:id")
@OnUndefined(UserNotFoundError)
async remove(@Param("id") id: string): Promise<void> {
return this.dataSource.manager.findOneBy(User, { id });
}
}
如果控制器动作返回 null
,则可以使用 @OnNull
装饰器。
import { Controller, Get, OnNull, Param } from 'typeorm';
@Controller()
export class UserController {
@Get('/questions/:id')
@OnNull(404)
public async detail(@Param('id') id: string): Promise<string> {
return new Promise((ok, fail) => {
ok(null);
});
}
}
你可以在响应中设置任何自定义头部:
import { Controller, Get, Header, Param } from 'typeorm';
@Controller()
export class UserController {
@Get("/users/:id")
@Header("Cache-Control", "none")
public async getOne(@Param('id') id: string): Promise<string> {
// ...
}
}
如果你正在使用服务器端渲染,你可以**render
**任何模板:
import { Controller, Get, Render } from 'typenexus';
@Controller('/')
export class UserController {
@Get()
@Render("index")
getOne() {
return {
title: "这些参数被使用"
};
}
}
要使用渲染功能,请确保正确配置了 express
。要在 express
中使用渲染功能,你需要使用第三方渲染中间件,例如 ejs。
$ npm install ejs
模板文件所在的目录。例如:app.set('views', './views')
。默认情况下,这将设置为应用程序根目录中的 views 目录。
app.express.set('views', path.join(__dirname, 'views'));
要使用的模板引擎。例如,要使用 ejs
模板引擎:app.set('view engine', 'ejs')
。
app.express.set('view engine', 'ejs');
在 views 目录下创建一个名为 index.ejs
的 ejs
模板文件,内容如下:
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
</head>
<body>
<h1><%= title %></h1>
<p>Welcome to <%= title %></p>
</body>
</html>
完整的入口示例:
import { TypeNexus } from 'typenexus';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { UserController, CustomErrorHandler } from './UserController.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
;(async () => {
const app = new TypeNexus(3002, {
defaultErrorHandler: false,
});
app.express.set('views', path.join(__dirname, 'views'));
app.express.set('view engine', 'ejs');
app.controllers([UserController], [CustomErrorHandler]);
await app.start();
})();
如果你想返回具有特定错误代码的错误,有一个简单的方法:
import { Controller, Get, Header, Param } from 'typeorm';
@Controller()
export class UserController {
@Get("/users/:id")
public async getOne(@Param('id') id: string): Promise<string> {
const user = await dataSource.manager.findOneBy(User, { id });
if (!user) {
throw new NotFoundError(`未找到用户。`); // 消息是可选的
}
return user;
}
}
现在,当找不到请求的 id 对应的用户时,响应将是带有 404 的 HTTP 状态码,并具有以下内容:
{
"name": "NotFoundError",
"message": "未找到用户。"
}
你可以使用一组准备好的错误:
HttpError
BadRequestError
ForbiddenError
InternalServerError
MethodNotAllowedError
NotAcceptableError
NotFoundError
UnauthorizedError
你还可以通过扩展 HttpError
类来创建和使用自己的错误。要定义返回给客户端的数据,你可以在错误中定义一个 toJSON
方法。
class DbError extends HttpError {
public operationName: string;
public args: any[];
constructor(operationName: string, args: any[] = []) {
super(500);
Object.setPrototypeOf(this, DbError.prototype);
this.operationName = operationName;
this.args = args; // 可用于内部日志记录
}
toJSON() {
return {
status: this.httpCode,
failedOperation: this.operationName,
};
}
}
由于 CORS 是几乎在任何 Web API 应用程序中都会使用的功能,你可以在 typenexus 选项中启用它。
import { TypeNexus, Action } from 'typenexus';
import { UserController } from './UserController.js';
;(async () => {
const app = new TypeNexus(3002, {
cors: true,
});
app.controllers([UserController]);
await app.start();
})();
你也可以配置 cors:
import { TypeNexus, Action } from 'typenexus';
import { UserController } from './UserController.js';
;(async () => {
const app = new TypeNexus(3002, {
cors: {
// cors 文档中的选项
},
});
app.controllers([UserController]);
await app.start();
})();
authorization
功能TypeNexus
提供了两个装饰器来帮助你组织应用程序中的授权。
@Authorized
装饰器要使 @Authorized
装饰器起作用,你需要设置特殊的 TypeNexus
选项:
const app = new TypeNexus(3002, { ... });
await app.connect();
app.authorizationChecker = async (action: Action, roles: string[]) => {
// 在这里你可以使用来自 action 的请求/响应对象
// 如果装饰器定义了角色,它需要访问 action
// 你可以使用它们来提供细粒度的访问检查
// 检查器必须返回布尔值(true 或 false)
// 或者解析为布尔值的 promise
// 演示代码:
const token = action.request.query.token || action.request.body.token || (action.request.headers.authorization || '').replace(/^token\s/, '');
if (action.request.session.token !== token) return false;
const dataSource = action.dataSource;
const user = await dataSource.manager.findOne(User, {
where: { username },
select: ['username', 'id', 'roles'],
});
if (user && roles.find(role => user.roles.indexOf(role) !== -1)) return true;
// @ts-ignore
if (action.request.session.token === token) return true;
return false;
}
app.controllers([UserController]);
await app.start();
你可以在控制器动作上使用 @Authorized
:
import { Controller, Authorized, Req, Res, Get } from 'typeorm';
import { Response, Request }from 'express';
@Controller()
export class UserController {
@Authorized('POST_MODERATOR') // 你可以指定角色或角色数组
@Post('/posts') // => POST /posts
create(@Body() post: Post, @Req() request: Request, @Res() response: Response) {
// ...
}
}
@CurrentUser
装饰器要使 @CurrentUser
装饰器起作用,你需要设置特殊的 TypeNexus
选项:
import { TypeNexus, Action } from 'typenexus';
import { UserController } from './UserController.js';
import { User } from './User.js';
;(async () => {
const app = new TypeNexus(3002, {
routePrefix: '/api',
developmentMode: false,
});
app.currentUserChecker = async (action: Action) => {
return new User(1, 'Johny', 'Cage');
}
app.controllers([UserController]);
await app.start();
})();
你可以在控制器动作上使用 @CurrentUser
:
import { Controller, CurrentUser, Get } from 'typenexus';
import { User } from './User.js';
@Controller('/questions')
export class UserController {
@Get()
public async all(@CurrentUser() user?: User): Promise<any> {
return {
id: 1,
title: 'Question by ' + user.firstName,
};
}
}
如果你将 @CurrentUser
标记为 required,并且 currentUserChecker
逻辑返回空结果,那么 TypeNexus
将抛出授权必需错误。
您可以使用任何现有的 express 中间件,或者创建您自己的中间件。要创建自己的中间件,可以使用 @Middleware
装饰器,要使用已存在的中间件,可以使用 @UseBefore
和 @UseAfter
装饰器。
有多种方法可以使用中间件。例如,让我们尝试使用 compression 中间件:
$ npm install compression
import { Controller, Get, UseBefore } from "typeorm";
import compression from 'compression';
@Controller()
export class UserController {
@Get('/users/:id')
@UseBefore(compression())
async getOne(@Param("id") id: string): Promise<any> {
// ...
}
}
这样,compression 中间件仅会应用于 getOne
控制器动作,并且会在动作执行之前执行。要在动作之后执行中间件,请改用 @UseAfter
装饰器。
import { Controller, UseBefore } from "typeorm";
import compression from 'compression';
@Controller()
@UseBefore(compression())
export class UserController { }
这样,compression 中间件将应用于 UserController
控制器的所有动作,并在其动作执行之前执行。您也可以在这里使用 @UseAfter
装饰器。
import { TypeNexus, Action } from 'typenexus';
import { UserController } from './UserController.js';
;(async () => {
const app = new TypeNexus(3002, {
routePrefix: '/api',
developmentMode: false,
});
app.controllers([UserController]);
app.express.use(compression());
await app.start();
})();
或者,您可以创建一个自定义的全局中间件,然后简单地将其执行委托给 compression 模块。
下面是创建 express.js 中间件的示例:
首先,您可以创建一个简单的中间件函数:
import { Request, Response, NextFunction } from 'express';
export function loggingMiddleware(request: Request, response: Response, next?: NextFunction): any {
console.log('do something...');
next();
}
其次,您可以创建一个类:
import { ExpressMiddlewareInterface } from 'typenexus';
export class MyMiddleware implements ExpressMiddlewareInterface {
// 接口实现是可选的
use(request: Request, response: Response, next?: NextFunction): any {
console.log('do something...');
next();
}
}
import { Controller, UseBefore, UseAfter } from 'typeorm';
import { MyMiddleware, MyMiddleware2 } from './MyMiddleware';
import { loggingMiddleware } from './loggingMiddleware';
@Controller()
@UseBefore(MyMiddleware, MyMiddleware2)
@UseAfter(loggingMiddleware)
export class UserController {}
import { Controller, UseBefore, UseAfter, Get } from 'typeorm';
import { MyMiddleware } from './MyMiddleware';
import { loggingMiddleware } from './loggingMiddleware';
@Controller()
export class UserController {
@Get("/users/:id")
@UseBefore(MyMiddleware)
@UseAfter(loggingMiddleware)
getOne(@Param("id") id: string) {
// ...
}
}
@UseBefore
在控制器操作之前执行中间件。@UseAfter
在每个控制器操作后执行中间件。
全局中间件在每个请求之前始终运行。要使您的中间件全局化,请使用 @Middleware
装饰器标记,并指定它是在控制器操作之前还是之后运行。
import { ExpressMiddlewareInterface } from 'typenexus';
import { Request, Response, NextFunction } from 'express';
@Middleware({ type: 'before' })
export class LoggingMiddleware implements ExpressMiddlewareInterface {
use(request: Request, response: Response, next: NextFunction): void {
console.log('do something...');
// @ts-ignore
request.test = 'wcj';
next();
}
}
要启用此中间件,请在 typenexus
初始化期间指定它:
import { TypeNexus } from 'typenexus';
import './LoggingMiddleware.js';
const app = new TypeNexus(3002, {
routePrefix: '/api',
developmentMode: false,
});
或在 app.controllers()
中注册。
import { TypeNexus } from 'typenexus';
import { LoggingMiddleware } from './LoggingMiddleware.js';
import { UserController } from './UserController.js';
const app = new TypeNexus(3002, {
routePrefix: '/api',
developmentMode: false,
});
app.controllers([UserController], [LoggingMiddleware]);
错误处理程序仅针对 Express。错误处理程序与中间件的工作方式相同,但实现了 ExpressErrorMiddlewareInterface
接口:
创建一个实现 ErrorMiddlewareInterface
接口的类:
import { Middleware, ExpressErrorMiddlewareInterface } from 'typenexus';
import { Request, Response, NextFunction } from 'express';
@Middleware({ type: 'after' })
export class CustomErrorHandler implements ExpressErrorMiddlewareInterface {
error(error: any, request: Request, response: Response, next: NextFunction): void {
response.status(error.status || 500);
next();
}
}
自定义错误处理程序在默认错误处理程序之后被调用,因此您将无法更改响应代码或标头。为了防止这种情况,您必须通过在 TypeNexusOptions
中指定 defaultErrorHandler 选项来禁用默认错误处理程序:
import { TypeNexus } from 'typenexus';
const app = new TypeNexus(3002, {
routePrefix: '/api',
developmentMode: false,
defaultErrorHandler: false, // 禁用默认错误处理程序,只有当您有自己的错误处理程序时才需要
});
感谢我们的优秀贡献者们!
使用 contributors 制作。
本软件包采用 MIT 许可证授权。