Middleware
Middleware files (+middleware.js) process requests before they reach your templates. Use middleware to:
- Check authentication
- Load shared data
- Set headers
- Validate requests
- Handle errors
Basic Example
/** @type {import('pocketpages').MiddlewareLoaderFunc} */
module.exports = function (api) {
const { request, redirect } = api
// Check auth
if (!request.header('Authorization')) {
redirect('/login')
return {}
}
// Load data
const categories = $app.findRecordsByFilter('categories', {
filter: 'active = true',
})
return { categories }
}
How Middleware Works
Middleware executes hierarchically from root to leaf:
pb_hooks/pages/
+middleware.js # Runs first (all routes)
products/
+middleware.js # Runs second (/products/*)
[id]/
+middleware.js # Runs third (/products/[id]/*)
For URL /products/123:
/+middleware.jsruns/products/+middleware.jsruns/products/[id]/+middleware.jsruns
Common Use Cases
Auth Guard
/** @type {import('pocketpages').MiddlewareLoaderFunc} */
module.exports = function (api) {
const { request, redirect } = api
const session = request.cookie('session')
if (!session) {
redirect('/login')
return {}
}
const user = findRecordByFilter('users', {
filter: `session = "${session}"`,
})
return { user }
}
Request Validation
/** @type {import('pocketpages').MiddlewareLoaderFunc} */
module.exports = function (api) {
const { request, response, params } = api
if (!params.id?.match(/^[0-9]+$/)) {
response.status(400)
return { error: 'Invalid ID' }
}
return {}
}
Loading Data
/** @type {import('pocketpages').MiddlewareLoaderFunc} */
module.exports = function (api) {
const { params, response } = api
const product = findRecordByFilter('products', {
filter: `id = "${params.id}"`,
})
if (!product) {
response.status(404)
return { error: 'Not found' }
}
return { product }
}
Controlling Middleware Execution
Middleware supports an optional second argument: next.
If the
nextargument is omitted:- Middleware behaves as if
next()is called automatically when the function exits. - Execution will always continue to the next middleware or default handler.
- Middleware behaves as if
If the
nextargument is included:- Any data passed to
nextis merged with and overwrites previousdatakeys. - Middleware will not proceed unless
next()is called. - The page route will only be rendered if
next()is called all the way through the chain. - If you return early (without calling
next()), execution stops, no further middleware or loaders (+load,+get, etc) will run, no pluginonRenderwill be called, and no response will be sent. You must manually send a response, e.g.response.json(400, { error: 'Forbidden' }).
- Any data passed to
Example with next
/** @type {import('pocketpages').MiddlewareLoaderFunc} */
module.exports = function (api, next) {
const { params, response } = api
if (!params.id?.match(/^[0-9]+$/)) {
return response.json(400, { error: 'Invalid ID' })
}
next({ validatedId: parseInt(params.id, 10) })
}
This allows fine-grained control over flow and shared data between middleware layers.
Using Middleware Data
Access middleware data in templates through data:
<% if (data.user) { %>
<h1>Welcome, <%= data.user.name %></h1>
<% if (data.product) { %>
<h2><%= data.product.name %></h2>
<p><%= data.product.description %></p>
<% } %>
<% } %>
Important Notes
- Middleware runs before page loaders (
+load.js) - All middleware along the route path executes
- Return an object to pass data to templates
- Return early to stop processing
- Keep processing efficient
HTTP Method-Specific Middleware
In addition to +middleware.js which runs for all HTTP methods, you can create method-specific middleware files that only run for specific HTTP methods:
// +get.js - Only runs for GET requests
/** @type {import('pocketpages').MiddlewareLoaderFunc} */
module.exports = function (api) {
// Handle GET requests
return {}
}
// +post.js - Only runs for POST requests
/** @type {import('pocketpages').MiddlewareLoaderFunc} */
module.exports = function (api) {
// Handle POST requests
return {}
}
Supported method-specific middleware files:
+get.js+post.js+put.js+patch.js+delete.js
These files follow the same hierarchical execution order as +middleware.js.