Basic Middleware Pattern in JavaScript
Tags: typescript, javascript, middleware, koa
Published: 2020-10-07 10:00:21


Ever wondered how the middlewares in popular web frameworks, e.g. [Express](https://expressjs.com/) or [Koa](https://koajs.com/), work?

In Express, we have the middleware functions with this signature:

```js
const middleare = (req, res, next) => {
  // do stuffs
  next()
}
```

In Koa, we have this:

```js
const middleware = (ctx, next) => {
  // do stuffs
  next()
}
```

Basically, you have some objects (`req`, `res` for Express or `ctx` for Koa) and a `next()` function as the arguments of the middleware function. When `next()` is called, the next middleware function is invoked. If you modify the argument objects in the current middleware function, the next middleware will received those modified objects. For example:


```js
// Middleware usage in Koa

app.use((ctx, next) => {
  ctx.name = 'Doe'
  next()
})

app.use((ctx, next) => {
  console.log(ctx.name) // will log `Doe`
})

app.use((ctx, next) => {
  // this will not get invoked
})
```

And if you don't call the `next()` function, the execution stops there and the next middleware function will not be invoked.

## Implementation

So, how do you implement a pattern like that? With 30 lines of JavaScript:

```js
function Pipeline(...middlewares) {
  const stack = middlewares

  const push = (...middlewares) => {
    stack.push(...middlewares)
  }

  const execute = async (context) => {
    let prevIndex = -1

    const runner = async (index) => {
      if (index === prevIndex) {
        throw new Error('next() called multiple times')
      }

      prevIndex = index

      const middleware = stack[index]

      if (middleware) {
        await middleware(context, () => {
          return runner(index + 1)
        })
      }
    }

    await runner(0)
  }

  return { push, execute }
}
```

This implementation of middleware pattern is almost the same as Koa. If you want to see how Koa does it, check out the  source code of [`koa-compose`](https://github.com/koajs/compose/blob/master/index.js#L31-L47) package.


## Usage

Let's see an example of using it:

```js
// create a middleware pipeline
const pipeline = Pipeline(
  // with an initial middleware
  (ctx, next) => {
    console.log(ctx)
    next()
  }
)

// add some more middlewares
pipeline.push(
  (ctx, next) => {
    ctx.value = ctx.value + 21
    next()
  },
  (ctx, next) => {
    ctx.value = ctx.value * 2
    next()
  }
)

// add the terminating middleware
pipeline.push((ctx, next) => {
  console.log(ctx)
  // not calling `next()`
})

// add another one for fun ¯\_(ツ)_/¯
pipeline.push((ctx, next) => {
  console.log('this will not be logged')
})

// execute the pipeline with initial value of `ctx`
pipeline.execute({ value: 0 })
```

If you run that piece of code, can you guess what the output will be? Yeah, you guessed it right:

```
{ value: 0 }
{ value: 42 }
```

By the way, this would absolutely work with async middleware functions too.

## TypeScript

Now, how about giving it some TypeScript love?

```ts
type Next = () => Promise<void> | void

type Middleware<T> = (context: T, next: Next) => Promise<void> | void

type Pipeline<T> = {
  push: (...middlewares: Middleware<T>[]) => void
  execute: (context: T) => Promise<void>
}

function Pipeline<T>(...middlewares: Middleware<T>[]): Pipeline<T> {
  const stack: Middleware<T>[] = middlewares

  const push: Pipeline<T>['push'] = (...middlewares) => {
    stack.push(...middlewares)
  }

  const execute: Pipeline<T>['execute'] = async (context) => {
    let prevIndex = -1

    const runner = async (index: number): Promise<void> => {
      if (index === prevIndex) {
        throw new Error('next() called multiple times')
      }

      prevIndex = index

      const middleware = stack[index]

      if (middleware) {
        await middleware(context, () => {
          return runner(index + 1)
        })
      }
    }

    await runner(0)
  }

  return { push, execute }
}
```

With everything being typed, now you can declare the type of the context object for a specific middleware pipeline, like this:

```ts
type Context = {
  value: number
}

const pipeline = Pipeline<Context>()
```

Okay, that's all for now.

-------

_Originally published at <a href="https://muniftanjim.dev/blog/basic-middleware-pattern-in-javascript/" rel="canonical">muniftanjim.dev</a> on October 4, 2020._