# easy-api **Repository Path**: zangzhihong/easy-api ## Basic Information - **Project Name**: easy-api - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2025-08-25 - **Last Updated**: 2026-07-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Easy API 一个轻量简洁(≈10k)的 API 开发框架,更简洁优雅实现易于维护的业务功能,并且通过适配器可同时运行到 serverless , node , bun 等js运行环境。 ## 特性 - **ts支持**:完整ts类型支持,通过宏定义`defineService`包裹业务方法,传入的参数自动校验且支持ts语法提示 - **目录结构即路由**:根据目录划分模块,自动生成路由,并根据业务schema描述自动生成接口文档,减少大量样板代码,工程干净整洁。 - **接口描述**:支持对接口进行详细描述,通过schema定义接口参数和返回结果,实现参数验证并支持ts语法提示,并生成最权威的接口文档。 - **灵活配置**:可以定义请求方法、拦截器和错误处理,也可以通过中间件实现更多扩展功能。 - **参数验证**:支持对查询参数、路径参数、请求体进行定义。 ## 基础工程模板地址 node模板:https://gitee.com/zangzhihong/easy-api-tpl ## 安装 在`package.json` 文件中添加依赖: ```json "devDependencies": { "easy": "git+https://gitee.com/zangzhihong/easy-api.git#master" } ``` 执行`npm install`命令安装依赖。 ## 快速开始 创建一个`main.js`入口文件,并添加以下代码: Node.js环境最小配置代码如下: ![](./screen-shot/1.png) ```typescript import { cwd } from 'node:process' import Easy from 'easy' import { nodeServerWrap } from 'easy/adapter' const config = { debug: true, // 输出调试信息 baseDir: cwd(), // 应用根目录 controllerDir: 'service', } const app = new Easy(config) nodeServerWrap(app.serve, 3000) ``` ### 业务文件创建 创建一个`service`目录,名称可以通过`controllerDir`自定义,默认为`service`。并添加一个`user`目录,再在`user`下添加`login`文件夹,业务文件命名为`yi.js`,即会生成`/user/login`接口。handler方法即是业务逻辑处理方法。第一个参数包含请求上下文,也可以用this替代访问 `yi.js`文件内容如下: ![](./screen-shot/2.png) ```typescript import { defineService } from 'easy' export default defineService({ handler(ctx) { // this === ctx // 或者 this.send(ctx.query) ctx.send(ctx.query) }, }) ``` 运行`node main.js`,访问`http://localhost:3000/user/login?name=zhangsan&age=18`,返回结果如下: ```json { "name": "zzh", "sex": "ll" } ``` 文档接口地址为`http://localhost:3000/__doc__.html` ## 更多配置 ```typescript import { defineService } from 'easy' export default defineService({ disabled: true, // 是否禁用该接口 method: 'GET|POST', // 不传默认为GET schema: { query: { name: { type: 'string', maxLength: 10, desc: '用户名' }, sex: { type: 'string', desc: '性别' }, age: { type: 'number', min: 1, max: 90 } }, body: { name: { type: 'string', maxLength: 10, desc: '用户名', required: false }, sex: { type: 'string', desc: '性别' }, }, }, handler(ctx) { ctx.send({ query: ctx.query, body: ctx.body }) }, }) ``` 可将接口约束提取为单独`schema.ts`文件以便保持yi.js简洁,通过`defineSchema`宏实现schema ts提示,声明后ctx.query、ctx.body会自动校验,使用的时候确保类型安全。 : ```typescript import { defineSchema } from 'easy' export default defineSchema({ desc: '测试接口', query: { name: { type: 'string', required: false }, sex: { type: 'string' }, age: { type: 'number', default: 18, min: 1, max: 90, }, } }) ``` 在`yi.ts`文件中使用: ```typescript import { defineService } from 'easy' import schema from './schema' export default defineService({ disabled: true, // 是否禁用该接口 method: 'GET|POST', // 默认不传为GET schema, handler(ctx) { ctx.send({ query: ctx.query, body: ctx.body }) }, }) ``` handler方法参数ctx包含以下属性: ```typescript const test = { a:1 ,b: 2, c: 3 } // 选出对象中的某些项 ctx.pick(test, 'a') // 输出 { a: 1 } ctx.pick(test, ['a','b']) // 输出 { a: 1, b: 2 } // 删除对象中的某些项 ctx.omit(test, 'a') // 输出 { b: 2, c: 3 } ctx.omit(test, ['a','b']) // 输出 { c: 3 } ctx.timestamp // 获取当前时间戳 // 返回数据 ctx.send(data, statusCode) // 抛出错误 ctx.throw(statusCode, message) ctx.throw(400, '参数错误') ctx.app.config // 获取应用配置 ctx.json(data) // 返回json数据 ctx.text(data) // 返回文本数据 ctx.html(data) // 返回html数据 ``` ## 拦截器 ```typescript const app = new Easy(config) // 前置钩子 app.on('request', (ctx, next) => { if (ctx.request.path === '/user/login') { // ctx.throw(401, 'Unauthorized') // 终止后续钩子 } next() }) // 响应后钩子(记录日志) app.on('response', (ctx, next) => { next() }) // 错误处理钩子 app.on('error', (err, ctx, next) => { console.error('全局错误:', err) // ctx.response.send({ error: 'Internal Server Error', err }) next() }) ``` ## 适配器 适配器用于将Easy应用适配到不同的环境,如Node.js、UniCloud、腾讯云等环境 uniCloud下云函数适配器使用如下,目前支持node.js和uniCloud云函数和云对象 ```typescript import { cwd } from 'node:process' import Easy from 'easy' import { uniCloudFuncWrap } from 'easy/adapter' const config = { debug: true, // 输出调试信息 baseDir: cwd(), // 应用根目录 controllerDir: 'service', } const app = new Easy(config) exports.main = async (event, context) => { //event为客户端上传的参数 console.log({ event, context }) const dbJQL = uniCloud.databaseForJQL({ // 获取JQL database引用,此处需要传入云对象的clientInfo event, context }) app.context.jql = dbJQL //返回数据给客户端 return uniCloudFuncWrap(app.serve, event, context) }; ``` uniCloud下云对象适配器使用如下 ```typescript import { cwd } from 'node:process' import Easy from 'easy' import { uniCloudObjWrap } from 'easy/adapter' const config = { debug: true, // 输出调试信息 baseDir: cwd(), // 应用根目录 controllerDir: 'service', } const app = new Easy(config) module.exports = { app: function(event) { const dbJQL = uniCloud.databaseForJQL({ // 获取JQL database引用,此处需要传入云对象的clientInfo clientInfo: this.getClientInfo() }) app.context.jql = dbJQL return uniCloudObjWrap(app.serve, event, this, '/app') } } ``` UniCloud环境云对象请求代码如下: ```typescript const APP = uniCloud.importObject('app') APP.app({ method: 'GET', path: '/user/login', headers: { Authorization: 'Bearer eyJ0eXAiOiJKsdfssssssss' }, query: {name:'xxxxxxxxx',password:'666'} }) ``` ### 添加必备内置中间件后入口文件参考代码如下 ```typescript import { cwd } from 'node:process' import { nodeServerWrap } from '../src/adapter' import Easy from '../src/index' import jwt from '../src/middleware' import docRegister from './doc' import busiRes from './utils/busi-res' declare module '../src/index' { interface CustomContext { /** * 获取当前时间戳 */ addTime: () => number /** * 业务成功 */ busiSuc: (data: any) => void // 其他自定义属性 } interface CustomState { userInfo: { name: string id: number role: string token: string } } } const config = { debug: false, // 输出调试信息 baseDir: cwd(), // 应用根目录 controllerDir: 'examples/service', } const app = new Easy(config) Object.keys(busiRes).forEach((key) => { app.context[key] = busiRes[key] }) app.use(jwt.jwt({ secret: 'secret', unless: [/user/, /^\/system/] })) docRegister(app) // 前置钩子 app.on('request', (ctx, next) => { if (ctx.request.path === '/user/login') { // ctx.throw(401, 'Unauthorized') // 终止后续钩子 } next() }) // 响应后钩子(记录日志) app.on('response', (ctx, next) => { next() }) // 错误处理钩子 app.on('error', (err, ctx, next) => { console.error('全局错误:', err) // ctx.response.send({ error: 'Internal Server Error', err }) next() }) nodeServerWrap(app.serve, 3000) ``` ## 实现跨平台缓存 实际缓存可自己选择,只需要实现set,get,remove方法即可,在入口文件main.ts中添加如下代码 ### LRUCache 缓存实现示例 ```typescript import Easy, { LRUCache } from 'easy' ....省略其它 const mapCache = new LRUCache (1000) const cache = useCache({ set: (key, data, expire) => { const iat = Date.now() let exp if (expire > 0) { exp = iat + expire } mapCache.set(key, { data, exp, iat }) return data }, get: (key) => { const cache = mapCache.get(key) if (!cache) return null if (cache.exp && Date.now() > cache.exp) { mapCache.remove(key) return null } return cache.data }, del: (key) => { mapCache.remove(key) }, }) app.context.cache = cache ``` ### redis 缓存实现示例 根据使用的redis包做相应修改 ```typescript import redis from 'redis' const cache = useCache({ set: async (key, data, expire) => { await redis.set(key, data, expire) return data }, get: (key) => { return redis.get(key) }, del: (key) => { return redis.remove(key) }, }) app.context.cache = cache ``` 在业务代码中使用this.cache包裹业务函数即可缓存结果 ```typescript export default defineService({ async handler(ctx) { // 首次会执行其中函数返回的结果,第二次会直接从缓存中获取结果 const cacheData = await this.cache('userInfo', async (cacheKey) => { console.log(cacheKey) await delayTime(5000) const token = `Bearer ${jwt.sign({ id: 123, nick: 'name', dsf: '谁大风刮过、、‘+-098()*&……%¥#<>?/";:{}[]\\\\@!~,。、' }, 'secret', { expiresIn: '1d' })}` return { 'username': 'admin', 'phone': 1111111111, 'timestamp': this.timestamp, 'this===ctx': this === ctx, 'path': this.request.path, 'role': 'admin', 'avatar': 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif', 'introduction': 'I am a super administrator', token, } }) this.send(cacheData) }, }) ```