Fastify AJV date-time schema validation missing timezone normalization
0 reputation · 01 Apr 2024, 21:21 UTC
0 reputation · 01 Apr 2024, 21:21 UTC
A design goal is to accept ISO 8601 date-time strings in request bodies and ensure they are handled in a canonical timezone, such as UTC or a configured regional zone, before business logic runs.
Fastify uses AJV for schema validation. The format: 'date-time' keyword validates the string shape but does not transform the value. Validated data remains a raw string in request.body. There is no built-in hook or plugin that automatically converts validated date-time fields to Date objects or to a normalized offset during the request lifecycle.
The missing constraint is where and how the conversion boundary should be defined so all routes share consistent timezone handling without per-route manual parsing. The uncertainty is whether normalization belongs in schema configuration, a global request lifecycle extension, or application-level code.
Which Fastify extension point is appropriate for normalizing validated date-time strings to a canonical timezone? Should the permission boundary for conversion be enforced via schema, a global preHandler, or a custom AJV format? What is the recommended way to declare a regional timezone default for all validated date-time fields?
33275 reputation · 02 Apr 2024, 09:10 UTC
Use a global preValidation hook. AJV’s format: 'date-time' only validates the string shape; it does not transform the value, so request.body still contains a raw ISO 8601 string after schema validation. A global preValidation hook runs after validation and before the route handler, making it the correct boundary to convert matched fields to Date objects or UTC-normalized strings.
Schema keywords describe constraints, not transformations. AJV format validators return a boolean; they are not designed to rewrite values during validation. A custom format that mutates data couples validation to conversion and makes the schema harder to test. Keep validation in the schema and conversion in the hook.
Europe/Paris, and declare it through an environment variable (CANONICAL_TZ) or plugin options rather than hard-coding it in every route.fastify-plugin so it applies across the application without per-route duplication.format: 'date-time'. Maintain an explicit field list or inspect known body keys; do not blindly walk every string property.request.body.normalized or replace the field only if downstream code does not need the original string.const fp = require('fastify-plugin');
const { DateTime } = require('luxon');
module.exports = fp(async function (fastify, opts) {
const canonicalTz = process.env.CANONICAL_TZ || 'UTC';
const dateTimeFields = ['startsAt', 'endsAt'];
fastify.addHook('preValidation', async (request, reply) => {
const body = request.body;
if (!body || typeof body !== 'object') return;
for (const field of dateTimeFields) {
if (typeof body[field] === 'string') {
const parsed = DateTime.fromISO(body[field]);
if (!parsed.isValid) continue;
body[field] = parsed.setZone(canonicalTz).toISO();
}
}
});
});Start the server, then send a request with a non-UTC offset:
curl -X POST http://localhost:3000/bookings \
-H 'Content-Type: application/json' \
-d '{"startsAt":"2026-10-17T15:30:00-05:00"}'Inspect the handler output or add a temporary log in the hook to confirm startsAt is now expressed in CANONICAL_TZ. Repeat with a different offset to verify consistent normalization.
Date cannot reliably convert between IANA zones without external data.request.body.Missing diagnostic detail: Do any handlers or plugins downstream of validation require the original, unnormalized date-time string? If so, preserve it in a separate property rather than overwriting the field.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.