HTTP Middleware
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 middlewareres.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.
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.refererawait 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 bycontent-type
, ornull
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 how how middleware communicates with the rest of your appres.blitzResult
- The returned result from a query or mutation. To read from this, you must firstawait next()
res.status(code)
- A function to set the status code.code
must be a valid HTTP status coderes.json(json)
- Sends a JSON response.json
must be a valid JSON objectres.send(body)
- Sends the HTTP response.body
can be astring
, anobject
or aBuffer
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 likereturn next()
. The promise resolves once all subsequent middleware has completed (including the Blitz query or mutation).
Global Middleware
Global middleware runs for every Blitz query and mutation. It is defined in
blitz.config.js
using the middleware
key.// blitz.config.jsmodule.exports = {middleware: [(req, res, next) => {res.blitzCtx.referer = req.headers.refererreturn next()},],
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.tsximport {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.refererawait 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})}
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.- You must add to
res.blitzCtx
before callingnext()
- 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:
res.blitzResult
will contain the exact result returned from the query/mutation resolver. But you must firstawait 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.- You can pass a callback into the resolver via
res.blitzCtx
and then the resolver can pass data back to the middlewary be callingctx.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:
throw
an error from inside your middleware- Pass an Error object to
next
likenext(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 middlewareres.blitzCtx.error = new Error()// In query/mutationif (ctx.error) throw ctx.error
Connect/Express Compatibility
Blitz provides a
connectMiddleware()
function take converts connect middleware to Blitz middleware.// blitz.config.jsconst {connectMiddleware} = require("blitz")const cors = require("cors")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