novakite
v2.1.3
Published
A lightweight TypeScript backend framework
Maintainers
Readme
Novakite 使用文档
简介
Novakite 是一个轻量级的 TypeScript 后端框架,内置路由、中间件、参数校验、IP 黑名单、文件上传、Session/Cookie、静态资源、CORS、压缩、超时、优雅关闭等功能,让你用极少的代码构建健壮的 API 服务。
安装
npm install novakite运行依赖:
busboy(multipart 解析),框架自动安装。 开发依赖:你需要tsx来运行 TypeScript。
项目配置
tsconfig.json(必须开启装饰器)
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"experimentalDecorators": true
},
"include": ["src"]
}package.json(推荐使用 ESM)
{
"type": "module",
"scripts": {
"dev": "tsx src/index.ts"
}
}⚠️ 注意:框架本身发布为 ES Module,用户项目最好也采用 ESM(
"type": "module")以避免兼容问题。
快速开始
目录结构
my-app/
├── src/
│ ├── index.ts
│ └── routes/
│ └── user.controller.ts
├── package.json
└── tsconfig.json入口文件 src/index.ts
import { Novakite } from 'novakite'
const app = new Novakite()
// 自动扫描 routes 目录,按子目录分组路由
app.scanRoutes('./src/routes')
// 启动服务,默认端口 3000
app.listen()控制器 src/routes/user.controller.ts
import { Controller, Get, Post, type, HttpException,RouterRequest } from 'novakite'
// 定义校验规则
const CreateUser = type({
name: 'string',
email: 'email',
age: 'number',
})
@Controller()
export class UserController extends RouterRequest {
@Get('/users')
list() {
return { data: [{ id: 1, name: 'John' }] }
}
@Get('/users/:id')
detail() {
const { id } = this.request.params
if (id !== '1') throw new HttpException(404, '用户不存在')
return { data: { id, name: 'John' } }
}
@Post('/users', CreateUser)
create() {
const body = this.request.body // 自动校验,类型安全
return { data: { id: 2, ...body }, message: '创建成功' }
}
}启动
npx tsx src/index.ts核心概念
1. 控制器与路由
使用 @Controller() 装饰器声明控制器,方法上使用 @Get、@Post、@Put、@Delete 装饰路由。
无需传递 request 参数,直接通过 this.request 访问请求上下文(已注入)。
@Controller()
export class MyController extends RouterRequest {
@Get('/hello')
hello() {
return { data: 'world' }
}
}路由匹配支持动态参数:
@Get('/posts/:year/:month')
getPost() {
const { year, month } = this.request.params
}路由分组:目录结构自动映射为路由前缀。
src/routes/
├── admin/ → 前缀 /admin
│ └── dashboard.controller.ts
├── user.controller.ts → 无前缀scanRoutes('./src/routes') 会自动处理。
2. 请求上下文 this.request
通过 this.request 可以获取所有请求相关信息:
| 属性/方法 | 说明 |
| :----------------------------------------------------------- | :-------------------------------------------- |
| this.request.params | 路径参数 |
| this.request.query | 查询参数 |
| this.request.body | 请求体(JSON/form/multipart 自动解析) |
| this.request.ip | 客户端 IP(透明代理下可取 X-Forwarded-For) |
| this.request.method | HTTP 方法 |
| this.request.path | 请求路径 |
| this.request.headers | 请求头 |
| this.request.global.token | 从 Authorization 头解析的 token(可配置前缀) |
| this.request.global.cookie.get(name) | 读取 Cookie(只读) |
| this.request.global.set(key, value) / get(key) | 请求级临时存储 |
| this.request.cookies.get(name) / set(name,value,opts) / delete(name) | Cookie 操作 |
| this.request.session.get(key) / set(key,value) / destroy() | Session 操作 |
| this.request.file(fieldname) | 单个上传文件 |
| this.request.files(fieldname) | 多个上传文件 |
| this.request.allFiles() | 所有上传文件 |
| this.request.setHeader(key, value) | 设置响应头 |
| this.request.status(code) | 设置 HTTP 状态码 |
3. 响应
普通 JSON 响应:直接 return 一个对象,框架自动包裹成统一格式 { code, data, message }。
return { data: user }; // { code:200, data:user, message:"success" }
return { data: user, code:1000, message:'自定义' };设置 HTTP 状态码和响应头:
return { data: {}, __code__: 201 }; // HTTP 201
return { data: {}, __handler__: { 'X-Custom': 'val' } }; // 自定义头设置 Cookie(响应阶段):
return {
data: {},
__cookie__: [{ name: 'token', value: 'xxx', maxAge: 3600, httpOnly: true }]
};特殊响应:使用 ResFiles、resRedirectUrl、SSR 工具函数。
import { ResFiles, resRedirectUrl, SSR } from 'novakite'
// 文件下载
return ResFiles.returnFile('./report.xlsx', '报表.xlsx');
// 流式响应(SSE、视频流等)
const stream = createReadStream('./video.mp4');
return ResFiles.stream(stream, 'video/mp4');
// 空响应 204
return ResFiles.empty();
// 重定向
return resRedirectUrl('/new', 301);
// SSR 渲染(需先注入渲染器)
return SSR.renderPage(App, { user: {...} });
// 或直接返回 HTML 字符串
return SSR.html('<h1>Hello</h1>');配置项
创建应用时可传入配置:
const app = new Novakite({
// 存储驱动(默认本地 ./uploads)
storage: new LocalStorageDriver('./uploads'),
// 静态资源
static: [
{ name: 'public', path: './public' },
{ name: 'assets', path: './dist/assets' },
],
// CORS(传入 cors 包实例)
cors: cors({ origin: '*' }),
// Session 配置
session: {
secret: 'my-secret',
maxAge: 86400, // 秒,默认 24h
store: new RedisStore(...) // 可选,不传则使用内存存储
},
// 请求超时(毫秒),默认无限制
timeout: 30000,
// gzip 压缩,默认开启,阈值 1024 字节
compress: { threshold: 2048 }, // 设为 false 关闭
// token 解析前缀,默认 false 不解析
auth: 'Bearer',
// SSR 渲染器注入
ssr: {
render: (component, props) => renderToString(component)
}
})listen 方法
app.listen(); // 3000, debug false
app.listen(3000); // 3000
app.listen({ port: 3000, debug: true });
app.listen({ debug: true });
app.listen(config => {
config.port = 8080;
config.debug = true;
});
app.listen({ port: 3000 }, () => console.log('ready'));debug: true 会开启 /__routes 路由信息查看。
中间件
全局中间件:使用 app.use(mw) 或 app.useArrFuns([mw1, mw2])。
app.use(async (request, next) => {
console.log('before')
await next()
console.log('after')
})分组中间件:使用 @GroupMiddleware 装饰器,应用于整个控制器。
@GroupMiddleware(authMiddleware)
@Controller()
export class AdminController { ... }路由中间件:使用 @Use(mw) 装饰器,应用于单个路由。
@Get('/profile')
@Use(authMiddleware)
profile() { ... }中间件执行顺序:全局 → 分组 → 路由级(@Blacklist、@Use)→ 处理器。
参数校验
使用 type() 定义校验模板,传给 @Post 的第二个参数。
const LoginSchema = type({ username: 'string', password: 'string' });
@Post('/login', LoginSchema)
login() {
const { username, password } = this.request.body;
}支持的字段类型:'string' | 'number' | 'boolean' | 'email' | 'url' | 'date'
IP 黑名单
在项目根目录放置 BU.json:
{
"blacklist": ["192.168.1.1", "10.0.0.5"]
}在路由上使用 @Blacklist() 装饰器,也可以追加额外 IP:
@Get('/admin')
@Blacklist()
admin() { ... }
@Get('/sensitive')
@Blacklist({ ips: ['192.168.1.100'] })
sensitive() { ... }命中返回 { code: 403, data: {}, message: "IP blocked" }。
文件上传
支持 multipart/form-data,文件通过 this.request.file() 访问。
@Post('/upload')
async upload() {
const file = this.request.file('avatar'); // 取第一个文件
const files = this.request.files('photos'); // 取全部同名文件
// 保存文件(使用全局存储驱动)
const url = await file.save(); // 或 file.saveWith(driver, name)
return { data: { url } };
}上传进度:
this.request.onProgress(percent => console.log(`${percent}%`));文件大小限制(路由级别):
@Post('/upload', undefined, { maxFileSize: 10 * 1024 * 1024 })Session
框架自动通过 novakite_session Cookie 维护 Session。
this.request.session.set('userId', '123');
const uid = this.request.session.get('userId');
this.request.session.destroy();Session 数据在响应结束时自动保存。默认使用内存存储,可自定义存储(实现 SessionStore 接口)。
Cookie
读取:this.request.cookies.get('token') 或 this.request.global.cookie.get('token')。
设置:
- 在 Handler 中通过
return { __cookie__: [...] }批量设置(推荐) - 在中间件中通过
this.request.cookies.set(name, value, options)设置
WebSocket
框架暴露 app.server(getter),可接入任何 WebSocket 库。
import { WebSocketServer } from 'ws'
const wss = new WebSocketServer({ server: app.server })
wss.on('connection', ws => { ... })优雅关闭
process.on('SIGTERM', () => app.close()); // 优雅关闭,等待现有请求完成(默认 10s 超时)
process.on('SIGINT', () => app.forceClose()); // 立即强制关闭自定义超时:app.close({ timeout: 5000 })。
路由信息查看
当 debug: true 时,访问 GET /__routes 查看所有路由,按分组展示。
[
{
"name": "users",
"prefix": "/users",
"routes": [
{ "method": "GET", "path": "/", "fullPath": "/users", "handler": "list", ... }
]
}
]SSR 支持
注入渲染器:
const app = new Novakite({
ssr: {
render: (component, props) => renderToString(component)
}
})控制器中渲染:
import { SSR } from 'novakite'
@Get('/')
home() {
return SSR.renderPage(App, { title: 'Home' });
}注意事项
- 运行环境:必须使用
tsx运行,不支持ts-node。 - 装饰器:
tsconfig.json必须设置"experimentalDecorators": true。 - 模块系统:框架为 ES Module,用户项目推荐使用
"type": "module",避免混合 CJS 导致问题。 - 控制器:类必须使用
@Controller()装饰,且类名大写开头,否则不会被自动扫描。 - 请求对象:不要手动在方法参数中接收
request,直接通过this.request访问。 - 返回数据:普通 JSON 直接
return对象;特殊响应使用ResFiles、resRedirectUrl、SSR。 - 参数校验:仅在
@Post装饰器中传入type(),@Get等不需要。 - IP 黑名单:需要项目根目录存在
BU.json,文件不存在不会生效。 - Session 持久化:默认内存存储,生产环境建议传入自定义
store(如 Redis)。 - 文件上传:依赖
busboy,框架已内置,无需额外安装。 - 静态资源:配置
static字段,支持多目录。 - CORS:需要自己安装
cors包并传入cors()实例。 - 压缩:gzip 默认开启,阈值 1KB,可关闭或调整。
- 优雅关闭:
app.close()会等待现有请求处理完,建议监听SIGTERM。 - WebSocket:通过
app.server获取原生 Server 对象。
特别强调
🔔 务必使用 @Controller() 并且不传参数给路由方法
Controller 类需要 @Controller() 装饰,路由方法直接通过 this.request 获取请求信息,不要再写 (request: Context) 参数。
🔔 响应统一通过 return 完成
所有响应(JSON、文件、流、重定向、HTML)都通过 return 语句返回,框架自动处理。
🔔 SSR 需要提前注入渲染器
要使用 SSR.renderPage(),必须在 Novakite 构造函数中配置 ssr.render,否则会报错。
🔔 this.request.body 的类型
目前 body 为 Record<string, unknown> | null,需要手动断言为具体类型。未来会增强类型推导。
🔔 __routes 默认关闭
只有 listen({ debug: true }) 时才能访问,生产环境请勿开启。
🔔 导入类型时使用 import type
从 novakite 导入仅用于类型的接口时,请使用 import type { ... } from 'novakite' 以减小编译开销。
