Fastify: валидация и сериализация
Вот главный аргумент за Fastify в проде, ради которого терпят чуть больше формальностей, чем в Express. В Fastify схема роута это не документация рядом с кодом, а исполняемый контракт: вход проверяется по схеме до обработчика, выход сериализуется по схеме на выходе. Одна декларация закрывает сразу валидацию, типы, скорость и утечки данных. Разберём, как это работает и как заменить дефолтные движки на свои.
1. Schema-first: схема на роуте
Каждый роут принимает поле schema с подсхемами для частей запроса и для
ответа.
app.post('/articles', {
schema: {
body: {
type: 'object',
required: ['title'],
properties: {
title: { type: 'string', minLength: 1 },
tags: { type: 'array', items: { type: 'string' } },
},
},
querystring: {
type: 'object',
properties: { draft: { type: 'boolean' } },
},
response: {
200: {
type: 'object',
properties: {
id: { type: 'integer' },
title: { type: 'string' },
},
},
},
},
handler: async (request) => {
// request.body уже провалидирован
return createArticle(request.body);
},
});
Схема это обычный JSON Schema. Подсхемы:
body,querystring,params,headers, проверяют части входа;response.<код>, описывает выход для данного статуса.
2. Валидация входа: Ajv
Входные подсхемы Fastify компилирует через Ajv. Важная
деталь: компиляция происходит один раз при старте, схема превращается в
функцию-валидатор, и на каждый запрос вызывается уже она. Битый или
несоответствующий вход отсекается автоматически с 400, обработчик даже не
вызывается.
Поиграй с этим на виджете: меняй тело запроса слева и смотри, какие правила входной схемы проходят, а какие нет. Невалидный вход, и до правой части дело не дойдёт: handler не вызовется.
name: string, required, minLength 1
age: integer, optional, >= 0клиент получил3. Сериализация выхода: fast-json-stringify
Тут начинается то, чего нет у большинства фреймворков. Если задать
response-схему, Fastify сериализует ответ не через JSON.stringify, а
через fast-json-stringify. Он
компилирует схему ответа в специализированную функцию и потому быстрее
универсального JSON.stringify. Но скорость тут не главное.
Главное, схема ответа отсекает лишние поля. В выход попадает только то,
что описано в response-схеме. Это превращает сериализацию в границу
безопасности: даже если handler по ошибке вернул запись из БД целиком, со
служебными полями и хешем пароля, наружу уйдут только поля из схемы.
Переключи на виджете тумблер «выходная схема». Без схемы сериализуется вся
запись, и passwordHash утекает клиенту. Со схемой остаются только id и
title, утечка закрыта. Это не побочный эффект, а штатный приём: на
публичных роутах response-схема обязательна.
4. Type providers: типы из схемы
Схема описывает форму данных в рантайме. Но request.body по умолчанию
типизирован как unknown, и приходится писать request.body as { ... },
дублируя то, что уже сказано в схеме. Type provider убирает это
дублирование: типы выводятся прямо из схемы.
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox';
import { Type } from '@sinclair/typebox';
const app = Fastify().withTypeProvider<TypeBoxTypeProvider>();
app.post('/articles', {
schema: {
body: Type.Object({
title: Type.String({ minLength: 1 }),
tags: Type.Optional(Type.Array(Type.String())),
}),
},
handler: async (request) => {
// request.body.title типизирован как string, без as
return { id: 1, title: request.body.title };
},
});
Одна декларация схемы даёт сразу три вещи: рантайм-валидацию, сериализацию
и статические типы. Источник истины один, типы и проверка не разъезжаются.
Кроме typebox есть @fastify/type-provider-json-schema-to-ts (выводит
типы из голой JSON Schema) и провайдер для zod, см. § 5. Это та же идея, что
в 04-ts: надёжность и типы: держать один
источник истины для формы данных.
дальше после входа
Урок целиком открыт всем, кто вошёл. Войди по своему email, чтобы читать дальше.
Вход пока по приглашению. Если хочешь учиться, напиши Vassiliy.