Definire un contratto API esplicito
Un'API NestJS usa le stesse date di JavaScript: Date si aspetta millisecondi. Chiedi al client l'unità invece di indovinarla. L'esempio seguente accetta un intero con segno e unit=s oppure unit=ms.
Controller: validare, convertire e rispondere in JSON
In un progetto NestJS esistente, inserisci questo codice in timestamps.module.ts e aggiungi TimestampsModule ai imports del modulo principale. Non sono necessarie dipendenze oltre a Nest.
import { BadRequestException, Controller, Get, Module, Query } from '@nestjs/common';
@Controller('timestamps')
export class TimestampsController {
@Get('convert')
convert(@Query('value') value: unknown, @Query('unit') unit: unknown) {
if (typeof value !== 'string' || !/^-?\d+$/.test(value)) {
throw new BadRequestException('value deve essere un intero decimale');
}
if (unit !== 's' && unit !== 'ms') {
throw new BadRequestException('unit deve essere s o ms');
}
const input = Number(value);
const milliseconds = unit === 's' ? input * 1000 : input;
if (!Number.isSafeInteger(input) || !Number.isSafeInteger(milliseconds)
|| Math.abs(milliseconds) > 8_640_000_000_000_000) {
throw new BadRequestException('Timestamp fuori intervallo');
}
const date = new Date(milliseconds);
return {
timestampMilliseconds: milliseconds,
timestampSeconds: Math.floor(milliseconds / 1000),
isoUtc: date.toISOString(),
};
}
}
@Module({ controllers: [TimestampsController] })
export class TimestampsModule {}Il controllo del tipo rifiuta anche parametri ripetuti ricevuti come array. Il limite di Date è più restrittivo del limite degli interi sicuri di JavaScript: entrambi i controlli sono utili prima di toISOString().
Esempio di richiesta e risposta
GET /timestamps/convert?value=1711972800&unit=s{
"timestampMilliseconds": 1711972800000,
"timestampSeconds": 1711972800,
"isoUtc": "2024-04-01T12:00:00.000Z"
}Valori non validi, unità mancante e date fuori intervallo producono una risposta HTTP 400. Questo endpoint di esempio appartiene alla tua applicazione NestJS; Timestampinfo non fornisce questa API.
Perché ParseIntPipe da solo non basta?
ParseIntPipe converte un parametro in intero e rifiuta input non numerici. Non sostituisce i controlli di unità, precisione e intervallo consentiti dall'applicazione. Per più campi, un DTO con ValidationPipe centralizza la validazione. L'annotazione TypeScript value: number da sola non valida una richiesta HTTP.
Variante DTO con class-validator
Per un corpo JSON, ecco due contratti indipendenti: un intero in millisecondi oppure una stringa ISO con offset esplicito. Installa class-validator e class-transformer, poi abilita la validazione in main.ts.
import { ValidationPipe } from '@nestjs/common';
// Dopo NestFactory.create(AppModule):
app.useGlobalPipes(new ValidationPipe({
transform: true,
whitelist: true,
forbidNonWhitelisted: true,
}));import { Type } from 'class-transformer';
import { IsInt, IsISO8601, Matches, Max, Min } from 'class-validator';
export class TimestampDto {
@Type(() => Number)
@IsInt()
@Min(-8_640_000_000_000_000)
@Max(8_640_000_000_000_000)
timestampMs!: number;
}
export class IsoDateDto {
@IsISO8601({ strict: true })
@Matches(/T.*(?:Z|[+-]\d{2}:\d{2})$/)
at!: string;
}Usa queste classi con @Body() body: TimestampDto oppure @Body() body: IsoDateDto. Aggiungi @Min(0) solo se le regole applicative vietano date prima del 1970. La conversione @Type(() => Number) è permissiva: una stringa vuota o null possono diventare zero. Se l'API deve rifiutarli, usa la validazione rigorosa del primo esempio oppure rifiuta questi valori prima della trasformazione.
Trasformare un intero in Date con Transform
Questa variante accetta solo un numero JSON intero in millisecondi. Un valore non valido rimane invariato affinché @IsDate() lo rifiuti; così si evita di convertire null nell'Epoch.
import { Transform } from 'class-transformer';
import { IsDate } from 'class-validator';
export class DateFromTimestampDto {
@Transform(({ value }) =>
typeof value === 'number' && Number.isSafeInteger(value)
&& Math.abs(value) <= 8_640_000_000_000_000
? new Date(value)
: value,
{ toClassOnly: true },
)
@IsDate()
at!: Date;
}Normalizzare una risposta senza trasformare l'intero payload
Un interceptor può formattare un campo noto. Questo si aspetta una risposta di tipo { createdAt: Date } e va applicato solo a route che rispettano questo contratto, usando @UseInterceptors(CreatedAtInterceptor). Non tentare di indovinare se ogni intero del payload è un timestamp.
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable, map } from 'rxjs';
type Source = { createdAt: Date };
type Response = { createdAt: string };
@Injectable()
export class CreatedAtInterceptor implements NestInterceptor<Source, Response> {
intercept(_context: ExecutionContext, next: CallHandler<Source>): Observable<Response> {
return next.handle().pipe(
map(({ createdAt }) => ({ createdAt: createdAt.toISOString() })),
);
}
}La data deve essere già valida nel servizio. Per un oggetto semplice, una chiamata esplicita a toISOString() nella mappatura della risposta è sufficiente.
Frazioni, valori negativi e unità grandi
Questo contratto rifiuta intenzionalmente i secondi frazionari. Invia millisecondi per preservare tale precisione. Per value=-1&unit=ms, il risultato ISO è 1969-12-31T23:59:59.999Z; il campo dei secondi è -1, perché viene arrotondato per difetto.
Per nanosecondi oltre la precisione di Number, trasporta una stringa decimale ed elaborala con BigInt. Un BigInt non può essere serializzato direttamente in JSON: restituisci una stringa. La successiva conversione in Date perde la frazione di millisecondo.
Fusi orari e memorizzazione
toISOString() produce qui una data UTC che termina con Z. Memorizza un istante comune nel database e applica il fuso per la visualizzazione. Con Prisma o TypeORM, controlla anche il tipo di colonna e la configurazione del driver: l'ORM da solo non garantisce la gestione dei fusi. Un'ora aziendale inserita senza offset richiede una regola esplicita per fuso e cambi dell'ora.
Verifica i valori con il convertitore Unix oppure con il convertitore ISO 8601, confronta le unità dei timestamp e consulta la guida JavaScript per formattare con Intl.DateTimeFormat.
Riferimenti ufficiali
NestJS — controller · Pipe e ParseIntPipe · ValidationPipe e DTO · MDN — Date
Versione italiana pubblicata il 26 settembre 2026.