Middleware in EdgeSpec follows a similar pattern to other libraries like Express/Fastify/Koa. Middleware receives three parameters: the incoming req
object, the ctx
context object, and the next
function. It is expected to always call next
.
Here's a few example use cases:
Mutate the passed req
object. For example:
import type { Middleware } from "edgespec"
export const exampleMiddleware: Middleware = (req, ctx, next) => {
req.foo = "bar"
return next(req, ctx)
}
// Later, in a route...
withEdgeSpec({
// ...
middleware: [exampleMiddleware],
})(async (req) => {
console.log(req.foo) // "bar"
})
Wait for a Response to be returned from the next
callback, then modify it directly. For example:
import type { Middleware } from "edgespec"
export const exampleMiddleware: Middleware = (req, ctx, next) => {
const response = await next(req, ctx)
response.headers.set("X-Example", "Hello, world!")
return response
}
The Middleware
type accepts two type parameters: the first is the additional context required on the incoming Request
object, and the second is the output context that the middleware will return. Both are optional, but specifying the input context is very helpful when you have middlewares that depend on each other. For example:
import type { Middleware } from "edgespec"
export const databaseMiddleware: Middleware<
{},
{
db: DatabaseClient
}
> = async (req, ctx, next) => {
const db = await connectToDatabase()
req.db = db
return next(req, ctx)
}
export const bearerAuthMiddleware: Middleware<
// Required request options (Middleware "input"). Assumes that the database middleware has already been called, maybe as part of `beforeAuthMiddleware[]` in `createWithEdgeSpec`.
{
db: DatabaseClient
},
// Result request options (Middleware "output")
{
is_authenticated: boolean
}
> = (req, ctx, next) => {
const authToken = req.headers.get("authorization")?.split("Bearer ")?.[1]
if (!authToken) {
// EdgeSpec will attach returned properties to the Request object
return {
is_authenticated: false,
}
}
const [user] = await req.db.query("SELECT * FROM users WHERE token=?", [
authToken,
])
req.is_authenticated = Boolean(user)
return next(req, ctx)
}
Authentication middleware is just like any other middleware, except by passing it to authMiddleware
in createWithEdgeSpec
, you can specify it as a valid auth
option on a route. Using the auth
option is usually simpler and will also affect code generation.
For example:
// src/use-edge-spec.ts
import { createWithEdgeSpec } from "edgespec"
import { withApiKey, withBrowserSession } from "src/middlewares"
export const withEdgeSpec = createWithEdgeSpec({
apiName: "hello-world",
authMiddleware: {
apiKey: withApiKey,
browserSession: withBrowserSession,
},
beforeAuthMiddleware: [],
productionServerUrl: "https://example.com",
})
// routes/resource/list.ts
import { withEdgeSpec } from "src/with-edge-spec"
export default withEdgeSpec({
auth: "apiKey",
// you can also specify an array of methods, e.g. ["apiKey", "browserSession"]
// auth: ["apiKey", "browserSession"],
})(async (req) => {
// Recommendation: Have your auth middleware add req.auth to your request,
// the type will carry over!
const { userId } = req.auth
// ...
})