@kirrus/compose
is a module part of the kirrus namespace, used for composing middleware in a typesafe and lazy manner
import { Middleware, MiddlewareComposer } from "@kirrus/compose"
type Context<T = {}> = T & {
body: object
headers: Map<string, string>
method: string
url: string
status: number
}
const context: Context = {
body: {},
headers: new Map(),
method: "GET",
url: "/hello",
status: 200
}
const logger: Middleware<Context> = async (ctx, next) => {
await next()
const rt = ctx.headers.get("X-Response-Time")
console.log(`${ctx.method} ${ctx.url} - ${rt}`)
}
const xResponseTime: Middleware<Context> = async (ctx, next) => {
const start = Date.now()
await next()
const ms = Date.now() - start
ctx.headers.set("X-Response-Time", `${ms}ms`)
}
const helloHandler: Middleware<Context> = () => {
return {
status: 200,
body: {
status: 200,
message: "Hello from /hello !"
}
}
}
const composedMiddleware = new MiddlewareComposer<Context>()
.use(logger)
.use(xResponseTime)
.use(helloHandler)
.getMiddleware()
composedMiddleware(context, async () => {})
This package exposes a type(Middleware
) and a class(MiddlewareComposer
).
The type Middleware
is used for defining what a middleware and it looks as follows:
interface IMiddleware<T, R extends T> {
getMiddleware(): Middleware<T, R>
}
type FunctionMiddleware<T, R extends T> = (
ctx: T,
next: () => Promise<void>
) => Awaitable<DeepPartial<R> | void>
type Middleware<T, R extends T = T> =
| IMiddleware<T, R>
| FunctionMiddleware<T, R>
In most use cases, you'll find yourself writing middleware that fit FunctionMiddleware
type, but just in case you want to do something fancy like a custom router, the IMiddleware
type has been exposed too 🙂
For usage of MiddlewareComposer
, see Using this module
To install the compose
module, simply type:
npm i @kirrus/compose
import { Middleware, MiddlewareComposer } from "@kirrus/compose"
// This is the type of the context
// we'll be using for type safety
type Context = { count: number }
// This is the initial context
// adding a basic count property
// and giving it an initial value of 0
const context: Context = { count: 0 }
// This is the first middleware
// that increases the count property
// of the context
const increaseCount: Middleware<Context> = async (ctx, next) => {
ctx.count++
await next()
}
// This is the second middleware
// that doubles the count property
// of the context
const doubleCount: Middleware<Context> = async (ctx, next) => {
ctx.count *= 2
await next()
}
// And finally this is our middleware composer instance
// that we provide with a chain of middleware
// in the order we want the middleware to be executed
// using the .use(middleware) method
// and finally get the resulting composed middleware
// using the .getMiddleware() method
const composedMiddleware = new MiddlewareComposer<Context>()
.use(increaseCount)
.use(doubleCount)
.getMiddleware()
// The initial context
console.log(context) // { count: 0 }
// We apply the composed middleware
// on our context
// providing an empty next function
// Note: this assumes top-level await
await composedMiddleware(context, async () => {})
// The resulting context
console.log(context) // { count: 2 }
You can find the automatically generated changelog here
The documentation in markdown format can be found here
To contribute, check the contribution guide here
@kirrus/compose
and all other repos under the kirrus organisation use the Unlicense.