Blitz is in beta! 🎉 1.0 expected in May or June
Back to Documentation Menu

HTTP Middleware

Topics

Jump to a Topic

HTTP Middleware is the escape hatch for queries and mutations to access HTTP primitives. Middleware can then pass data to queries and mutations using the middleware res.blitzCtx parameter.

Middleware should not be used for business logic, because HTTP middleware is less composable and harder to test than queries and mutations. All business logic should live in queries & mutations.

For example, authentication middleware can set and read cookies and pass the session object to queries and mutations via the context object. Then queries and mutations will use the session object to perform authorization and whatever else they need to do.

Global Middleware

Global middleware runs for every Blitz query and mutation. It is defined in blitz.config.js using the middleware key.

// blitz.config.js
module.exports = {
  middleware: [
    (req, res, next) => {
      res.blitzCtx.referer = req.headers.referer
      return next()
    },
  ],

Global Ctx Type

You can access the type of the res.blitzCtx parameter by importing Ctx from blitz. The default Ctx provided by Blitz is an empty object.

You can extend the Ctx type by placing the following file in your project (this is included in new apps by default).

// types.ts
import { DefaultCtx, SessionContext } from "blitz"

declare module "blitz" {
  export interface Ctx extends DefaultCtx {
    session: SessionContext
  }
}

Whatever types you add to Ctx add will be automatically available in your queries and mutations as shown here.

import { Ctx } from "blitz"

export default async function getThing(input, ctx: Ctx) {
  // Properly typed
  ctx.session
}

Local Middleware

Local middleware only runs for a single query or mutation. It is defined by exporting a middleware array from a query or mutation.

// app/products/queries/getProduct.tsx
import { Middleware } from "blitz"
import db, { FindOneProjectArgs } from "db"

type GetProjectInput = {
  where: FindOneProjectArgs["where"]
}

export const middleware: Middleware[] = [
  async (req, res, next) => {
    res.blitzCtx.referer = req.headers.referer
    await next()
    if (req.method !== "HEAD") {
      console.log("[Middleware] Loaded product:", res.blitzResult)
    }
  },
]

export default async function getProject(
  { where }: GetProjectInput,
  ctx: Record<any, unknown> = {}
) {
  console.log("Referer:", ctx.referer)
  return await db.project.findOne({ where })
}

Middleware API

This API is essentially the same as connect/Express middleware but with an asynchronous next() function like Koa. The main difference when writing connect vs Blitz middleware is you must return a promise from Blitz middleware by doing return next() or await next(). See below for the connectMiddleware() adapter for existing connect middleware.

import { Middleware } from "blitz"

const middleware: Middleware = async (req, res, next) => {
  res.blitzCtx.referer = req.headers.referer
  await next()
  console.log("Query/middleware result:", res.blitzResult)
}

Arguments

  • req: MiddlewareRequest
    • An instance of http.IncomingMessage, plus the following:
    • req.cookies - An object containing the cookies sent by the request. Defaults to {}
    • req.query - An object containing the query string. Defaults to {}
    • req.body - An object containing the body parsed by content-type, or null if no body was sent
  • res: MiddlewareResponse
    • An instance of http.ServerResponse, plus the following:
    • res.blitzCtx - An object that is passed as the second argument to queries and mutations. This is how middleware communicates with the rest of your app
    • res.blitzResult - The returned result from a query or mutation. To read from this, you must first await next()
    • res.status(code) - A function to set the status code. code must be a valid HTTP status code
    • res.json(json) - Sends a JSON response. json must be a valid JSON object
    • res.send(body) - Sends the HTTP response. body can be a string, an object or a Buffer
  • next(): MiddlewareNext
    • Required: Callback for continuing the middleware chain. You must call this inside your middleware
    • Required: Returns a promise so you must either await it or return it like return next(). The promise resolves once all subsequent middleware has completed (including the Blitz query or mutation).

Communicating Between Middleware and Query/Mutation Resolvers

From Middleware to Resolvers

Middleware can pass anything, including data, objects, and functions, to Blitz queries and mutations by adding to res.blitzCtx. At runtime, res.blitzCtx is automatically passed to the query/mutation handler as the second argument.

  1. You must add to res.blitzCtx before calling next()
  2. Keep in mind other middleware can also modify res.blitzCtx, so be sure to program defensively because anything can happen.

From Resolvers to Middleware

There are two ways middleware can receive data, objects, and functions from resolvers:

  1. res.blitzResult will contain the exact result returned from the query/mutation resolver. But you must first await next() before reading this. await next() will resolve once all subsequent middleware is run, including the Blitz internal middleware that runs the query/mutation resolver.
  2. You can pass a callback into the resolver via res.blitzCtx and then the resolver can pass data back to the middleware be calling ctx.someCallback(someData)

Error Handling

Short Circuit the Request

Normally this is the type of error you expect from middleware. Because middleware should not have business logic, so you won't be throwing authorization errors for example. Usually, an error in middleware means something is really wrong with the incoming request.

There are two ways to short circuit the request:

  1. throw an error from inside your middleware
  2. Pass an Error object to next like next(error)

Handle via Normal Query/Mutation Flow

Another uncommon way to handle a middleware error is to pass it to the query/mutation and then throw the error from there.

// In middleware
res.blitzCtx.error = new Error()

// In query/mutation
if (ctx.error) throw ctx.error

Connect/Express Compatibility

Blitz provides a connectMiddleware() function that converts connect middleware to Blitz middleware.

// blitz.config.js
const { connectMiddleware } = require("blitz")
const Cors = require("cors")

const cors = Cors({
  methods: ["GET", "POST", "HEAD", "OPTIONS"],
})

module.exports = {
  middleware: [connectMiddleware(cors)],
}

Arguments

  • middleware - Any connect/express middleware.

More Information

If you'd like more details on the RPC API, see the RPC API documentation


Idea for improving this page? Edit it on GitHub.