Aller au contenu

TypeScript : projets Node.js et web

TypeScript vérifie le code avant exécution. Toute donnée qui vient de l’extérieur (corps de requête, JSON d’une API, variables d’environnement, base de données, localStorage) doit être validée à l’exécution : un type seul n’apporte aucune garantie.

const client = (await reponse.json()) as Client; // aucune vérification : peut mentir
Fenêtre de terminal
npm install zod
import { z } from 'zod';
const SchemaClient = z.object({
id: z.number().int().positive(),
nom: z.string().trim().min(1).max(100),
email: z.string().email().optional(),
});
type Client = z.infer<typeof SchemaClient>; // le type est DÉRIVÉ du schéma
const resultat = SchemaClient.safeParse(JSON.parse(texte));
if (!resultat.success) {
console.error(resultat.error.issues);
} else {
const client: Client = resultat.data; // type vérifié à l'exécution
}

z.infer produit le type à partir du schéma : une seule source de vérité pour le type et la validation. Voir Express : API JSON.

Fenêtre de terminal
npm install express
npm install -D @types/express tsx
import express, {
type NextFunction,
type Request,
type Response,
} from 'express';
import { z } from 'zod';
const app = express();
app.use(express.json({ limit: '100kb' }));
const SchemaCreation = z.object({
nom: z.string().min(1),
email: z.string().email(),
});
type Creation = z.infer<typeof SchemaCreation>;
app.post('/api/clients', (req: Request, res: Response) => {
const resultat = SchemaCreation.safeParse(req.body);
if (!resultat.success) {
res
.status(400)
.json({ erreur: 'données invalides', details: resultat.error.issues });
return;
}
const donnees: Creation = resultat.data;
res.status(201).json({ id: 1, ...donnees });
});
app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => {
console.error(err);
res.status(500).json({ erreur: 'erreur interne' });
});
app.listen(3000, '127.0.0.1');

Voir Express : installation et Express : sécurité et production.

import { z } from 'zod';
const SchemaEnv = z.object({
NODE_ENV: z
.enum(['development', 'production', 'test'])
.default('development'),
PORT: z.coerce.number().int().min(1).max(65535).default(3000),
DATABASE_URL: z.string().url(),
});
export const env = SchemaEnv.parse(process.env); // échoue au démarrage si une variable est invalide

Valider l’environnement au démarrage : une configuration incorrecte fait échouer l’application immédiatement, avec un message clair. env.PORT est alors un number garanti.

class ErreurHttp extends Error {
constructor(
public readonly statut: number,
message: string,
) {
super(message);
this.name = 'ErreurHttp';
}
}
try {
throw new ErreurHttp(404, 'client introuvable');
} catch (erreur: unknown) {
if (erreur instanceof ErreurHttp)
console.error(erreur.statut, erreur.message);
else throw erreur;
}

En TypeScript, la variable d’un catch est de type unknown (avec strict) : l’affiner avant usage. Le mot-clé public dans les paramètres du constructeur est une syntaxe qui génère du code (voir l’avertissement sur l’exécution directe dans Installation).

type Resultat<T, E = Error> =
{ ok: true; valeur: T } | { ok: false; erreur: E };
function diviser(a: number, b: number): Resultat<number, string> {
return b === 0
? { ok: false, erreur: 'division par zéro' }
: { ok: true, valeur: a / b };
}
const r = diviser(1, 0);
if (r.ok) console.log(r.valeur);
else console.error(r.erreur);

L’union discriminée oblige l’appelant à traiter l’échec (Types de base).

import { useState, type ReactNode } from 'react';
interface Props {
titre: string;
enfants?: ReactNode;
onValider: (valeur: string) => void;
}
export function Formulaire({ titre, enfants, onValider }: Props) {
const [valeur, setValeur] = useState('');
return (
<form
onSubmit={(e) => {
e.preventDefault();
onValider(valeur);
}}
>
<h2>{titre}</h2>
<input value={valeur} onChange={(e) => setValeur(e.target.value)} />
{enfants}
</form>
);
}

useState<string | null>(null) précise l’état s’il ne peut pas être déduit. Voir React : composants et JSX.

Les fichiers .astro et .ts sont typés ; vérifier avec :

Fenêtre de terminal
npx astro check
---
interface Props {
titre: string;
tags?: string[];
}
const { titre, tags = [] } = Astro.props;
---

Voir Astro : composants et layouts.

Next.js génère les types des routes et des pages ; les paramètres de route sont des promesses dans les versions récentes :

type Props = { params: Promise<{ slug: string }> };
export default async function Page({ params }: Props) {
const { slug } = await params;
return <h1>{slug}</h1>;
}

Voir Next.js : routage avec l’App Router.

Étape Commande
Vérification des types tsc --noEmit (ou astro check, next build)
Analyse du code eslint . avec typescript-eslint (ESLint : TypeScript)
Formatage prettier --check .
Tests vitest run (Vitest : installation)
Construction tsc, ou l’outil du framework

Dans l’intégration continue, séparer types, analyse, tests : un échec indique immédiatement la cause (voir Husky : intégration continue et équipes).

Besoin Bibliothèque
Validation de données Zod, Valibot
Exécuter du TypeScript tsx, Node.js (types effacés)
Tests Vitest
Requêtes SQL typées Kysely, Drizzle
Client d’API typé Génération à partir d’une spécification OpenAPI
Dates date-fns, Temporal (selon la version du moteur)
Symptôme Piste
Property 'body' does not exist ou types d’Express manquants npm install -D @types/express
req.body de type any Valider avec un schéma Zod (le type de any n’est pas une garantie)
Types qui divergent des données réelles Type écrit à la main et non validé : dériver du schéma
process.env.X est string | undefined Valider l’environnement au démarrage
Les tests passent, tsc échoue Vitest ne vérifie pas les types : tsc --noEmit en CI
Cannot find module après le build Extensions dans les imports (.js avec NodeNext)
Build lent incremental: true, skipLibCheck, limiter include

Ubuntu 24.04 et 26.04 : différences à connaître

Section intitulée « Ubuntu 24.04 et 26.04 : différences à connaître »

Aucune propre à Ubuntu. Pour déployer, compiler (tsc) puis exécuter node dist/index.js sous systemd (voir Express : sécurité et production) ; le code compilé ne dépend plus de TypeScript.