Générateur de modèle pour Express
Il y a un Générateur d’applications Express qui permet de générer la structure de base recommandée en Typescript.
Voici comment générer l’application
Situez vous dans le dossier où vous désirez créer l’application (le dossier de l’application sera automatiquement créé à la prochaine étape)
Exécutez la commande de création d’application :
Une fois l’application générée, déplacez-vous dans le dossier créé et installez les modules
Si vous avez des erreurs de modules dépréciés, vous pouvez les corriger en suivant les instructions de la section Module déprécié.
Exécutez l’application en utilisant la commande suivante :
Configurer l'analysateur de code ESLint dans le projet Express
Étape 1 - Installer le bon module ESLint
Étape 2 - Désinstaller les modules ESLint TS et JS
Étape 3 - Corriger configuration ESLint
Il faut retirer les références aux vieux modules ESLint TS et JS en faveur au module unifié.
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import stylistic from '@stylistic/eslint-plugin';
import nodePlugin from 'eslint-plugin-n';
export default tseslint.config(
eslint.configs.recommended,
nodePlugin.configs['flat/recommended-script'],
...tseslint.configs.strictTypeChecked,
...tseslint.configs.stylisticTypeChecked,
{
ignores: ['**/node_modules/*', '**/*.mjs', '**/*.js'],
},
{
languageOptions: {
parserOptions: {
project: './tsconfig.json',
warnOnUnsupportedTypeScriptVersion: false,
},
},
},
{
plugins: {
'@stylistic': stylistic,
},
},
{
files: ['**/*.ts'],
},
{
rules: {
'@typescript-eslint/explicit-member-accessibility': 'warn',
'@typescript-eslint/no-misused-promises': 0,
'@typescript-eslint/no-floating-promises': 0,
'@typescript-eslint/no-confusing-void-expression': 0,
'@typescript-eslint/no-unnecessary-condition': 0,
'@typescript-eslint/restrict-template-expressions': [
'error',
{ allowNumber: true },
],
'@typescript-eslint/restrict-plus-operands': [
'warn',
{ allowNumberAndString: true },
],
'@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/no-unsafe-enum-comparison': 0,
'@typescript-eslint/no-unnecessary-type-parameters': 0,
'@stylistic/no-extra-semi': 'warn',
'max-len': [
'warn',
{
code: 80,
},
],
'@stylistic/semi': ['warn', 'always'],
'@stylistic/member-delimiter-style': [
'warn',
{
multiline: {
delimiter: 'comma',
requireLast: true,
},
singleline: {
delimiter: 'comma',
requireLast: false,
},
overrides: {
interface: {
singleline: {
delimiter: 'semi',
requireLast: false,
},
multiline: {
delimiter: 'semi',
requireLast: true,
},
},
},
},
],
'@typescript-eslint/no-non-null-assertion': 0,
'@typescript-eslint/no-unused-expressions': 'warn',
'comma-dangle': ['warn', 'always-multiline'],
'no-console': 1,
'no-extra-boolean-cast': 0,
indent: ['warn', 2],
quotes: ['warn', 'single'],
'n/no-process-env': 1,
'n/no-missing-import': 0,
'n/no-unpublished-import': 0,
'prefer-const': 'warn',
},
},
);
Pour Windows :
Étape 1 - installer cross-env :
Étape 2 - modifier package.json comme suit :
"dev": "cross-env NODE_ENV=development ts-node ./src",
"dev:hot": "cross-env nodemon --exec \"npm run dev\" --watch ./src --ext .ts",
Étape 3 - modifier eslint.config.ts :
{
languageOptions: {
parserOptions: {
project: './tsconfig.json',
tsconfigRootDir: process.cwd(), // AJOUTER CETTE LIGNE EN WINDOWS
warnOnUnsupportedTypeScriptVersion: false,
},
},
},
Coder un API Express
L'exemple suivant code une mini API qui gère des réservations dans un hotel.
CodeSandbox
Étape 1 - Créer l’interface model/Reservation.ts
import { isNumber, isString, isEnumVal } from 'jet-validators';
import { parseObject, TParseOnError } from 'jet-validators/utils';
import { isRelationalKey, transIsDate } from '@src/common/util/validators';
import { IModel } from './common/types';
/******************************************************************************
Constants
******************************************************************************/
const DEFAULT_RESERVATION_VALS = (): IReservation => ({
id: -1,
nom: '',
courriel: '',
dateDebut: new Date(),
dateFin: new Date(),
typeChambre: TypeChambre.Standard,
prixParNuit: 0,
created: new Date(), // Vient du IModel pour la date de création
});
/******************************************************************************
Types
******************************************************************************/
export enum TypeChambre {
Standard='Standard',
Deluxe='Deluxe',
}
export interface IReservation extends IModel {
nom: string;
courriel: string;
dateDebut: Date;
dateFin: Date;
typeChambre: TypeChambre;
prixParNuit: number;
}
/******************************************************************************
Setup
******************************************************************************/
// Crée une validation pour l'énumération de type de chambre
const isTypeChambreEnumVal = isEnumVal(TypeChambre);
// Initialize the "parseUser" function
const parseReservation = parseObject<IReservation>({
id: isRelationalKey,
nom: isString,
courriel: isString,
dateDebut: transIsDate,
dateFin: transIsDate,
prixParNuit: isNumber,
typeChambre: isTypeChambreEnumVal,
created: transIsDate,
});
/******************************************************************************
Functions
******************************************************************************/
/**
* Nouvel objet de réservation.
*/
function __new__(reservation?: Partial<IReservation>): IReservation {
const retVal = { ...DEFAULT_RESERVATION_VALS(), ...reservation };
return parseReservation(retVal, errors => {
throw new Error('Création a échouée ' + JSON.stringify(errors, null, 2));
});
}
/**
* Valide si l'objet est une réservation, pour la route.
*/
function test(arg: unknown, errCb?: TParseOnError): arg is IReservation {
return !!parseReservation(arg, errCb);
}
/******************************************************************************
Export default
******************************************************************************/
export default {
new: __new__,
test,
} as const;
Étape 2 - Ajouter le modèle à la base de données bidon
import jsonfile from 'jsonfile';
import ENV from '@src/common/constants/ENV';
import { NodeEnvs } from '@src/common/constants';
import { IReservation } from '@src/models/Reservation';
/******************************************************************************
Constants
******************************************************************************/
const DB_FILE_NAME = (
ENV.NodeEnv === NodeEnvs.Test
? 'database.test.json'
: 'database.json'
);
/******************************************************************************
Types
******************************************************************************/
interface IDb {
reservations: IReservation[];
}
/******************************************************************************
Functions
******************************************************************************/
/**
* Fetch the json from the file.
*/
function openDb(): Promise<IDb> {
return jsonfile.readFile(__dirname + '/' + DB_FILE_NAME) as Promise<IDb>;
}
/**
* Update the file.
*/
function saveDb(db: IDb): Promise<void> {
return jsonfile.writeFile((__dirname + '/' + DB_FILE_NAME), db);
}
/**
* Empty the database
*/
function cleanDb(): Promise<void> {
return jsonfile.writeFile((__dirname + '/' + DB_FILE_NAME), {});
}
/******************************************************************************
Export default
******************************************************************************/
export default {
openDb,
saveDb,
cleanDb,
} as const;
Étape 3 - Mettre à jour la base de données
{"reservations":[{"id":716805071644,"nom":"Kamala Harris","courriel":"kamala@profinfo.ca","dateDebut":"2025-01-06","dateFin":"2025-01-08","typeChambre":"Deluxe","prixParNuit":150},{"id":177404267690,"nom":"Kamala Harris","courriel":"kamala@profinfo.ca","dateDebut":"2025-01-06","dateFin":"2025-01-08","typeChambre":"Deluxe","prixParNuit":150},{"id":161338412973,"nom":"Kamala Harris","courriel":"kamala@profinfo.ca","dateDebut":"2025-01-06","dateFin":"2025-01-08","typeChambre":"Deluxe","prixParNuit":150},{"id":904137092223,"nom":"Kamala Harris","courriel":"kamala@profinfo.ca","dateDebut":"2025-01-06T00:00:00.000Z","dateFin":"2025-01-08T00:00:00.000Z","typeChambre":"Deluxe","prixParNuit":150,"created":"2025-08-11T00:00:00.000Z"},{"id":997592835914,"nom":"Kamala Harris","courriel":"kamala@profinfo.ca","dateDebut":"2025-01-06T00:00:00.000Z","dateFin":"2025-01-08T00:00:00.000Z","typeChambre":"Deluxe","prixParNuit":150,"created":"2025-08-11T00:00:00.000Z"}]}
Étape 4 - Créer le repo
import { IReservation } from '@src/models/Reservation';
import { getRandomInt } from '@src/common/util/misc';
import orm from './MockOrm';
/******************************************************************************
Functions
******************************************************************************/
/**
* Extraire une réservation.
*/
async function getOne(courriel: string): Promise<IReservation | null> {
const db = await orm.openDb();
for (const reservation of db.reservations) {
if (reservation.courriel === courriel) {
return reservation;
}
}
return null;
}
/**
* Vérifier si une réservation existe pour cet id.
*/
async function persists(id: number): Promise<boolean> {
const db = await orm.openDb();
for (const reservation of db.reservations) {
if (reservation.id === id) {
return true;
}
}
return false;
}
/**
* Extraire toutes les réservations.
*/
async function getAll(): Promise<IReservation[]> {
const db = await orm.openDb();
return db.reservations;
}
/**
* Ajouter une réservation.
*/
async function add(reservation: IReservation): Promise<void> {
const db = await orm.openDb();
reservation.id = getRandomInt();
db.reservations.push(reservation);
return orm.saveDb(db);
}
/**
* Mettre à jour une réservation.
*/
async function update(reservation: IReservation): Promise<void> {
const db = await orm.openDb();
for (let i = 0; i < db.reservations.length; i++) {
if (db.reservations[i].id === reservation.id) {
const dbReservation = db.reservations[i];
db.reservations[i] = {
...dbReservation,
nom: reservation.nom,
courriel: reservation.courriel,
dateDebut: reservation.dateDebut,
dateFin: reservation.dateFin,
typeChambre: reservation.typeChambre,
prixParNuit: reservation.prixParNuit,
};
return orm.saveDb(db);
}
}
}
/**
* Supprimer une réservation.
*/
async function delete_(id: number): Promise<void> {
const db = await orm.openDb();
for (let i = 0; i < db.reservations.length; i++) {
if (db.reservations[i].id === id) {
db.reservations.splice(i, 1);
return orm.saveDb(db);
}
}
}
// **** Unit-Tests Only **** //
/**
* Supprimer toutes les réservations.
*/
async function deleteAllReservations(): Promise<void> {
const db = await orm.openDb();
db.reservations = [];
return orm.saveDb(db);
}
/**
* Insérer plusieurs réservations
*/
async function insertMult(
reservations: IReservation[] | readonly IReservation[],
): Promise<IReservation[]> {
const db = await orm.openDb(),
reservationsF = [ ...reservations ];
for (const reservation of reservationsF) {
reservation.id = getRandomInt();
reservation.created = new Date();
}
db.reservations = [ ...db.reservations, ...reservations ];
await orm.saveDb(db);
return reservationsF;
}
/******************************************************************************
Export default
******************************************************************************/
export default {
getOne,
persists,
getAll,
add,
update,
delete: delete_,
deleteAllReservations,
insertMult,
} as const;
Étape 5 - Créer le service
import { RouteError } from '@src/common/util/route-errors';
import HttpStatusCodes from '@src/common/constants/HttpStatusCodes';
import ReservationRepo from '@src/repos/ReservationRepo';
import { IReservation } from '@src/models/Reservation';
/******************************************************************************
Constants
******************************************************************************/
export const RESERVATION_NOT_FOUND_ERR = 'Réservation non trouvée';
/******************************************************************************
Functions
******************************************************************************/
/**
* Extraire toutes les réservations.
*/
function getAll(): Promise<IReservation[]> {
return ReservationRepo.getAll();
}
/**
* Ajouter une réservation.
*/
function addOne(reservation: IReservation): Promise<void> {
return ReservationRepo.add(reservation);
}
/**
* Mettre à jour une réservation
*/
async function updateOne(reservation: IReservation): Promise<void> {
const persists = await ReservationRepo.persists(reservation.id);
if (!persists) {
throw new RouteError(
HttpStatusCodes.NOT_FOUND,
RESERVATION_NOT_FOUND_ERR,
);
}
// Return user
return ReservationRepo.update(reservation);
}
/**
* Delete a user by their id.
*/
async function _delete(id: number): Promise<void> {
const persists = await ReservationRepo.persists(id);
if (!persists) {
throw new RouteError(
HttpStatusCodes.NOT_FOUND,
RESERVATION_NOT_FOUND_ERR,
);
}
// Delete user
return ReservationRepo.delete(id);
}
/******************************************************************************
Export default
******************************************************************************/
export default {
getAll,
addOne,
updateOne,
delete: _delete,
} as const;
Étape 6 - Créer les routes
import { isNumber } from 'jet-validators';
import { transform } from 'jet-validators/utils';
import HttpStatusCodes from '@src/common/constants/HttpStatusCodes';
import ReservationService from '@src/services/ReservationService';
import { IReq, IRes } from './common/types';
import { parseReq } from './common/util';
import Reservation from '@src/models/Reservation';
/******************************************************************************
Constants
******************************************************************************/
const Validators = {
add: parseReq({ reservation: Reservation.test }),
update: parseReq({ reservation: Reservation.test }),
delete: parseReq({ id: transform(Number, isNumber) }),
} as const;
/******************************************************************************
Functions
******************************************************************************/
/**
* Extraire toutes les réservations.
*/
async function getAll(_: IReq, res: IRes) {
const reservations = await ReservationService.getAll();
res.status(HttpStatusCodes.OK).json({ reservations });
}
/**
* Ajouter une réservation.
*/
async function add(req: IReq, res: IRes) {
const { reservation } = Validators.add(req.body);
await ReservationService.addOne(reservation);
res.status(HttpStatusCodes.CREATED).end();
}
/**
* Update one user.
*/
async function update(req: IReq, res: IRes) {
const { reservation } = Validators.update(req.body);
await ReservationService.updateOne(reservation);
res.status(HttpStatusCodes.OK).end();
}
/**
* Delete one user.
*/
async function delete_(req: IReq, res: IRes) {
const { id } = Validators.delete(req.params);
await ReservationService.delete(id);
res.status(HttpStatusCodes.OK).end();
}
/******************************************************************************
Export default
******************************************************************************/
export default {
getAll,
add,
update,
delete: delete_,
} as const;
Étape 7 - Ajouter les chemins de l’API dans les commons
export default {
Base: '/api',
Reservations: {
Base: '/reservations',
Get: '/all',
Add: '/add',
Update: '/update',
Delete: '/delete/:id',
},
} as const;
Étape 8 - Ajouter les chemins de l’API dans index.ts
import { Router } from 'express';
import Paths from '@src/common/constants/Paths';
import ReservationRoutes from './ReservationRoutes';
/******************************************************************************
Setup
******************************************************************************/
const apiRouter = Router();
// ** Ajouter les routes de réservation ** //
// Initialiser le router
const reservationRoutes = Router();
// Les routes
reservationRoutes.get(Paths.Reservations.Get, ReservationRoutes.getAll);
reservationRoutes.post(Paths.Reservations.Add, ReservationRoutes.add);
reservationRoutes.put(Paths.Reservations.Update, ReservationRoutes.update);
reservationRoutes.delete(Paths.Reservations.Delete, ReservationRoutes.delete);
// Ajouter le router à l'API
apiRouter.use(Paths.Reservations.Base, reservationRoutes);
/******************************************************************************
Export default
******************************************************************************/
export default apiRouter;