npm.io
1.2.1 • Published 7 months ago

koa-api-plus

Licence
ISC
Version
1.2.1
Deps
11
Size
394 kB
Vulns
0
Weekly
0
Stars
13

基于 Typescript + KOA 开发的 Restful APi 接口

测试网站

测试网站 api.xygeng.cn

特点


  • ts 开发
  • 支持装饰器和 控制器 创建接口
  • 具有日志记录功能

安装


快速开始

  • 安装依赖
pnpm install
  • 运行测试
pnpm test
  • 打包命令
pnpm build
  • 测试命令
pnpm test
  • 工具链
包管理器使用 pnpm,开发/构建统一由 Vite 管理,测试基于 Vitest
  • 生产环境
pnpm add koa-api-plus
  • 验证产物可消费
pnpm test:dist
  • 发布前检查
pnpm release:check
  • 贡献指南
CONTRIBUTING.md
  • 变更记录
CHANGELOG.md

控制器(重点)


存放控制器的目录,防止路由重复,文件名将默认为路由前缀。路由规则:/文件夹名/类装饰器(文件名,index文件为斜杠)/方法装饰器(方法名),支持不断嵌套

控制器文件命名规则:index.controller.ts

装饰器
  • Controller()装饰器,在类前面使用:@Controller(路由前缀),默认文件名就是前缀

    @Controller('user')

  • Get/Post,请求方法,在类方法前面使用:@函数(路由)

  • Put/Patch/Delete 同样支持,对应 HTTP 方法装饰器为 @Put() / @Patch() / @Delete()

    @Get('/login') 对应的路由就是 /user/login

  • CurrentContext() 属性装饰器,可直接在类里读取 Koa3 currentContext

    @CurrentContext() private readonly currentContext!: Context

  • 默认会为每个请求生成 requestId,并写入响应体和响应头 x-request-id

  • 可直接从包中导出 helper:getCurrentContext()、getRequestId(),第三方业务工具函数也能读取当前请求上下文

  • 文档装饰器:@Request()、@Response()、@Mcp()

  • @Controller({ path, doc }) 支持一次性定义路由前缀 + 控制器级文档信息(推荐)

  • 也支持 @Controller(path, { doc }) 写法

API 文档装饰器(Swagger 风格)
import { Controller, Get, Mcp, Param, Request, Response } from 'koa-api-plus';

@Controller({ path: '/user', doc: { tag: 'User', summary: 'User APIs' } })
export default class UserController {
    @Request({ in: 'query', name: 'id', type: 'string', required: true, description: 'User id' })
    @Response({ status: 200, description: 'Success' })
    @Mcp({ name: 'get_user_profile', description: 'Get user profile MCP tool' })
    @Get('/profile', { summary: 'Get user profile', description: 'Fetch profile by id', tags: ['User'] })
    public profile(@Param.Query('id') id: string) {
        return { id, name: 'demo' };
    }
}

控制器级文档统一由 @Controller({ path, doc }) 或 @Controller(path, { doc }) 提供。

一键生成 API Markdown 文档
pnpm doc:gen --baseUrl ./src/controllers --title "My API" --version 1.0.0 --output docs/api.md

生成物:

  • docs/api.md:接口文档(Markdown)
  • docs/api.mcp-tools.json:MCP 工具清单(AI 接入可直接消费)
  • docs/api.openapi.json:OpenAPI 3.0 JSON(Swagger 生态可直接使用)
MCP 接入(AI 调用)
import { createApiMcpInvoker, getApiMcpTools, getApiSpec } from 'koa-api-plus';

const spec = getApiSpec({ title: 'My API', version: '1.0.0' });
const tools = getApiMcpTools(spec);
const invoker = createApiMcpInvoker(tools, { baseUrl: 'http://127.0.0.1:3000' });

const result = await invoker.invoke('get_user_profile', { id: '1001' });
内置 MCP 路由
import Api, { createMcpRoutes, getApiMcpTools, getApiSpec } from 'koa-api-plus';

const api = new Api({ baseUrl: './src/controllers' });
api.callback();

const spec = getApiSpec({ title: 'My API', version: '1.0.0' });
const tools = getApiMcpTools(spec);

api.use(createMcpRoutes(tools, { enabled: true, basePath: '/mcp' }));

// GET  /mcp/tools
// POST /mcp/invoke

如果希望 MCP 网关执行真实接口:

api.use(createMcpRoutes(tools, { enabled: true, basePath: '/mcp' }));

createMcpRoutes 默认就是同端口执行(same-origin),无需额外字段。

一键启用 MCP(零样板代码)
import Api from 'koa-api-plus';

const api = new Api({
    baseUrl: './src/controllers',
    mcp: {
        enabled: true,
        basePath: '/mcp',
        docs: {
            title: 'My API',
            version: '1.0.0'
        }
    }
});

api.start();

开启后默认提供:

  • GET /mcp/tools:返回文档装饰器生成的工具清单
  • POST /mcp/invoke:按工具名在同端口执行目标 API
MCP 聚合(本机 + 远端统一路由)

可以在同一个 /mcp/* 下聚合远端 MCP:

const api = new Api({
    baseUrl: './src/controllers',
    mcp: {
        enabled: true,
        basePath: '/mcp',
        remotes: [
            { name: 'crm', baseUrl: 'https://crm.example.com/mcp' },
            { name: 'pay', baseUrl: 'https://pay.example.com/mcp', headers: { authorization: 'Bearer <token>' } }
        ],
        cacheMs: 30000,
        docs: { title: 'My API', version: '1.0.0' }
    }
});

行为:

  • GET /mcp/tools:返回本机工具 + 远端工具合并列表
  • 远端工具自动加前缀:<remoteName>.<toolName>(例如 crm.get_customer)
  • POST /mcp/invoke:本机工具走同端口调用;带前缀的工具自动转发到对应远端 MCP
  • GET /mcp/health:查看远端 MCP 健康状态(支持 ?refresh=true 强制刷新)

冲突处理:

  • 若工具名冲突,默认保留首次出现的工具,并在 /mcp/tools 的 meta.conflicts 返回冲突明细。
OpenCode 接入 koa-api MCP 教程

下面给一套最短闭环流程,方便在 OpenCode 中让 AI 直接调用你的 koa-api 接口能力。

  1. 启动 API 并开启 MCP:
import Api from 'koa-api-plus';

const api = new Api({
    baseUrl: './src/controllers',
    port: 3000,
    mcp: {
        enabled: true,
        basePath: '/mcp',
        docs: {
            title: 'My API',
            version: '1.0.0'
        }
    }
});

api.start();
  1. 先本地验证 MCP 路由:
curl http://127.0.0.1:3000/mcp/tools
curl -X POST http://127.0.0.1:3000/mcp/invoke \
  -H 'content-type: application/json' \
  -d '{"name":"get_user_profile","input":{"id":"1001"}}'
  1. 在 OpenCode 中新增 MCP 服务:
  • 服务地址填写:http://127.0.0.1:3000/mcp
  • OpenCode 会通过 /mcp/tools 自动发现可调用工具
  • AI 调用工具时,OpenCode 会请求 /mcp/invoke
  1. 在 OpenCode 对话里直接调用工具:
请调用 get_user_profile,参数 id=1001,并总结结果
  1. 生产环境建议:
  • 给 /mcp/* 增加鉴权(例如 token/header)
  • 在 @Mcp() 中补全 description 和 inputSchema,提高 AI 调用稳定性
  • 每次接口变更执行 pnpm doc:gen,保持文档、工具、接口一致

说明:不同 OpenCode 版本界面入口可能略有区别,但接入要点一致:指向同一 basePath(例如 /mcp)即可。

完整可运行 demo 见:docs/opencode-mcp-demo.md(包含启动、验证、OpenCode 接入全流程)。

自动参数推断
  • 如果没有写 @Request(),框架会根据 @Param.Query() / @Param.Body() 自动推断基础文档参数。
  • 显式 @Request() 优先,自动推断只补充缺失项。
使用方法
// index.js
import { Controller, Get, Post } from 'koa-api-plus';
@Controller()
export default class IndexController {
    @Get('/test')
    async hello() {
        return {
            title: 'haha'
        };
    }
}
// [Get] /test => {}

// user/index.controller.ts
import { Controller, Get, Post } from 'koa-api-plus';
@Controller()
export default class IndexController {
    @Post('/test')
    async hello() {
        return {
            title: 'haha'
        };
    }
}

// 【Post】 /user/test =>{}
测试用例

迁移后,原有示例已全部改为 Vitest 集成测试,推荐直接阅读 tests/:

  • tests/controllers/basic-routes.test.ts:基础 GET/POST/PUT/DELETE 行为
  • tests/controllers/error-handling.test.ts:同步/异步错误响应
  • tests/controllers/middleware-behavior.test.ts:路由中间件与控制器中间件
  • tests/controllers/static-assets.test.ts:静态资源与 API 共存
  • tests/controllers/image-response.test.ts:图片流响应
  • tests/controllers/upload.test.ts:文件上传
  • tests/controllers/sse.test.ts:SSE 输出
  • tests/helpers/request-context.test.ts:requestId / currentContext helper
基础路由用法
//
import Api, { ApiLogger, Context, Controller, CurrentContext, Delete, Get, Logger, Param, Post, Put } from '../../src';

type User = {
    id: number;
    name: {
        firstName: string;
        lastname: string;
    };
};

@Controller()
export class GetController {
    @CurrentContext()
    private readonly currentContext!: Context;

    @Get('/get')
    // 获取query参数
    public get(@Param.Query<User>('id') id: number) {
        if (id) {
            return {
                id,
                requestId: this.currentContext.state?.requestId || null
            };
        }
        return null;
    }

    @Put('/put')
    public put(@Param.Query<User>('id') id: number) {
        return { id, method: 'put' };
    }

    @Delete('/delete')
    public delete(@Param.Query<User>('id') id: number) {
        return { id, method: 'delete' };
    }
}

@Controller('/post')
export class PostController {
    // 定义日志
    @Logger()
    private logger!: ApiLogger;
    @Post('/')
    // 获取body参数
    public post(@Param.Body<User>('id') id: number, @Param.Body<User>('name.firstName') firstName: string) {
        this.logger.log('post测试:', { id, firstName });
        if (id && firstName) {
            return {
                id,
                firstName
            };
        }
        return null;
    }
}

// 创建实例
const api = new Api();

api.on('log', (...args) => {
    console.log('[log]', ...args);
});
api.on('start', () => {
    console.log('[start]');
});

api.on('error', (e) => {
    console.error('[error]', e);
});

// 启动
api.start();
在 utils 工具函数里获取 requestId
import { getRequestId } from 'koa-api-plus';

export function buildAuditLog(action: string) {
    return {
        action,
        requestId: getRequestId()
    };
}
import { Controller, Get } from 'koa-api-plus';
import { buildAuditLog } from './utils/audit';

@Controller('/audit')
export default class AuditController {
    @Get('/detail')
    public detail() {
        return buildAuditLog('detail');
    }
}
  • 这个 helper 在请求链路内调用时可返回当前请求的 requestId
  • 如果在启动脚本、定时任务、独立命令中调用,没有请求上下文时会返回 null
实例函数
// 事件监听
app.on();
// koa默认中间件
app.use();

// 默认启动服务
app.start();
打包流程

1、用 pnpm test 验证所有框架能力

2、生产构建通过 vite build 输出 dist/index.cjs、dist/index.mjs、dist/index.d.ts

3、控制器自动加载已改为基于动态 import(),更适配 Vite/ESM 环境

4、发布前可运行 pnpm release:check,依次验证测试、构建、dist 消费和打包结果

Keywords