koa-lambda-middleware
v1.0.6
Published
Downloads
75
Maintainers
Readme
koa-lambda-middleware
一接口一函数,传一参,反一世界。
koa-lambda中间件提供lambda方式进行接口开发,保持简单,使用函数式方式,约定统一仅使用post请求
,去TMD的RESTful,降低心智负担才是生产效率。
虽然koa原生的中间件为同为函数式,但是其实函数的传参可以进一步优化,并抽象到前端接口调用的传参,并做统一。而且return
返回值也没有充分利用上,因此koa-lambda
中间件弥补这些不足带来了这些便利。
开发要高效,不仅需要代码和配置量少,代码逻辑清晰,易于维护,而且框架/库提供的规范需要符合直觉,约定要优于配置。
安装
npm i -s koa-lambda-middleware
初始化koaLambda中间件
koaLambda中间件依赖 body parsers中间件,这里以koa-body为例(其实只要解析为ctx.request.body就行)
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实例>) 返回中间件。
配置说明(默认值)为
{
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
module.exports = {
hello(){
return "hello world!"
}
};
对应访问地址为:http://localhost/hello/hello 路由地址:文件名/函数路径
不过没有特定配置(默认)情况下,需要httpPOST
才能正常请求和响应,这是约束或者说更是约定
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函数。但是注意不要在目录里面出现同路径文件名,请保持路由唯一
module.exports = {
a:{
b:{
c(){
return {path:'a.b.c'}
}
}
}
};
接口地址为:http://localhost/path/a/b/c
传参
传参上面的约定,前端以application/json(取决于bodyparsers中间件)传args数组为参数数组
POST http://localhost:3333/a/foo HTTP/1.1
content-type: application/json
{
"args":[
2,
3
]
}
创建一个a.js文件为例,返回传参之和,这里Lambda函数的参数a, b分别对应接口请求传入的args数组
module.exports = {
foo(a, b){ // 这里a, b分别为2, 3
return {sum: a + b}
}
};
响应返回(即为函数return值)如下:
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。
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和中间件,并且你可以利用好它们之间的转化。
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方法进行重新定义。
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属性进行设置
module.exports = {
foo(a, b){
return a + b
}
};
//修改foo函数为get方式请求
module.exports.foo.method = 'get';
注意:要使用get请求,还需要对自定义传参requestParams方法内针对get实现获取参数的逻辑。
前端封装示例简易版
这个例子约定使用fetch请求,当然也可以使用axios等请求库去封装实现请求。
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',
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
,它是对前面简易版调用封装。
api为生成的对象具备动态响应式路径。
约定:invoke对象调用链路就是接口访问地址路径
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
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);
特性:
- 约定统一默认使用POST请求
- 文件目录即对应接口路径
- 约定使用args数组作为函数参数,前后端调用一致性
- 保持koa洋葱模型,lambda既是函数也是中间件
- hook方式获取ctx 和 next,和koa中间件可以互替换
- 前端(client.js)搭配,使用动态路径对象访问接口(前端调用链、接口url路径、后端函数路由)三者统一,且无需额外配置