# koa-lambda **Repository Path**: SporeTeam/koa-lambda ## Basic Information - **Project Name**: koa-lambda - **Description**: A simple functional middleware that can use hooks for koa. - **Primary Language**: JavaScript - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2022-02-23 - **Last Updated**: 2024-12-27 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # koa-lambda-middleware > **一接口一函数,传一参,反一世界。** > koa-lambda中间件提供lambda方式进行接口开发,保持简单,使用函数式方式,约定统一仅`使用post请求`,去TMD的RESTful,降低心智负担才是生产效率。 虽然koa原生的中间件为同为函数式,但是其实函数的传参可以进一步优化,并抽象到前端接口调用的传参,并做统一。而且`return`返回值也没有充分利用上,因此`koa-lambda`中间件弥补这些不足带来了这些便利。 开发要高效,不仅需要代码和配置量少,代码逻辑清晰,易于维护,而且框架/库提供的规范需要符合直觉,约定要优于配置。 ## 安装 ```shell npm i -s koa-lambda-middleware ``` ## 初始化koaLambda中间件 koaLambda中间件依赖 body parsers中间件,这里以koa-body为例(其实只要解析为ctx.request.body就行) ```javascript const Koa = require('koa'); const { koaBody } = require('koa-body'); const koaLambda = require('koa-lambda-middleware'); const app = new Koa(); app.use(koaBody()) .use(koaLambda({}, app)); // 初始化koaLambda中间件 app.listen(3333); ``` koaLambda为初始化方法,传参为(<配置>,<当前koa app实例>) 返回中间件。 配置说明(默认值)为 ```javascript { handlerAopDefault: "", //函数逻辑在next前还是在next后,为空无next 值有:'after' | 'before' | '' root: "", //http请求访问路径头,如果配置baz,接口访问都统一 http://localhost/baz/**下 dirname: __dirname + "/src", //源码目录,递归获取所有src下的模块js文件 filter: /(.*)\.js$/, //过滤器 可以是 正则或函数 } // koaLambda({}, app) 相当于默认配置: koaLambda({ handlerAopDefault: "", root: "", dirname: __dirname + "/src", filter: /(.*)\.js$/, }, app) ``` ## 路由 在src目录下创建一个js模块文件hello.js。定义一个hello方法,这里后面我们统一称为**Lambda函数**,函数的路径为`/hello/hello` ```javascript module.exports = { hello(){ return "hello world!" } }; ``` 对应访问地址为:http://localhost/hello/hello 路由地址:`文件名/函数路径` 不过没有特定配置(默认)情况下,需要http`POST`才能正常请求和响应,这是约束或者说更是约定 ```http HTTP/1.1 200 OK Content-Type: text/plain; charset=utf-8 Content-Length: 11 Date: Mon, 28 Feb 2022 03:20:46 GMT Connection: close hello world ``` 允许出现这种**嵌套**方式定义Lambda函数。但是注意不要在目录里面出现同路径文件名,请**保持路由唯一** ```javascript module.exports = { a:{ b:{ c(){ return {path:'a.b.c'} } } } }; ``` 接口地址为:http://localhost/path/a/b/c ## 传参 传参上面的约定,前端以application/json(取决于bodyparsers中间件)传args数组为参数数组 ```http POST http://localhost:3333/a/foo HTTP/1.1 content-type: application/json { "args":[ 2, 3 ] } ``` 创建一个a.js文件为例,返回传参之和,这里Lambda函数的参数a, b分别对应接口请求传入的args数组 ```javascript module.exports = { foo(a, b){ // 这里a, b分别为2, 3 return {sum: a + b} } }; ``` 响应返回(即为函数return值)如下: ```http HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 Content-Length: 9 Date: Mon, 28 Feb 2022 06:41:51 GMT Connection: close { "sum": 5 } ``` ## Hook 在Lambda函数中使用了类似React hook的方式,通过使用 `useContext` 获取ctx, 使用 `useNext` 获取next。可以说是一次曲线救国,解决纯函数没有 ctx 和 next。 ```javascript const { useContext, useNext } = require('koa-lambda-middleware'); module.exports = { async foo(){ let ctx = useContext(); let next = useNext(); ctx.body = 'ok!'; await next(); } }; ``` 这里需要注意的是如果next后置同时需要返回,方法的return就不应该使用了,应该直接去修改ctx.body。 > Lambda函数并没有减少koa中间件功能。他们之间完全可以替代和相互转换。 另外,基于这种Hook方式,在项目内可以自行封装一些常用Hook,诸如:`useDB`、`useOrm`、`useModel`、`useSession`、`useOss`、`useRedis`、`useCookie`、`useValidator`等。 ## 高阶和转化 拥抱koa的丰富的中间件生态,并保持简单通用,不必自定义另外的规范,你的项目中应该仅需要Lambda和中间件,并且你可以利用好它们之间的转化。 ```javascript const { middleware, lambda } = require('koa-lambda-middleware'); // 注:下面例子有些乱,总之属性上挂的是lambda函数 module.exports = { // 单个中间件 用中间件来当作lambda foo: middleware(async(ctx, next)=>{ ctx.body = "hello" await next() }), // lambda 转 middleware 再转 lambda 套娃😂 foo2: middleware(lambda(async(a, b)=>{ return a + b })), // 多个中间件, 数组即可,会合并为一个中间件,内部运作也是一个洋葱模型 bar: middleware([ async (ctx, next)=>{ //todo something await next() }, async (ctx, next)=>{ ctx.body = "hello" await next() }, async (ctx, next)=>{ //todo something await next() }, ]), // 可以套娃 lambda转换为中间件 baz: middleware([ async (ctx, next)=>{ //todo something await next() }, lambda(async (a, b)=>{ return a + b }), async (ctx, next)=>{ //todo something await next() }, //嵌套 middleware([ async (ctx, next)=>{ await next() } //... ]) ]) }; ``` > 注意:middleware方法的数组内中间件同样遵循 `洋葱模型`。 ## 自定义传参规则 lambda的参数默认约定是`ctx.request.body.args`数组作为参数,如果要自定义可以对koaLambda.requestParams方法进行重新定义。 ```javascript const Koa = require('koa'); const koaBody = require('koa-body'); const koaLambda = require('koa-lambda-middleware'); const app = new Koa(); //自定义参数逻辑 koaLambda.requestParams = function(ctx, next){ //处理获取参数,将参数以数组方式返回 return [a, b, c...]; }; app.use(koaBody()) .use(koaLambda({}, app)); app.listen(3333); ``` ## 其它请求方式 虽然约定了统一使用POST请求,如果项目有这种特殊需求可以对lambda函数的method属性进行设置 ```javascript module.exports = { foo(a, b){ return a + b } }; //修改foo函数为get方式请求 module.exports.foo.method = 'get'; ``` > 注意:如果要使用get请求,前端请求需要将参数放在query中,而不是body中。?&args=1&args=2 这样上面的例子参数a、b 就分别为1、2 ## 前端封装示例简易版 这个例子约定使用fetch请求,当然也可以使用axios等请求库去封装实现请求。 ```javascript const host = `http://localhost:3000`; // https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch export const fn = async (url:string, ...params:any[]) => { let apiUrl = `${host}${url}`; try{ let response = await fetch(apiUrl, { mode: 'cors', method: 'POST', //这里只考虑了POST请求 credentials: "include", headers: { 'Content-Type': 'application/json' }, body:JSON.stringify({ //args是这里默认约定的字段!! //它决定了前端传参的方式, //如果自定义传参规则,需要修改requestParams方法 args:[...params] }) }); let data = await response.json(); console.log(`response from:${url}:`, data) return data; }catch(e){ console.error(`请求${apiUrl}发生错误`,e) throw e; } }; /** 调用方法: import {fn} from "./fn"; // 用户列表 export const userList = async function(params = {}){ return fn('/api/user/list', params); } */ ``` ## 前端 client.js 调用示例 参考文件`static/client.js`,它是对前面简易版调用封装。 client方法生成的对象具备动态响应式路径。 > 约定:invoke对象调用链路就是接口访问地址路径 ```javascript import client from './client.js' // 用client方法返回的invoke对象是一个动态路径的对象 // 动态路径对象说明: // 简单理解为:invoke对象下的属性有任意多的(动态)属性,你可以随意执行诸如: // invoke.a(); invoke.b()... invoke.z()... // 甚至 invoke.a 其实也是动态路径对象,所以可以继续链式调用属性, // 比如:invoke.a.b.c.d.e.f() 任意路径,但这个动态路径是有意义的 // 只需和url路径和后端的路由都保持一致,你就可以不关心接口的url问题 let invoke = client({ host: 'http://localhost:3333', root: '', //根路径 理论上应该与后端koaLambda配置的root一致 }); let foo2 = await invoke.test.foo2(1, 2); // 调用接口地址/test/foo2 路径与函数 invoke.test.foo2 路径对应 console.log("/test/foo2:", foo2); let gg = await invoke.test.gg(12, 2); // 调用接口地址/test/gg console.log("/test/gg:", gg); let bar = await invoke.a.b.c.bar(); // 调用接口地址/a/b/c/bar console.log("/a/b/c/bar:", bar); ``` ## 特性: > 1. 约定统一默认使用POST请求,约定content-type: application/json > 2. 文件目录即对应接口路径 > 3. 约定使用args数组作为函数参数,前后端调用一致性 > 4. 保持koa洋葱模型,lambda既是函数也是中间件 > 4. hook方式获取ctx 和 next,和koa中间件可以互替换 > 5. 前端(client.js)搭配,使用动态路径对象访问接口(前端调用链、接口url路径、后端函数路由)三者统一,且无需额外配置