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 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()
},
],
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 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 })
}
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)
}
req
: MiddlewareRequest
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 sentres
: MiddlewareResponse
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 appres.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 coderes.json(json)
- Sends a JSON response. json
must be a valid JSON
objectres.send(body)
- Sends the HTTP response. body
can be a string
,
an object
or a Buffer
next()
: MiddlewareNext
await
it or
return it like return next()
. The promise resolves once all
subsequent middleware has completed (including the Blitz query or
mutation).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.
res.blitzCtx
before calling next()
res.blitzCtx
, so be
sure to program defensively because anything can happen.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 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.res.blitzCtx
and then
the resolver can pass data back to the middleware be calling
ctx.someCallback(someData)
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 middlewarenext
like next(error)
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
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)],
}
middleware
- Any connect/express middleware.If you'd like more details on the RPC API, see the RPC API documentation