快捷 API
Express.js的功能公用事业,具有类型安全验证和出错处理功能.
安装
npm install @fishka/express
概览
这个软件包提供了与Express直接合作的独立公用功能. 每个特性都可以独立使用——混合并匹配你所需要的.
快速启动
import express from 'express';
import { addErrorHandling, body, patchExpressAsyncErrors, pathParam, queryParam } from '@fishka/express';
import { assertString } from '@fishka/assertions';
const app = express();
app.use(express.json());
// Enable automatic async error catching (call once at startup)
patchExpressAsyncErrors();
// GET /users/:id - with typed path params
app.get('/users/:id', async (req, res) => {
const id = pathParam(req, 'id'); // string - validated inline
res.json({ id, name: 'John' });
});
// GET /users - with query params
app.get('/users', async (req, res) => {
const page = queryParam(req, 'page'); // string - throws 400 if missing
res.json({ page, users: [] });
});
// POST /users - with body validation
app.post('/users', async (req, res) => {
const data = body(req, { name: assertString });
res.json({ id: 1, name: data.name }); // validators infer a type for the data!
});
// Add error handling middleware (must be last)
addErrorHandling(app);
app.listen(3000);
公用事业
pathParam(req, name, validator?)
从Express请求获取并验证路径参数.
import { pathParam, transform, toInt, minLength } from '@fishka/express';
app.get('/users/:id', async (req, res) => {
// Simple - returns string, throws 400 if missing
const id1 = pathParam(req, 'id');
// With validation - transform to number
const id2 = pathParam(req, 'id', transform(toInt()));
// Transform with validation - string min length
const slug = pathParam(req, 'slug', transform(minLength(3)));
res.json({ id: '...' });
});
queryParam(req, name, validator?)
从Express请求获取并验证查询参数.
import { queryParam, transform, toInt, min, range, oneOf } from '@fishka/express';
app.get('/users', async (req, res) => {
// Simple - returns string, throws 400 if missing
const search = queryParam(req, 'search');
// With validation - transform to number with min value
const page = queryParam(req, 'page', transform(toInt(), min(1)));
// With validation - number range
const limit = queryParam(req, 'limit', transform(toInt(), range(1, 100)));
// With validation - enum
const sort = queryParam(req, 'sort', transform(oneOf('asc', 'desc')));
res.json({ page, limit, users: [] });
});
body(req, validator)
获取并验证请求机构.
import { body } from '@fishka/express';
import { assertString, assertNumber } from '@fishka/assertions';
app.post('/users', async (req, res) => {
// Object assertion - validates and infers a type.
const data = body(req, {
name: assertString,
age: assertNumber,
});
res.json({ id: 1, name: data.name });
});
patchExpressAsyncErrors()
补丁快取以自动捕获同步错误 。 应用程序启动时呼叫一次 .
import { patchExpressAsyncErrors } from '@fishka/express';
// Call before defining routes
patchExpressAsyncErrors();
// Now async route handlers don't need try/catch
app.get('/users', async (req, res) => {
const users = await db.users.findAll(); // Errors are caught automatically
res.json(users);
});
addErrorHandling(app)
添加处理中间软件的集中错误 。 必须在所有路线后呼叫.
import { addErrorHandling, HttpError } from '@fishka/express';
// Define routes...
app.get('/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
assertHttp(user, 404, 'User not found');
res.json(user);
});
// Add error handling last
addErrorHandling(app);
参数验证
使用 transform() 用于验证和转换参数。 所有运算符都是可复合的 :
import { transform, toInt, minLength, matches, min, range, oneOf } from '@fishka/express';
app.get('/users/:id', async (req, res) => {
const id = pathParam(req, 'id', transform(toInt())); // string → number
const page = queryParam(req, 'page', transform(toInt(), min(1))); // number >= 1
const limit = queryParam(req, 'limit', transform(toInt(), range(1, 100))); // number 1-100
const sort = queryParam(req, 'sort', transform(oneOf('asc', 'desc'))); // enum
const search = queryParam(req, 'search', transform(minLength(3))); // string min 3 chars
res.json({ id, page, limit });
});
可用的运算符
转换(字符串-T):
toInt()- 解为整数toNumber()- 解说数字toBool()- 分析"真"/"假"以布尔oneOf('a', 'b')- 激发值
字符串验证器 :
minLength(n)- 最小长度maxLength(n)- 最大长度matches(/regex/)- 正则匹配trim- 修剪白空间lowercase编号 :uppercase- 案件转换
数字验证符 :
min(n)- 最低价值max(n)- 最大值range(min, max)- 数值范围
常规 :
transform(...ops)- 验证器/转换器链check(predicate, msg)- 带有上游的自定义验证validator(fn)- 返回字符串的自定义验证器map(fn)- 转换价值
所需参数
pathParam(req, 'name')- 返回字符串( 如果缺少的话, 移动400)queryParam(req, 'name')- 返回字符串( 如果缺少/ 空)queryParam(req, 'name', validator)- 返回已验证的值( 如果缺少/ 空/ 无效, 则扔出 400)- 所有参数都需要 - 缺少或空值丢出 BAD REQEST
认证
import { createAuthMiddleware, BasicAuthStrategy, getAuthUser } from '@fishka/express';
const auth = new BasicAuthStrategy(async (user, pass) =>
user === 'admin' && pass === 'secret' ? { id: '1', role: 'admin' } : null,
);
app.get('/profile', createAuthMiddleware(auth), async (req, res) => {
const user = getAuthUser(req);
res.json({ id: user.id });
});
限制费率
import { createRateLimiterMiddleware } from '@fishka/express';
app.use(
await createRateLimiterMiddleware({
points: { read: 100, write: 50 },
duration: 60,
}),
);
HTTP 错误
使用 HttpError 特定状态代码:
import { HttpError, HTTP_UNAUTHORIZED, HTTP_NOT_FOUND } from '@fishka/express';
app.get('/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
if (!req.headers.authorization) {
throw new HttpError(HTTP_UNAUTHORIZED, 'Authorization required');
}
if (!user) {
throw new HttpError(HTTP_NOT_FOUND, 'User not found');
}
res.json(user);
});
使用 assertHttp 内在断言:
import { assertHttp, HTTP_UNAUTHORIZED, HTTP_NOT_FOUND } from '@fishka/express';
app.get('/admin', async (req, res) => {
assertHttp(req.headers.authorization, HTTP_UNAUTHORIZED, 'Authorization required');
const user = await db.users.findById(req.params.id);
assertHttp(user, HTTP_NOT_FOUND, 'User not found');
assertHttp(user.isAdmin, HTTP_FORBIDDEN, 'Admin access required');
res.json({ admin: true });
});
配置
请求编号
import { configureExpressApi, createTlsMiddleware } from '@fishka/express';
// Enable request ID with custom header name
configureExpressApi({
requestIdHeader: 'x-request-id', // or 'x-correlation-id', 'trace-id', etc.
trustRequestIdHeader: true, // default: true, if we trust and propagate request ID passed by client.
});
// Add TLS middleware to track request context
app.use(createTlsMiddleware());
处理器
处理未发现的错误和优雅的关闭 :
import { installProcessHandlers } from '@fishka/express';
installProcessHandlers({
onUncaughtException: err => sendToMonitoring(err),
onUnhandledRejection: reason => sendToMonitoring(reason),
onShutdown: async () => {
await database.close();
await server.close();
},
shutdownTimeout: 15000,
});
完整示例
import express from 'express';
import {
addErrorHandling,
body,
min,
patchExpressAsyncErrors,
pathParam,
queryParam,
range,
toInt,
transform,
} from '@fishka/express';
import { assertString } from '@fishka/assertions';
import { assertHttp } from './api.types';
const app = express();
app.use(express.json());
// Enable automatic async error catching
patchExpressAsyncErrors();
// Routes
app.get('/users/:id', async (req, res) => {
const id = pathParam(req, 'id', transform(toInt()));
const user = await db.users.findById(id);
assertHttp(user, 404, 'User not found');
res.json(user);
});
app.get('/users', async (req, res) => {
const page = queryParam(req, 'page', transform(toInt(), min(1)));
const limit = queryParam(req, 'limit', transform(toInt(), range(1, 100)));
const users = await db.users.findAll({ page, limit });
res.json({ users, page, limit });
});
app.post('/users', async (req, res) => {
const data = body(req, { name: assertString });
const user = await db.users.create(data);
res.status(201).json(user);
});
// Error handling (must be last)
addErrorHandling(app);
app.listen(3000);
许可证
麻省理工学院