npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

aggrekite

v5.5.0

Published

A lightweight TypeScript BFF framework with Blueprint routing

Downloads

4,210

Readme

AggreKite

轻量级 TypeScript BFF 框架 —— Blueprint 蓝图 + 函数式路由 + 内置 BFF 聚合层

AggreKite 是一个面向 BFF(Backend For Frontend) 场景的 Node.js HTTP 框架。它以 Blueprint 蓝图 为路由组织单元,内置并发 BFF 聚合、参数校验、Session/Cookie、文件上传、IP 黑名单等能力,支持 ESM only、零装饰器、开箱即用。

适用场景:BFF 层、微服务网关、快速 API 开发、中台聚合服务。


1. 介绍

AggreKite 的核心理念是将 路由定义业务逻辑 解耦:Blueprint 负责声明路由的 HTTP 方法、路径、校验规则和中间件,而 handler 可以内联编写,也可以通过 filepath 指向独立文件(配合柯里化 RouteRegistrar 注册)。

框架内置了 BFF 聚合层 bff(),能够将多个下游微服务调用并发执行、合并结果、支持降级、重试、缓存、链路追踪,大幅减少前端网络请求次数。


2. 安装

npm install aggrekite 
or
npm install aggrekite -g

要求:Node.js >= 18(BFF 聚合依赖原生 fetch)。

CLI 脚手架

npx aggrekite create my-app
cd my-app
npm run dev

CLI 会自动从本地 AggreKite 包读取版本号,生成的项目依赖中已包含对应版本的 aggrekitenpm install 由脚手架自动执行,无需手动安装。


3. 快速开始

最小可运行示例:

// app.ts
import { AggreKite, Blueprint } from 'aggrekite'

const app = new AggreKite()

const hello = new Blueprint({ name: 'hello', prefix: '' })

hello.get('/', (req) => {
  return { data: { message: 'Hello AggreKite!' } }
})

app.mount(hello)
app.listen(3000)

启动:

npx tsx app.ts

访问 http://localhost:3000/ 即可看到响应:

{ "code": 200, "data": { "message": "Hello AggreKite!" }, "message": "success" }

4. 核心概念

4.1 Blueprint 蓝图

Blueprint 是路由的组织单元。通过 name 自动生成路由前缀(默认在 name 后加 s),也可手动指定 prefix

import { Blueprint } from 'aggrekite'

// name="user" → 自动前缀 /users
const users = new Blueprint({ name: 'user' })

// 手动指定前缀
const admin = new Blueprint({ name: 'admin', prefix: '/admin' })

// 根路径
const home = new Blueprint({ name: 'home', prefix: '' })

两种路由注册方式

方式一:内联 handler

users.get('/:id', (req) => {
  return { data: { id: req.params.id } }
})

users.post('/', (req) => {
  return { data: { created: true } }
})

type() 已在 v5.0.0 中移除,参数校验请通过 TypeScript 接口在泛型参数中定义类型约束。

方式二:filepath + 柯里化 RouteRegistrar

使用 $() 辅助函数指定相对路径(相对于蓝图文件所在目录),获得 IDE 路径补全。get/post/put/delete 返回一个柯里化函数,供 handler 文件 import 并调用注册:

// bpRoutes/index.ts
import { Blueprint, $ } from 'aggrekite'

const users = new Blueprint({ name: 'user' })

export const getUser = users.get('/:id',   { filepath: $('../handlers/user/detail.ts') })
export const createUser = users.post('/',     { filepath: $('../handlers/user/create.ts') })
// handlers/user/detail.ts
import { getUser } from '../bpRoutes/index'

getUser((req) => {
  return { data: { id: req.params.id, name: 'John' } }
})

泛型类型推导(v5.0.0+):

通过 RouteOptions 接口 + 泛型参数,get/post<TRoute> 可自动推导 req.body / req.params / req.query 类型:

// bpRoutes/option.ts
export interface RouteOptions {
  login: {
    body: { username: string; password: string }
  }
}

// bpRoutes/index.ts
export const loginCallable = users.post<RouteOptions['login']>('/login', { filepath: $('../handlers/login/index.ts') })

// handlers/login/index.ts
import { loginCallable } from '../bpRoutes/index'

loginCallable((req) => {
  const { username, password } = req.body
  //     ^? string    ^? string  ← IDE 自动推导
  return { token: '...', username }
})

柯里化函数应在文件顶层同步调用。若 handler 文件被 import 但未调用柯里化函数注册 handler,框架会在控制台输出 [AggreKite] 前缀的警告信息。

4.2 请求上下文 req

每个 handler 接收一个 Request 对象,包含以下属性/方法:

| 属性 / 方法 | 类型 | 说明 | |---|---|---| | req.params | Record<string, string> | 路径参数,如 /users/:id{ id: "1" } | | req.query | Record<string, string> | 查询参数 ?page=1&size=10 | | req.body | Record<string, unknown> | 请求体(JSON / 表单 / multipart 自动解析)。初始为 null,首次访问前会自动解析 | | req.ip | string | 客户端 IP(trustProxy 开启时从 X-Forwarded-For 读取) | | req.headers | IncomingHttpHeaders | 原始请求头 | | req.method | string | HTTP 方法(大写) | | req.path | string | 请求路径 | | req.traceId | string | 链路追踪 ID(trace: true 时生效) | | req.req | IncomingMessage | Node.js 原生请求对象 | | req.res | ServerResponse | Node.js 原生响应对象 | | req.global.token | string \| undefined | 从 Authorization 头解析的 token | | req.global.get(key) | unknown | 读取请求级临时数据 | | req.global.set(key, val) | void | 写入请求级临时数据 | | req.global.delete(key) | void | 删除请求级临时数据 | | req.cookies.get(name) | string \| undefined | 读取 Cookie | | req.cookies.set(name, value, opts?) | void | 设置 Cookie | | req.cookies.delete(name) | void | 删除 Cookie | | req.session.get(key) | unknown | 读取 Session | | req.session.set(key, value) | void | 写入 Session | | req.session.destroy() | void | 销毁当前 Session | | req.file(fieldname) | UploadedFile \| undefined | 获取单个上传文件 | | req.files(fieldname) | UploadedFile[] | 获取多个同名上传文件 | | req.allFiles() | Record<string, UploadedFile[]> | 获取所有上传文件 | | req.fileStream() | IncomingMessage | 获取原始请求流 | | req.onProgress(fn) | void | 上传进度回调 | | req.setHeader(key, value) | void | 设置响应头 | | req.status(code) | Request | 设置 HTTP 状态码,返回自身(链式调用) | | req.parseBody() | Promise<Record<string, unknown>> | 手动解析请求体 |

4.3 响应方式

普通 JSON 响应

直接 return 一个对象,框架自动包裹为 { code, data, message } 格式:

return { data: { id: 1 } }
// → { code: 200, data: { id: 1 }, message: "success" }

return { code: 1001, data: {}, message: "业务错误" }
// → { code: 1001, data: {}, message: "业务错误" }

自定义 HTTP 状态码、响应头、Cookie

通过特殊字段 __code__ / __handler__ / __cookie__ 控制:

return {
  data: {},
  __code__: 201,
  __handler__: { 'X-Custom': 'hello', 'Cache-Control': 'no-cache' },
  __cookie__: [
    { name: 'token', value: 'abc123', maxAge: 3600, httpOnly: true, secure: true }
  ],
}

文件下载 / 流式响应 / 空响应 / 重定向

import { ResFiles, resRedirectUrl } from 'aggrekite'

// 文件下载
return ResFiles.returnFile('./report.xlsx', '报表.xlsx')

// 流式响应(视频、SSE 等)
const stream = fs.createReadStream('./video.mp4')
return ResFiles.stream(stream, 'video/mp4')

// 空响应 (204)
return ResFiles.empty()

// 重定向
return resRedirectUrl('/new-page', 301)

SSR HTML 渲染

import { SSR } from 'aggrekite'

return SSR.html('<h1>Hello World</h1>')

// 配合 SSR 渲染器
return SSR.renderPage(MyComponent, { title: 'Home' })

4.4 中间件与钩子

全局中间件

app.wrap() 兼容 Express 中间件签名 (req, res, next)

import cors from 'cors'

app.wrap(cors({ origin: '*' }))

app.wrap((req, res, next) => {
  console.log(`${req.method} ${req.url}`)
  next()
})

Blueprint 级钩子

const users = new Blueprint({ name: 'user' })

// before 钩子:所有路由之前执行,支持 async
users.before(async (req) => {
  const token = req.global.token
  if (!token) throw new HttpException(401, '未登录')
})

// after 钩子:handler 执行之后触发
users.after((req, result) => {
  console.log(`[after] ${req.method} ${req.path}`)
})

// 按 HTTP 状态码捕获错误
users.error(401, (req) => {
  return { code: 401, data: {}, message: '请先登录' }
})

users.error(404, (req) => {
  return { code: 404, data: {}, message: '资源不存在' }
})

4.6 BFF 聚合层

bff() 是框架内置的下游服务聚合函数,能够并发调用多个微服务并将结果合并裁剪。

基本用法

import { bff } from 'aggrekite'

users.get('/:id/profile', bff({
  calls: {
    user: 'http://user-service/users/${params.id}',
    orders: 'http://order-service/orders?userId=${params.id}',
  },
  map(user, orders) {
    return {
      code: 0,
      data: {
        user: (user as any).data,
        orderCount: (orders as any).data?.length ?? 0,
      },
    }
  },
}))
  • calls 中的所有调用 并发执行
  • map 的参数顺序与 calls 定义顺序一致。单个调用失败时对应参数为 null
  • URL 支持以下模板语法(优先级从高到低):

| 语法 | 示例 | 替换来源 | |---|---|---| | ${params.x} | /users/${params.id} | req.params.id | | ${query.x} | /search?q=${query.q} | req.query.q | | ${body.x} | /api/${body.userId} | req.body.userId | | :param | /users/:id | req.params.id(Express 风格) | | {param} | /users/{id} | req.params.id(兜底语法) |

降级 fallback

当所有下游调用全部失败时,返回 fallback 指定的默认值:

bff({
  calls: { user: 'http://user-service/users/${params.id}' },
  map(user) { return { data: (user as any).data } },
  fallback: { code: 0, data: { name: '未知用户' } },
})

重试 retry

支持指数退避重试:第 n 次重试等待 delay × 2^(n-1) 毫秒。

bff({
  calls: { product: 'http://api.example.com/product/1' },
  map(product) { return { data: product } },
  retry: { times: 3, delay: 200 },
  // 第1次: 立即, 第2次: 200ms后, 第3次: 400ms后, 第4次: 800ms后
})

缓存 cache

内存缓存,ttl 为毫秒。缓存键由请求路径 + calls 指纹 + 查询参数(前 10 个)组成。

bff({
  calls: { stats: 'http://api.example.com/stats' },
  map(stats) { return { data: stats } },
  cache: { ttl: 60000 }, // 60 秒
})

超时 timeout

单个调用超时时间(毫秒),默认 10000。

bff({
  calls: { slow: 'http://slow-service/api' },
  map(slow) { return { data: slow } },
  timeout: 3000,
})

链路追踪 trace

开启后自动向下游注入 x-trace-id 请求头:

bff({
  calls: { user: 'http://user-service/users/1' },
  map(user) { return { data: user } },
  trace: true,
})

请求去重 dedup(自动生效)

同一 BFF handler 内部,相同 URL 的并发调用自动合并为单次请求,无需手动配置。

自定义请求头

bff({
  calls: { user: 'http://user-service/users/1' },
  map(user) { return { data: user } },
  headers: (req) => ({
    Authorization: req.headers.authorization || '',
    'X-Trace-Id': req.traceId,
  }),
})

4.7 文件上传

框架自动解析 multipart/form-data,提供三层文件访问 API。

upload.post('/', async (req) => {
  // 单文件
  const file = req.file('avatar')
  if (file) {
    const url = await file.save() // 保存到存储驱动
    return { data: { url, size: file.size } }
  }

  // 多文件(同一字段名)
  const files = req.files('attachments')

  // 全部文件
  const all = req.allFiles()

  // 上传进度
  req.onProgress((percent) => {
    console.log(`上传进度: ${percent}%`)
  })
})

UploadedFile 对象:

| 属性 / 方法 | 类型 | 说明 | |---|---|---| | fieldname | string | 表单字段名 | | originalname | string | 原始文件名 | | encoding | string | 编码 | | mimetype | string | MIME 类型 | | size | number | 文件大小(字节) | | buffer | Buffer | 文件内容 | | save(name?) | Promise<string> | 保存到默认存储驱动,返回 URL | | saveWith(driver, name) | Promise<string> | 保存到指定存储驱动 |

4.8 Session & Cookie

Session

配置 session 后自动通过 aggrekite_session Cookie 维护会话。默认使用 MemoryStore 内存存储。

const app = new AggreKite({
  session: {
    secret: 'my-secret-key',
    maxAge: 86400, // 秒,默认 24 小时
  },
})
// 读取
const userId = req.session.get('userId')

// 写入
req.session.set('userId', 123)

// 销毁
req.session.destroy()

Session ID 经过 HMAC-SHA256 签名,防止篡改。Cookie 属性默认 HttpOnly / SameSite=Lax / Path=/,当 trustProxytrue 时自动追加 Secure

自定义 Session 存储(如 Redis)

实现 SessionStore 接口:

import type { SessionStore } from 'aggrekite'

class RedisStore implements SessionStore {
  async get(id: string) { /* ... */ }
  async set(id: string, data: Record<string, unknown>, maxAge: number) { /* ... */ }
  async destroy(id: string) { /* ... */ }
}

const app = new AggreKite({
  session: { secret: 'my-key', store: new RedisStore() },
})

Cookie

// 读取
const theme = req.cookies.get('theme')

// 设置
req.cookies.set('theme', 'dark', {
  maxAge: 3600,
  httpOnly: false,
  secure: true,
  sameSite: 'Lax',
  path: '/',
  domain: '.example.com',
})

// 删除
req.cookies.delete('theme')

4.9 IP 黑名单

全局黑名单配置文件 BU.json(项目根目录):

{
  "blacklist": ["192.168.1.100", "10.0.0.5"]
}

路由级启用:

// 使用 BU.json 全局黑名单
users.blacklist('/admin')

// 追加额外 IP
users.blacklist('/secret', ['1.2.3.4', '5.6.7.8'])

命中返回 403,响应头 X-Blocked: true,日志字段 blocked: true。黑名单文件缓存 30 秒。

4.10 静态资源

配置 static 数组即可自动提供静态文件服务,支持条件请求 304

const app = new AggreKite({
  static: [
    { name: 'public', path: './public' },
    { name: 'assets', path: './dist/assets' },
  ],
})
  • 访问 /public/logo.png → 映射到 ./public/logo.png
  • 访问 /assets/style.css → 映射到 ./dist/assets/style.css
  • 自动设置 Content-TypeContent-Length
  • 内置路径穿越防护(用于目录遍历攻击)
  • 请求根目录时自动寻找 index.html

4.11 集群模式(Cluster)

server.cluster 默认 true,框架在 listen() 时自动 fork 子进程以利用多核 CPU。

工作原理

  • listen() 内部判断当前进程角色:Primary 进程 fork N 个子进程(N = CPU 核心数),Worker 进程直接启动 HTTP 服务
  • 兼容 PM2:当检测到 NODE_APP_INSTANCE 环境变量时自动跳过 fork,由 PM2 管理多进程
  • cluster: false 时禁用集群,仅启动单进程
  • 重要:这是多进程,不是多线程。 每个 Worker 拥有独立的 V8 实例和内存空间,全局变量和内存 Map 互不相通。Session 默认使用 MemoryStore 在单进程内有效,多进程部署必须改用 Redis 等共享存储。性能方面,单进程约 3-5 万 QPS,4 核集群理论可达 12-20 万
// aggrekite.config.json
{ "server": { "cluster": true } }

mergeLogs(日志合并)

server.mergeLogs 默认 false。开启后,Worker 通过 process.send() 将日志发送给 Primary,Primary 按时间戳排序后统一输出。

[Worker-12345] GET /api/users 200 3ms
[Worker-12346] POST /api/orders 201 5ms
  • 排序依据:res.on('finish') 触发时刻的时间戳
  • cluster: falsemergeLogs 自动忽略
  • PM2 环境下由 PM2 管理日志流,建议关闭
{ "server": { "cluster": true, "mergeLogs": true } }

4.12 安全加固

Helmet 安全头

推荐通过 app.wrap() 集成 helmet 中间件,为所有响应自动注入安全 HTTP 头:

npm install helmet
import helmet from 'helmet'

app.wrap(helmet({
  contentSecurityPolicy: false, // 按需开启
}))

Helmet 会自动添加以下安全头:

  • Content-Security-Policy(需手动配置)
  • Cross-Origin-Opener-Policy
  • Cross-Origin-Resource-Policy
  • Origin-Agent-Cluster
  • Referrer-Policy
  • Strict-Transport-Security(HTTPS 环境)
  • X-Content-Type-Options
  • X-DNS-Prefetch-Control
  • X-Download-Options
  • X-Frame-Options
  • X-Permitted-Cross-Domain-Policies
  • X-XSS-Protection

框架自身已注入 X-Content-Type-Options: nosniffX-Frame-Options: DENYReferrer-Policy: strict-origin-when-cross-origin。Helmet 会补全其余头并覆盖框架默认值。

JWT Secret 管理

JWT Secret 严禁硬编码。推荐从环境变量读取:

const secret = process.env.JWT_SECRET || (() => {
  throw new Error('JWT_SECRET 环境变量未设置')
})()

使用 .env 文件存储,并确保 .env 已加入 .gitignore

CORS 白名单

生产环境应限制允许的源,而非使用 *

import cors from 'cors'

const allowedOrigins = ['https://example.com', 'https://admin.example.com']

app.wrap(cors({
  origin: (origin, callback) => {
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true)
    } else {
      callback(new Error('CORS not allowed'))
    }
  },
  credentials: true,
}))

5. 配置项

AggreKite 支持三层配置,优先级:aggrekite.config.json < new AggreKite({...}) < app.listen({ debug: true })

AggreKiteConfig 完整表格

| 配置项 | 类型 | 默认值 | 说明 | |---|---|---|---| | server.host | string | "0.0.0.0" | 监听地址 | | server.port | number | 3000 | 监听端口 | | server.keepAlive | number | 5000 | Keep-Alive 超时(毫秒) | | server.timeout | number | 30000 | 请求超时(毫秒),超时返回 408 | | server.trustProxy | boolean | false | 信任反向代理,从 X-Forwarded-For 取真实 IP;同时 session cookie 标记 Secure | | server.bodyLimit | string \| number | "10mb" | 请求体大小上限,支持 "1kb" / "5mb" / 1048576 等 | | server.compress | boolean \| { threshold } | { threshold: 4096 } | 响应压缩。false 关闭;true 默认阈值 4096B;自动选择 brotli > gzip > deflate | | session.secret | string | "change-me" | Session 签名密钥 | | session.maxAge | number | 86400 | Session 过期时间(秒) | | session.store | SessionStore | MemoryStore | 自定义 Session 存储(如 Redis) | | storage | StorageDriver | LocalStorageDriver("./uploads") | 文件存储驱动 | | static | { name, path }[] | [{ name: "public", path: "./public" }] | 静态资源目录 | | auth | string \| false | "Bearer" | Token 前缀。false 禁用自动解析 | | trace | boolean | false | 链路追踪(生成 traceId)。与 app.log() 功能重叠,按需开启 | | debug | boolean | false | 调试模式(暴露 /__routes、详细错误信息) | | cors | — | — | CORS 通过 app.wrap(cors({...})) 手动配置 | | ssr.render | (component, props?) => string | — | SSR 渲染器注入 |

aggrekite.config.json 示例

{
  "server": {
    "host": "0.0.0.0",
    "port": 3000,
    "keepAlive": 5000,
    "timeout": 30000,
    "bodyLimit": "10mb",
    "trustProxy": false,
    "compress": { "threshold": 4096 },
    "cluster": false,
    "mergeLogs": false
  },
  "debug": false,
  "auth": "Bearer",
  "trace": false,
  "session": {
    "secret": "change-me",
    "maxAge": 86400
  },
  "static": [
    { "name": "public", "path": "./public" }
  ]
}

6. 注意事项

  1. 必须 tsx 运行:框架使用 ESM only("type": "module"),不支持 ts-node。推荐 npx tsx app.ts
  2. Node.js >= 18:BFF 聚合依赖原生 fetch,低版本无法运行。
  3. Blueprint name 自动生成前缀new Blueprint({ name: 'user' }) → 前缀 /users。如需自定义前缀,显式传入 prefix
  4. filepath handler 必须通过柯里化函数注册:从 Blueprint 方法返回的 RouteRegistrar 应在 handler 文件顶层同步调用。若 handler 文件被 import 但未调用柯里化函数,框架会输出 [AggreKite] 警告。
  5. handler 文件路径相对于 Blueprint 文件所在目录:框架通过 getCallerUrl() 自动解析调用方文件路径,$() 中的相对路径相对于蓝图文件所在目录进行解析,无需手动计算。
  6. Session 生产环境建议 Redis 存储:默认 MemoryStore 仅在单进程有效,多进程部署需替换为共享存储。
  7. trustProxy 在反向代理后务必开启:否则 req.ip 拿到的是代理 IP 而非真实客户端 IP。
  8. 静态文件路径防护自带,但不能替代 WAF:框架已内置 ../ 路径穿越校验,但复杂攻击仍需网关层防护。
  9. BFF fallback 仅全部失败时触发:部分调用成功时不会降级,失败的那路在 map 中为 null
  10. debug 模式仅供开发使用:生产环境必须关闭,否则 /__routes 会暴露路由结构。
  11. 安全响应头自动注入:所有响应自动添加 X-Content-Type-Options: nosniffX-Frame-Options: DENYReferrer-Policy: strict-origin-when-cross-origin
  12. 响应压缩自动生效:Body >= 4096 字节时自动选择 brotli / gzip / deflate,可通过 compress 配置关闭或调整阈值。

7. 完整项目示例

app.ts

import { AggreKite } from 'aggrekite'
import cors from 'cors'
import { users } from './router/users'
import { bffBP } from './router/bff'

const app = new AggreKite({
  server: { trustProxy: true, bodyLimit: '50mb' },
  session: { secret: 'prod-secret', maxAge: 86400 },
  static: [{ name: 'public', path: './public' }],
})

app.wrap(cors({ origin: '*' }))
app.mount(users)
app.mount(bffBP)
app.listen(3000)

router/users.ts — Blueprint + CRUD + 校验 + 中间件

import { Blueprint, HttpException } from 'aggrekite'

export const users = new Blueprint({ name: 'user' })

// 全局鉴权
users.before((req) => {
  if (!req.global.token) throw new HttpException(401, '未登录')
})

// 错误处理
users.error(401, () => ({ code: 401, data: {}, message: '未登录' }))
users.error(404, () => ({ code: 404, data: {}, message: '用户不存在' }))

// GET /users?page=1&size=10
users.get('/', (req) => {
  const { page = '1', size = '10' } = req.query
  return { data: { list: [], total: 0, page: +page, size: +size } }
})

// GET /users/:id
users.get('/:id', (req) => {
  return { data: { id: req.params.id, name: '张三', email: '[email protected]' } }
})

// POST /users
users.post('/', (req) => {
  return { __code__: 201, data: { id: 1, ...req.body } }
})

// PUT /users/:id
users.put('/:id', (req) => {
  return { data: { id: req.params.id, ...req.body } }
})

// DELETE /users/:id
users.delete('/:id', (req) => {
  return { data: { deleted: req.params.id } }
})

// IP 黑名单
users.blacklist('/admin')

router/bff.ts — BFF 聚合

import { Blueprint, bff } from 'aggrekite'

export const bffBP = new Blueprint({ name: 'bff', prefix: '/bff' })

// GET /bff/user-profile — 并发获取用户 + 订单
bffBP.get('/user-profile', bff({
  calls: {
    user: 'http://localhost:3000/users/1',
    orders: 'http://localhost:3000/orders',
  },
  map(user, orders) {
    return {
      code: 0,
      data: {
        user: (user as any).data,
        orders: (orders as any).data,
      },
    }
  },
  fallback: { code: 0, data: { user: null, orders: [] } },
  timeout: 5000,
  cache: { ttl: 30000 },
}))

// GET /bff/product-detail/:id — 带动态参数聚合
bffBP.get('/product-detail/:id', bff({
  calls: {
    product: 'http://localhost:3000/products/{id}',
    reviews: 'http://localhost:3000/products/{id}/reviews',
  },
  map(product, reviews) {
    return {
      code: 0,
      data: {
        product: (product as any).data,
        reviews: (reviews as any).data,
      },
    }
  },
  fallback: { code: 0, data: { product: null, reviews: [] } },
  retry: { times: 2, delay: 500 },
  timeout: 5000,
  trace: true,
}))

router/upload.ts — 文件上传

import { Blueprint } from 'aggrekite'

export const upload = new Blueprint({ name: 'upload', prefix: '/upload' })

upload.post('/', async (req) => {
  const file = req.file('avatar')
  if (!file) return { code: 400, message: 'No file uploaded' }

  const url = await file.save()
  return {
    data: {
      url,
      fieldname: file.fieldname,
      originalname: file.originalname,
      mimetype: file.mimetype,
      size: file.size,
    },
    message: 'File uploaded',
  }
})

upload.post('/multi', (req) => {
  const files = req.files('attachments')
  return {
    data: files.map((f) => ({
      originalname: f.originalname,
      size: f.size,
    })),
    message: `${files.length} files uploaded`,
  }
})