Définir un contrat explicite pour l’API
Une API NestJS utilise les mêmes dates que JavaScript : Date attend des millisecondes. Demandez l’unité au client plutôt que de la deviner. L’exemple suivant accepte un entier signé et unit=s ou unit=ms.
Contrôleur : valider, convertir et répondre en JSON
Dans un projet NestJS existant, placez ce code dans timestamps.module.ts puis ajoutez TimestampsModule aux imports de votre module principal. Il ne requiert aucune dépendance supplémentaire à 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 doit être un entier décimal');
}
if (unit !== 's' && unit !== 'ms') {
throw new BadRequestException('unit doit valoir s ou 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 hors plage');
}
const date = new Date(milliseconds);
return {
timestampMilliseconds: milliseconds,
timestampSeconds: Math.floor(milliseconds / 1000),
isoUtc: date.toISOString(),
};
}
}
@Module({ controllers: [TimestampsController] })
export class TimestampsModule {}La vérification du type rejette aussi les paramètres répétés reçus comme tableaux. La limite de Date est plus restrictive que celle des entiers sûrs JavaScript : les deux contrôles sont utiles avant toISOString().
Exemple de requête et réponse
GET /timestamps/convert?value=1711972800&unit=s{
"timestampMilliseconds": 1711972800000,
"timestampSeconds": 1711972800,
"isoUtc": "2024-04-01T12:00:00.000Z"
}Les valeurs invalides, l’unité absente et les dates hors plage produisent une réponse HTTP 400. L’endpoint illustré appartient à votre application NestJS ; Timestampinfo ne fournit pas cette API.
Pourquoi ne pas se contenter de ParseIntPipe ?
ParseIntPipe convertit un paramètre en entier et rejette une entrée non numérique. Il ne remplace pas le contrôle de l’unité, de la précision et de la plage autorisée par votre application. Pour plusieurs champs, un DTO avec ValidationPipe permet de centraliser la validation. L’annotation TypeScript value: number seule ne valide pas une requête HTTP.
Variante DTO avec class-validator
Pour un corps JSON, voici deux contrats indépendants : un entier en millisecondes, ou une chaîne ISO avec décalage explicite. Installez class-validator et class-transformer, puis activez la validation dans main.ts.
import { ValidationPipe } from '@nestjs/common';
// Après 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;
}Utilisez ces classes avec @Body() body: TimestampDto ou @Body() body: IsoDateDto. Ajoutez @Min(0) seulement si votre métier interdit les dates antérieures à 1970. La conversion @Type(() => Number) est permissive : une chaîne vide ou null peut devenir zéro. Si votre API doit les refuser, utilisez la validation stricte du premier exemple ou refusez ces valeurs avant transformation.
Transformer un entier en Date avec Transform
Cette variante accepte uniquement un nombre JSON entier en millisecondes. Une valeur invalide reste inchangée pour que @IsDate() la rejette ; on évite ainsi de transformer null en 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;
}Normaliser une réponse sans transformer tout le payload
Un interceptor peut formater un champ connu. Celui-ci attend une réponse { createdAt: Date } et doit être attaché uniquement aux routes qui respectent ce contrat, avec @UseInterceptors(CreatedAtInterceptor). N’essayez pas de deviner si chaque entier d’un payload est 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 date doit déjà être valide côté service. Pour un objet simple, appeler explicitement toISOString() dans le mapping de réponse reste suffisant.
Fractions, valeurs négatives et grandes unités
Ce contrat rejette volontairement les secondes décimales. Envoyez des millisecondes pour conserver cette précision. Pour value=-1&unit=ms, le résultat ISO est 1969-12-31T23:59:59.999Z ; le champ de secondes vaut -1, car il est arrondi vers le bas.
Pour des nanosecondes dépassant la précision de Number, transportez une chaîne décimale et traitez-la avec BigInt. Un BigInt ne se sérialise pas directement en JSON : renvoyez une chaîne. Convertir ensuite en Date perd la fraction de milliseconde.
Fuseaux et stockage
toISOString() fournit ici une date UTC terminée par Z. Gardez un instant commun en base et appliquez le fuseau à l’affichage. Avec Prisma ou TypeORM, vérifiez aussi le type de colonne et la configuration du pilote : l’ORM seul ne garantit pas le traitement du fuseau. Une heure métier saisie sans décalage exige une règle explicite pour le fuseau et les changements d’heure.
Contrôlez les valeurs avec le convertisseur Unix ou le convertisseur ISO 8601, comparez les unités de timestamp et consultez le guide JavaScript pour le formatage avec Intl.DateTimeFormat.
Références officielles
NestJS — contrôleurs · Pipes et ParseIntPipe · ValidationPipe et DTO · MDN — Date
Publié le 25 septembre 2026.