Middleware
Middleware functions run before your route handlers, enabling authentication, logging, CSRF protection, and other cross-cutting concerns.
Defining Middleware
Create a +server.mjs file in any directory. Exported functions become middleware:
export function auth(conn) {
const token = conn.headers.get('authorization');
if (!token) {
return conn.redirect('/login');
}
}
export function log(conn) {
console.log(`${conn.method} ${conn.request_path}`);
}
export default {
use: ['auth', 'log'],
};Using Middleware
Pages declare their middleware dependencies through the use array:
<script>
export default {
use: ['auth', 'csrf'],
POST: true,
};
</script>
<form method="POST">
<button type="submit">Protected Action</button>
</form>Middleware Resolution
Middleware files are resolved hierarchically:
- Root
+server.mjsapplies to all routes - Nested
+server.mjsfiles add to the chain - Middleware runs in order: root → parent → current
pages/
├── +server.mjs # Applies to all routes
├── index+page.html # Uses root middleware
└── admin/
├── +server.mjs # Adds admin middleware
└── dashboard+page.html # Uses both middleware chainsBuilt-in Middleware
CSRF Protection
export function csrf(conn) {
conn.req.csrfProtect();
}Enable it in your page:
<script>
export default {
use: ['csrf'],
POST: true,
};
</script>Session Middleware
Sessions are available through conn.req.session:
export function session(conn) {
const user = conn.session.user;
if (!user) {
return conn.redirect('/login');
}
}Route Handlers
+server.mjs can also define API routes without a page:
export default {
use: ['auth'],
['GET /api/users']() {
return Response.json([{ id: 1, name: 'Alice' }]);
},
['POST /api/users']({ body }) {
return Response.json({ created: true }, { status: 201 });
},
catch(error, conn) {
console.error(error);
return Response.json({ error: 'Internal Server Error' }, { status: 500 });
},
finally(response) {
response.headers.set('X-Response-Time', Date.now());
return response;
},
};Error Handling
The catch handler receives errors from the middleware chain:
export default {
use: ['auth'],
catch(error, conn) {
if (error.name === 'UnauthorizedError') {
return conn.req.redirect('/login');
}
throw error;
},
finally(response) {
return response;
},
};Always return the response in
finallyhandlers. The framework needs this to build the final response.