A simple middleware in spirit looks like this:

(handler) => {
  return (request) => {
    return handler(request)
  }
}

// or simply:
(handler) => (request) => handler(request)

You can think of handler as "next", the next function (middleware /or handler) to call.

It has access to the request that gets passed along. But it also has access to the return value when calling handler(request), therefore we can modify both the request (input) and response (output).

Transform request

We can tranform the request by changing what we pass into the next handler:

(handler) => (request) => {
  request.data = "some data to pass forward"
  return handler(request)
}

Transform response

Notice in our examples above we return the result of handler(request).

By unwinding and returning the response, middleware can also transform the response.

The return of handler(request) is always a Promise for async compatibility.

(handler) => (request) => {
  return handler(request).then((resp) => {
    if (request.headers["etag"] === resp.get("etag")) resp.status = 304
    return resp
  })
}

In this example we modify the response to have a 304 status code if certain conditions are met.

Recovering From Errors

In the same way we can recover from errors:

const {response} = require("spirit").node

(handler) => (request) => {
  return handler(request).catch((err) => {
    return response("Recovered from " + err)
  })
}

Which we can use to send back some custom response.

Async/Await Example

If you have ES7 compatibility (via babeljs) then the above examples combined will look like:

(handler) => async (request) => { 
  const resp = await handler(request)
  if (request.headers["etag"] === resp.get("etag")) resp.status = 304
  return resp
}

Initializing

Usually it's not needed, but you can have initialization code for your middleware:

(handler) => {
  // initialize middleware here

  return (request) => handler(request)
}

This code gets called once for every time your middleware is loaded. This is useful when you want to do some special setup for each instance of your middleware.

Testing

Notice middleware is just a closure function that takes a input (request) and returns an (output).

They do not deal with with req or res either, but instead a request map or response map (which are small JSON-like objects).

This makes testing very easy.

For example if we wanted to test a middleware that sets every HEAD request to be GET:

const middleware = (handler) => (request) => {
  if (request.method === "HEAD") request.method = "GET"
  return handler(request)
}

// test
it("sets GET for every HEAD", () => {
  const test = (assertion) => (request) => expect(request.method).toBe(assertion)
  middleware(test("GET"))({ method: "HEAD" })
  middleware(test("GET"))({ method: "GET" })   // doesn't change if already GET
  middleware(test("POST"))({ method: "POST" }) // doesn't change if POST
})

We don't need to mock a req object at all, we only needed to pass in an object with the same properties the middleware actually uses.

Composing

spirit's middleware signature may look familiar to some of you, as it's a common closure pattern.

It was purposely chosen because it's a natural way of composing functions together, following the motto of "it's just javascript".

There is nothing magical that happens when spirit runs or sets up our middleware together, it's basically composing (or wrapping) functions together.

For example if we had 3 middlewares a, b, c:

const final = (request) => { status: 200, headers: {}, body: "Hello World" }

// wrap the middlewares together with `final`
const app = a(b(c(final)))

// now we have a single function that runs through `a, b, c, final` returning the response along the way
app(web_request)

This is basically what spirit does. Except spirit also ensures a Promise is always returned.

You can compose manually this way, but spirit also provides a helper function to automatically do this for you in spirit.compose.

spirit-router.wrap also does this, but it has special handling for working with routes.

results matching ""

    No results matching ""