# Middleware
Creating and using Mali middleware is accomplished in the same mechanisms
as Koa (opens new window) middleware, just taking into account the changes to
the application context. Mali middleware allows for flow both "downstream",
and then control flows back "upstream". All middleware functions have a signature
(ctx, next)
, where the middleware should call next
appropriately.
Simple example of a logging middleware:
function sayHello (ctx) {
ctx.res = { message: 'Hello '.concat(ctx.req.name) }
}
async function logger (ctx, next) {
const start = new Date()
await next()
const ms = new Date() - start
console.log('%s [%s] - %s ms', ctx.name, ctx.type, ms);
}
const app = new Mali(PROTO_PATH)
app.use(logger)
app.use({ sayHello })
const server1 = app.start('127.0.0.1:50051')
A call to the sayHello
remote function will result in the following log output:
SayHello [unary] - 1 ms
# Middleware Best Practices
This section covers middleware authoring best practices, such as middleware accepting options, named middleware for debugging, among others.
# Middleware options
When creating public middleware it's useful to conform to the convention of wrapping the middleware in a function that accepts options, allowing users to extend functionality. Even if your middleware accepts no options, this is still a good idea to keep things uniform.
Here our contrived logger
middleware accepts a format
string for customization,
and returns the middleware itself:
function logger (format) {
format = format || ':name [:type]'
return async function (ctx, next) {
const str = format
.replace(':name', ctx.name)
.replace(':type', ctx.type)
console.log(str)
await next()
}
}
app.use(logger())
app.use(logger(':name | :type'))
# Named middleware
Naming middleware is optional, however it's useful for debugging purposes to assign a name.
function loggerMiddleware (format) {
return async function logger (ctx, next) {
}
}
# Response Middleware
Middleware that decide to respond to a request and wish to bypass downstream middleware may
simply omit next()
. Typically this will be in routing middleware, but this can be performed by
any. For example the following will respond with "two", however all three are executed, giving the
downstream "three" middleware a chance to manipulate the response.
async function fn1 (ctx, next) {
console.log('>> one')
await next()
console.log('<< one')
}
async function fn2 (ctx, next) {
console.log('>> two')
ctx.res = { message: 'two' }
await next()
console.log('<< two')
}
async function fn3 (ctx, next) {
console.log('>> three')
await next()
console.log('<< three')
}
app.use('sayHello', fn1, fn2, fn3)
The following configuration omits next()
in the second middleware, and will still respond
with "two", however the third (and any other downstream middleware) will be ignored:
async function fn1 (ctx, next) {
console.log('>> one')
await next()
console.log('<< one')
}
async function fn2 (ctx, next) {
console.log('>> two')
ctx.res = { message: 'two' }
console.log('<< two')
}
async function fn3 (ctx, next) {
console.log('>> three')
await next()
console.log('<< three')
}
app.use('sayHello', fn1, fn2, fn3)
When the furthest downstream middleware executes next()
, it's really yielding to a noop
function, allowing the middleware to compose correctly anywhere in the stack.
# Async operations
Async function and promise forms Mali's foundation, allowing you to write non-blocking sequential code.
For example this middleware reads the filenames from ./docs
, and then reads the contents
of each file in parallel before assigning the response to the joint result.
const fs = require('fs-promise');
app.use(async function (ctx, next) {
const paths = await fs.readdir('docs')
const files = await Promise.all(paths.map(path => fs.readFile(`docs/${path}`, 'utf8')))
ctx.res = { content: files.join('') }
await next()
})
# Common Middleware
Name | Description |
---|---|
apikey (opens new window) | Api key authorization metadata middleware. |
bearer (opens new window) | Bearer token authorization metadata middleware. |
iff (opens new window) | Conditionally add Mali middleware. |
jwt (opens new window) | JWT authentication middleware. |
logger (opens new window) | Development logging middleware. |
metadata (opens new window) | Metadata middleware. |
metadata auth (opens new window) | Authorization metadata middleware. |
metadata field auth (opens new window) | Mali base middleware utility for metadata auth field checks. |
onerror (opens new window) | On error middleware. |
param (opens new window) | Request param middleware. |
request ID (opens new window) | Request ID metadata middleware. Sources request ID into context. |
toJSON (opens new window) | Automatically calls toJSON on response if the method is present in response object. |
toObject (opens new window) | Automatically calls toObject on response if the method is present in response object. |
transform (opens new window) | Transform response payload middleware. |
unless (opens new window) | Conditionally add Mali middleware. |