Серверное middleware, валидация и безопасность
Серверные данные нельзя доверять клиенту: валидация, аккуратные ошибки и секреты в runtimeConfig — обязательная гигиена эндпоинтов.
Суть: файлы в server/middleware/ выполняются перед каждым серверным запросом. Входные данные нужно валидировать, ошибки отдавать через createError с правильным статусом, а секреты держать в runtimeConfig, а не в клиентском коде.
Серверный код мощен и потому требует дисциплины. Любые данные от клиента — тело, параметры, заголовки — потенциально вредоносны: их можно подделать. Бэкенд обязан проверять вход, не доверять токенам «на слово» и прятать секреты. Этот урок — про гигиену серверной части.
Серверное middleware живёт в server/middleware/ и срабатывает перед каждым запросом — удобно для логирования, аутентификации, CORS:
// server/middleware/auth.ts
export default defineEventHandler((event) => {
const token = getHeader(event, "authorization")
if (event.path.startsWith("/api/admin") && !isValid(token)) {
throw createError({ statusCode: 401, statusMessage: "Не авторизован" })
}
// ничего не возвращаем -> запрос идёт дальше
})
Валидация входа — не опция, а необходимость. Прежде чем сохранять или использовать данные, проверьте их форму и содержимое; неверный вход — это 400 Bad Request. Секреты (ключи API, строки подключения к БД) держат в runtimeConfig и читают на сервере через useRuntimeConfig() — так они не утекут в бандл.
Конвейер серверного запроса
запрос -> [server/middleware] -> обработчик server/api
| |
(auth, log, CORS) валидация входа
| |
401/403 при отказе 400 при плохих данных
|
секреты из runtimeConfig
Как работает под капотом
Серверное middleware регистрируется в Nitro как глобальный обработчик, выполняемый до маршрутных. Если оно бросает createError, цепочка прерывается и клиент получает ответ с этим статусом. runtimeConfig разделён на серверную часть (приватную) и public (доступную клиенту): приватные значения подставляются только в серверный бандл. Валидация — это ваша логика; популярны схемы (например, через библиотеки валидации), но суть одна: не пускать дальше кривые данные.
Смоделируем конвейер «middleware + валидация» с понятными статусами ответа:
// Серверный конвейер: авторизация -> валидация -> обработка.
function createError(status, msg) { return { error: true, status, msg }; }
function authMiddleware(req) {
if (req.path.startsWith("/api/admin") && req.token !== "secret") {
return createError(401, "Не авторизован");
}
return null; // пропускаем
}
function validate(body) {
if (!body || typeof body.name !== "string" || body.name.length === 0) {
return createError(400, "Поле name обязательно");
}
return null;
}
function handleRequest(req) {
const authErr = authMiddleware(req);
if (authErr) return authErr;
const valErr = validate(req.body);
if (valErr) return valErr;
return { status: 201, created: req.body.name };
}
console.log(handleRequest({ path: "/api/admin/x", token: "wrong", body: {} }));
console.log(handleRequest({ path: "/api/products", body: { name: "" } }));
console.log(handleRequest({ path: "/api/products", body: { name: "Книга" } }));
Попробуй сам ▶ — запрос проходит охрану и валидацию, и только корректный доходит до обработки с кодом 201. Кривой вход останавливается на 400, чужак — на 401.
Частые ошибки
- Доверять клиентским данным. Тело и заголовки подделываются — всегда валидируйте на сервере.
- Секреты в
publicили в клиентском коде. Приватные ключи — только в серверной частиruntimeConfig. - Возвращать 200 на ошибку. Используйте корректные статусы: 400, 401, 403, 404, 500 — это контракт API.
Best practices
- Валидируйте каждый вход; на нарушение — 400 с понятным сообщением.
- Авторизацию и сквозные проверки выносите в
server/middleware. - Секреты — в приватный
runtimeConfig, читать черезuseRuntimeConfig()на сервере.
Итог: серверная безопасность — это валидация входа, корректные статусы и секреты в runtimeConfig. Middleware централизует сквозные проверки. На этом раздел о бэкенде завершён — переходим к финалу: SEO и деплою.