Aller au contenu

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 :

console
npx express-generator-typescript mapremierappexpress

Une fois l’application générée, déplacez-vous dans le dossier créé et installez les modules

console
cd mapremierappexpress
npm install 

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 :

console
npm run dev 

express-generator

Exécution du modèle Express

Coder un API Express

L'exemple suivant code une mini API qui gère des réservations dans un hotel.

CodeSandbox

Démo - API Express

Étape 1 - Créer l’interface model/Reservation.ts

model/Reservation.ts
// **** Variables **** //

const INVALID_CONSTRUCTOR_PARAM =
  'nameOrObj arg must a string or an ' +
  'object with the appropriate user keys.';

export enum TypeChambre {
  Standard,
  Deluxe,
}

// **** Types **** //

export interface IReservation {
  id: number;
  nomClient: string;
  courrielClient: string;
  dateDebut: string;
  dateFin: string;
  typeChambre: TypeChambre;
  prixParNuit: number;
}

// **** Functions **** //

/**
 * Créer une réservation.
 *
 * @param {string=} nomClient - Le nom du client
 * @param {string=} courrielClient - L'adresse de courriel du client
 * @param {string=} dateDebut - Date de début de la réservation
 * @param {string=} dateFin - Date de fin de la réservation
 * @param {TypeChambre=} typeChambre - Le type de chambre réservée
 * @param {number=} prixParNuit - Le prix de la chambre par nuit
 * @param {number=} id - ID de la réservation dans la BD
 */
function new_(
  nomClient?: string,
  courrielClient?: string,
  dateDebut?: string,
  dateFin?: string,
  typeChambre?: TypeChambre,
  prixParNuit?: number,
  id?: number // id last cause usually set by db
): IReservation {
  return {
    id: id ?? -1,
    nomClient: nomClient ?? '',
    courrielClient: courrielClient ?? '',
    dateDebut: dateDebut ?? '',
    dateFin: dateFin ?? '',
    typeChambre: typeChambre ?? TypeChambre.Standard,
    prixParNuit: prixParNuit ?? 0,
  };
}

/**
 * Extraire une réservation d'un objet.
 *
 * @param {object} param - Objet représentant une réservation
 *
 * @returns {IReservation} - Une réservation
 */
function from(param: object): IReservation {
  // Check is réservation
  if (!isReservation(param)) {
    throw new Error(INVALID_CONSTRUCTOR_PARAM);
  }
  // Get user instance
  const p = param as IReservation;
  return new_(
    p.nomClient,
    p.courrielClient,
    p.dateDebut,
    p.dateFin,
    p.typeChambre,
    p.prixParNuit,
    p.id
  );
}

/**
 * Vérifier si l'objet représente une réservation
 *
 * @param {unknown} arg - Un paramètre qui pourrait être une réservation
 *
 * @returns {boolean} - Vrai si c'est une réservation
 */
function isReservation(arg: unknown): boolean {
  return (
    !!arg &&
    typeof arg === 'object' &&
    'id' in arg &&
    'nomClient' in arg &&
    'courrielClient' in arg &&
    'dateDebut' in arg &&
    'dateFin' in arg &&
    'typeChambre' in arg &&
    'prixParNuit' in arg
  );
}

// **** Export default **** //

export default {
  new: new_,
  from,
  isReservation,
} as const;

Étape 2 - Ajouter le modèle à la base de données bidon

repos/MockOrm.ts
import jsonfile from 'jsonfile';

import { IReservation } from '@src/models/Reservation';

// **** Variables **** //

const DB_FILE_NAME = '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);
}

// **** Export default **** //

export default {
  openDb,
  saveDb,
} as const;

Étape 3 - Mettre à jour la base de données

repos/database.json
{"reservations":[{"id":1,"nomClient":"Justin Trudeau","courrielClient":"justin@profinfo.ca","dateDebut":"2023-09-01","dateFin":"2023-09-03","typeChambre":"Deluxe","prixParNuit":150},{"id":716805071644,"nomClient":"Kamala Harris","courrielClient":"kamala@profinfo.ca","dateDebut":"2025-01-06","dateFin":"2025-01-08","typeChambre":"Deluxe","prixParNuit":150},{"id":177404267690,"nomClient":"Kamala Harris","courrielClient":"kamala@profinfo.ca","dateDebut":"2025-01-06","dateFin":"2025-01-08","typeChambre":"Deluxe","prixParNuit":150},{"id":161338412973,"nomClient":"Kamala Harris","courrielClient":"kamala@profinfo.ca","dateDebut":"2025-01-06","dateFin":"2025-01-08","typeChambre":"Deluxe","prixParNuit":150}]}

Étape 4 - Créer le repo

repos/ReservationRepo.ts
import { IReservation } from '@src/models/Reservation';
import { getRandomInt } from '@src/util/misc';
import orm from './MockOrm';

// **** Functions **** //

/**
 * Extraire une réservation.
 *
 * @param {string} courrielClient - Courriel du client
 *
 * @returns {IReservation | null} - Réservation si trouvée, sinon null.
 */
async function getOne(courrielClient: string): Promise<IReservation | null> {
  const db = await orm.openDb();
  for (const reservation of db.reservations) {
    if (reservation.courrielClient === courrielClient) {
      return reservation;
    }
  }
  return null;
}

/**
 * Vérifier si une réservation avec l'ID existe
 *
 * @param {number} id - ID de la réservation
 *
 * @returns {boolean} - Vrai si la réservation existe
 */
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.
 *
 * @returns {IReservation[]} - Tableau de toutes les réservations
 */
async function getAll(): Promise<IReservation[]> {
  const db = await orm.openDb();
  return db.reservations;
}

/**
 * Ajouter une réservation.
 *
 * @param {IReservation} reservation - Réservation à ajouter
 */
async function add(reservation: IReservation): Promise<IReservation> {
  const db = await orm.openDb();
  reservation.id = getRandomInt();
  db.reservations.push(reservation);
  orm.saveDb(db);
  return reservation;
}

/**
 * Mettre à jour une réservation
 *
 * @param {IReservation} reservation - Réservation à mettre à jour
 */
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) {
      db.reservations[i] = reservation;
      return orm.saveDb(db);
    }
  }
}

/**
 * Supprimer une réservation.
 *
 * @param {number} id - ID de la réservation à supprimer
 */
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);
    }
  }
}

// **** Export default **** //

export default {
  getOne,
  persists,
  getAll,
  add,
  update,
  delete: delete_,
} as const;

Étape 5 - Créer le service

services/ReservationService.ts
import ReservationRepo from '@src/repos/ReservationRepo';
import { IReservation } from '@src/models/Reservation';
import RouteError from '@src/common/RouteError';
import HttpStatusCodes from '@src/common/HttpStatusCodes';

// **** Variables **** //

export const RESERVATION_NOT_FOUND_ERR = 'Réservation non trouvée';

// **** Functions **** //

/**
 * Extraire toutes les réservations.
 *
 * @returns {IReservation[]} Tableau de toutes les réservations
 */
function getAll(): Promise<IReservation[]> {
  return ReservationRepo.getAll();
}

/**
 * Ajouter une réservation.
 *
 * @param {IReservation} reservation - Réservation à ajouter
 */
function addOne(reservation: IReservation): Promise<IReservation> {
  return ReservationRepo.add(reservation);
}

/**
 * Mettre à jour une réservation.
 *
 * @param {IReservation} reservation - Réservation à mettre à jour
 */
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);
  }
  // Retourner la réservation
  return ReservationRepo.update(reservation);
}

/**
 * Efface une réservation par son ID
 *
 * @param {number} id - ID de la réservation à supprimer
 */
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);
  }
  // Efface la réservation
  return ReservationRepo.delete(id);
}

// **** Export default **** //

export default {
  getAll,
  addOne,
  updateOne,
  delete: _delete,
} as const;

Étape 6 - Créer les routes

routes/ReservationRoute.ts
import HttpStatusCodes from '@src/common/HttpStatusCodes';

import ReservationService from '@src/services/ReservationService';
import { IReservation } from '@src/models/Reservation';
import { IReq, IRes } from './types/express/misc';

// **** Functions **** //

/**
 * Extraire toutes les réservations.
 *
 * @param {IReq} _ - non utilisé
 * @param {IRes} res - Réponse du serveur
 *
 * @returns {string} - Tableau des réservations en JSON
 */
async function getAll(_: IReq, res: IRes) {
  const reservations = await ReservationService.getAll();
  return res.status(HttpStatusCodes.OK).json({ reservations });
}

/**
 * Ajouter une réservation.
 *
 * @param {IReq} req - Requête au serveur avec une réservation
 * @param {IRes} res - Réponse du serveur
 */
async function add(req: IReq<{ reservation: IReservation }>, res: IRes) {
  const { reservation } = req.body;
  const returnedReservation = await ReservationService.addOne(reservation);
  return res
    .status(HttpStatusCodes.CREATED)
    .json({ reservation: returnedReservation })
    .end();
}

/**
 * Mettre à jour une réservation.
 *
 * @param {IReq} req - Requête au serveur avec une réservation
 * @param {IRes} res - Réponse du serveur
 */
async function update(req: IReq<{ reservation: IReservation }>, res: IRes) {
  const { reservation } = req.body;
  await ReservationService.updateOne(reservation);
  return res.status(HttpStatusCodes.OK).end();
}

/**
 * Supprimer une réservation.
 *
 * @param {IReq} req - Requête au serveur avec l'id d'une réservation
 * @param {IRes} res - Réponse du serveur
 */
async function delete_(req: IReq, res: IRes) {
  const id = +req.params.id;
  await ReservationService.delete(id);
  return 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

/common/Paths.ts
/**
 * Express router paths go here.
 */

export default {
  Base: '/api',
  Reservations: {
    Base: '/reservations',
    Get: '/',
    Add: '/',
    Update: '/',
    Delete: '/delete/:id',
  },
} as const;

Étape 8 - Ajouter les chemins de l’API dans index.ts

routes/index.ts
import { Router } from 'express';
import jetValidator from 'jet-validator';

import Paths from '../common/Paths';
import Reservation from '@src/models/Reservation';
import ReservationRoute from './ReservationRoute';

// **** Variables **** //

const apiRouter = Router(),
  validate = jetValidator();

// ** Add UserRouter ** //

const reservationRouter = Router();

// Extraire toutes les réservations
reservationRouter.get(Paths.Reservations.Get, ReservationRoute.getAll);

// Ajouter une réservation
reservationRouter.post(
  Paths.Reservations.Add,
  validate(['reservation', Reservation.isReservation]),
  ReservationRoute.add
);

// Mise à jour d'une réservation
reservationRouter.put(
  Paths.Reservations.Update,
  validate(['reservation', Reservation.isReservation]),
  ReservationRoute.update
);

// Supprimer une réservation
reservationRouter.delete(
  Paths.Reservations.Delete,
  validate(['id', 'number', 'params']),
  ReservationRoute.delete
);

// Ajouter le router à l'API
apiRouter.use(Paths.Reservations.Base, reservationRouter);

// **** Export default **** //

export default apiRouter;