Archives Wild Code School
JS Monorepo

07 - JS Monorepo : Mettre en place un CRUD / BREAD 🍞

QuĂȘte 8 sur 9 · 1 image archivĂ©e

Objectifs

  • Apprendre les conventions et bonnes pratiques REST
  • Mettre en place un CRUD / BREAD complet
  • Communiquer avec une application serveur depuis une application cliente
  • Étudier et utiliser un code existant

Pré-requis

  1. 1

    Valider les quĂȘtes suivantes

  2. 2

    Identifier les informations principales d'une requĂȘte HTTP

    Exemple de requĂȘte HTTP :

    POST /categories
    Host: foo.com
    Content-Type: application/json
    Content-Length: 23
    
    { "title": "Aventure" }
    

    Les informations principales :

    MĂ©thode HTTP Chemin d'URL Corps de la requĂȘte
    POST /categories { "title": "Aventure" }
  3. 3

    Identifier les informations principales d'une réponse HTTP

    Exemple de réponse HTTP :

    HTTP/1.1 201 Created
    ETag: W/"xyzzy"
    Content-Type: application/json
    Content-Length: 11
    Cache-Control: no-cache
    
    { "id": 3 }
    

    Les informations principales :

    Statut de la réponse Corps de la réponse
    201 Created { "id": 3 }

Sommaire

Introduction

Dans les quĂȘtes prĂ©cĂ©dentes, tu as mis en place une application serveur, et appris Ă  communiquer avec une base de donnĂ©es.

Dans cette quĂȘte, tu vas mettre en place un CRUD (Create, Read, Update, Delete) complet, aussi appelĂ© BREAD (Browse, Read, Edit, Add, Destroy), pour gĂ©rer les donnĂ©es de ton application. Tu vas Ă©galement apprendre Ă  communiquer avec une application serveur depuis une application cliente en suivant les principes REST.

Qu'est-ce que REST ?

REST (REpresentational State Transfer) est une architecture qui repose sur un ensemble de contraintes pour construire des services web. Les services RESTful utilisent les protocoles HTTP pour effectuer des opérations sur les ressources d'un serveur web.

Les bonnes pratiques REST :

  • Utiliser des noms de ressources : Les URL doivent reprĂ©senter des ressources. Par exemple, pour une ressource de type "article", l'URL pourrait ĂȘtre /articles pour l'ensemble des articles et /articles/:id pour un article spĂ©cifique.
  • Utiliser les mĂ©thodes HTTP de maniĂšre appropriĂ©e : GET pour rĂ©cupĂ©rer des ressources, POST pour crĂ©er des ressources, PUT pour mettre Ă  jour des ressources, et DELETE pour supprimer des ressources.
  • Statuts de rĂ©ponse HTTP : Utiliser les codes de statut HTTP pour indiquer le rĂ©sultat des opĂ©rations (200 pour OK, 201 pour Created, 204 pour No Content, etc.).
  • Utiliser des formats standardisĂ©s : JSON est couramment utilisĂ© pour reprĂ©senter les donnĂ©es des requĂȘtes et des rĂ©ponses.

Les opérations CRUD / BREAD

Les opĂ©rations CRUD (Create, Read, Update, Delete) correspondent aux actions de base que tu peux effectuer sur les donnĂ©es d'une application. Dans le cadre de cette quĂȘte, nous allons Ă©tendre ce concept avec BREAD (Browse, Read, Edit, Add, Destroy), qui est une version plus parlante des mĂȘmes opĂ©rations.

C'est un moyen mnémotechnique de retenir les opérations les plus courantes : une "checklist" à vérifier pour chaque ressource. Chaque ressource est identifiée par une URL et les opérations sur ces ressources sont effectuées en utilisant les méthodes HTTP.

Table de correspondance entre BREAD et HTTP (et SQL)

Pour une ressource "item" :

Nom de l'opĂ©ration MĂ©thode HTTP Chemin d'URL Corps de la requĂȘte OpĂ©ration SQL Statut de la rĂ©ponse Corps de la rĂ©ponse
Browse GET /items (rien) SELECT 200 OK Liste des ressources
Read GET /items/:id (rien) SELECT 200 OK Détails de la ressource
Add (Create) POST /items Données de la nouvelle ressource INSERT 201 Created Id d'insertion *
Edit (Update) PUT /items/:id Données mises à jour UPDATE 204 No Content (rien)
Destroy (Delete) DELETE /items/:id (rien) DELETE 204 No Content (rien)

* La recommandation pour la création de ressources est de ne rien retourner dans le corps de la réponse, et d'utiliser un header "Location". Cette pratique amÚne une complexité supplémentaire. Retourner l'id d'insertion dans le corps de la réponse est une alternative que tu utiliseras pendant ta formation.

Ces détails sont repris sur la page Using HTTP Methods for RESTful Services.

Pour aller plus loin sur REST, tu peux aussi consulter le site https://restfulapi.net/.

Avant d'aller plus loin

Comment utiliser cette quĂȘte ?

Tu vas récupérer un dépÎt avec un CRUD / BREAD complet (serveur et client) sur les catégories :

  • Lister les catĂ©gories (Browse)
  • Voir les dĂ©tails d'une catĂ©gorie (Read)
  • Modifier une catĂ©gorie (Edit)
  • CrĂ©er une nouvelle catĂ©gorie (Add)
  • Supprimer une catĂ©gorie (Destroy)

Tu peux suivre cette quĂȘte pour construire pas Ă  pas chaque fonctionnalitĂ© en t'appuyant sur le code du dĂ©pĂŽt fourni en exemple.

Si tu préfÚres expérimenter de ton cÎté ou explorer le code du dépÎt fourni en autonomie, tu peux prendre comme référence la Table de correspondance entre BREAD et HTTP (et SQL) pour implémenter ton propre BREAD des catégories.

Attention

Attention : une Ă©tape indispensable pour faire communiquer ton client et ton serveur est d'activer le parsing du JSON dans la requĂȘte. Si tu ne suis pas toutes les Ă©tapes de la quĂȘte, lis attentivement les commentaires dans server/src/app.ts pour avoir des indications sur ce que tu dois faire.

Le dĂ©pĂŽt fourni pour cette quĂȘte

Pour cette quĂȘte, un dĂ©pĂŽt de dĂ©monstration est disponible : il contient une application serveur prĂȘte Ă  l'emploi. Ce dĂ©pĂŽt contient Ă©galement le code client qui interagira avec le serveur. Pour le cloner, ouvre un terminal puis exĂ©cute la commande :

bash
git clone git@github.com:WildCodeSchool/quest-js-wild-series.git

Installe ensuite les dépendances du projet :

bash
cd quest-js-wild-series
npm install

Créé les fichiers server/.env et client/.env en copiant et en adaptant server/.env.sample et client/.env.sample. Mets en place la base de données en exécutant la commande :

bash
npm run db:migrate

Tu peux maintenant démarrer les applications avec la commande npm run dev et tester l'application client en ouvrant la page http://localhost:3000.

B, R, E, A, D

Chacune des parties suivantes dĂ©taille l'implĂ©mentation d'une mĂ©thode. Le schĂ©ma est toujours le mĂȘme :

  • CĂŽtĂ© client :
    • Fetch : l'appel qui envoie une requĂȘte au serveur
  • CĂŽtĂ© serveur :
    • Action : l'action dĂ©clenchĂ©e cĂŽtĂ© serveur.
    • Repository : la mĂ©thode utilisĂ©e pour appliquer l'action dans la base de donnĂ©es.
Attention

Plusieurs des requĂȘtes HTTP dans ce dĂ©pĂŽt utilisent des options avancĂ©es de fetch. Par exemple, une requĂȘte PUT ressemble Ă  ceci :

typescript
const category = { id: 1, name: "Comédie musicale" };

fetch(`${import.meta.env.VITE_API_URL}/api/categories/${someId}`, {
  method: "put",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify(category),
});

Décryptage :

  • import.meta.env.VITE_API_URL : c'est l'URL de base de ton serveur, indiquĂ©e dans client/.env. Elle est suivie du chemin d'URL Ă  requĂȘter : /api/categories/${someId}.
  • method: "put" : le verbe HTTP qui complĂšte la requĂȘte, ici PUT.
  • headers: { "Content-Type": "application/json" } : les en-tĂȘtes de la requĂȘte, ici pour prĂ©ciser que le type du contenu, le corps (body) contient des donnĂ©es au format JSON.
  • body: JSON.stringify(category) : le corps de la requĂȘte, qui doit impĂ©rativement ĂȘtre une chaine de caractĂšres (au format JSON, puisque c'est le format que nous avons fixĂ©).

L'opération Add inclue des détails supplémentaires sur l'activation du parsing du JSON dans server/src/app.ts : tu devrais commencer par cette opération. Libre à toi ensuite de piocher l'opération BREAD que tu veux dérouler.

Browse

Nom de l'opĂ©ration MĂ©thode HTTP Chemin d'URL Corps de la requĂȘte OpĂ©ration SQL Statut de la rĂ©ponse Corps de la rĂ©ponse
Browse GET /categories (rien) SELECT 200 OK Liste des catégories
  1. 1

    Fetch

    Dans client/src/pages/CategoryIndex.tsx :

    typescript
    import { useEffect, useState } from "react";
    import { Link } from "react-router-dom";
    
    type Category = {
      id: number;
      name: string;
    };
    
    function CategoryIndex() {
      const [categories, setCategories] = useState([] as Category[]);
    
      useEffect(() => {
        fetch(`${import.meta.env.VITE_API_URL}/api/categories`)
          .then((response) => response.json())
          .then((data: Category[]) => {
            setCategories(data);
          });
      }, []);
    
      return (
        <>
          <Link to={"/categories/new"}>Ajouter</Link>
          <ul>
            {categories.map((category) => (
              <li key={category.id}>
                <Link to={`/categories/${category.id}`}>{category.name}</Link>
              </li>
            ))}
          </ul>
        </>
      );
    }
    
    export default CategoryIndex;
    
  2. 2

    RequĂȘte envoyĂ©e, en attente d'une rĂ©ponse

    react et express dans l'architecture client serveur du web

  3. 3

    Action

    Dans server/src/modules/category/categoryActions.ts :

    typescript
    const browse: RequestHandler = async (req, res, next) => {
      try {
        // Fetch all categories
        const categories = await categoryRepository.readAll();
    
        // Respond with the categories in JSON format
        res.json(categories);
      } catch (err) {
        // Pass any errors to the error-handling middleware
        next(err);
      }
    };
    
  4. 4

    Repository

    Dans server/src/modules/category/categoryRepository.ts :

    typescript
    async readAll() {
      // Execute the SQL SELECT query to retrieve all categories from the "category" table
      const [rows] = await databaseClient.query<Rows>("select * from category");
    
      // Return the array of categories
      return rows as Category[];
    }
    

Read

Nom de l'opĂ©ration MĂ©thode HTTP Chemin d'URL Corps de la requĂȘte OpĂ©ration SQL Statut de la rĂ©ponse Corps de la rĂ©ponse
Read GET /categories/:id (rien) SELECT 200 OK Détails de la catégorie
  1. 1

    Fetch

    Dans client/src/pages/CategoryDetails.tsx :

    typescript
    import { useEffect, useState } from "react";
    import { Link, useParams } from "react-router-dom";
    
    import CategoryDeleteForm from "../components/CategoryDeleteForm";
    
    type Program = {
      id: number;
      title: string;
    };
    
    type Category = {
      id: number;
      name: string;
      programs: Program[];
    };
    
    function CategoryDetails() {
      const { id } = useParams();
      const [category, setCategory] = useState(null as null | Category);
    
      useEffect(() => {
        fetch(`${import.meta.env.VITE_API_URL}/api/categories/${id}`)
          .then((response) => response.json())
          .then((data: Category) => {
            setCategory(data);
          });
      }, [id]);
    
      return (
        category && (
          <>
            <hgroup className="details-hgroup">
              <h1>{category.name}</h1>
              <Link to={`/categories/${category.id}/edit`}>Modifier</Link>
              <CategoryDeleteForm id={category.id}>Supprimer</CategoryDeleteForm>
            </hgroup>
            <ul>
              {category.programs.map((program) => (
                <li key={program.id}>
                  <Link to={`/programs/${program.id}`}>{program.title}</Link>
                </li>
              ))}
            </ul>
          </>
        )
      );
    }
    
    export default CategoryDetails;
    
  2. 2

    RequĂȘte envoyĂ©e, en attente d'une rĂ©ponse

    react et express dans l'architecture client serveur du web

  3. 3

    Action

    Dans server/src/modules/category/categoryActions.ts :

    typescript
    const read: RequestHandler = async (req, res, next) => {
      try {
        // Fetch a specific category based on the provided ID
        const categoryId = Number(req.params.id);
        const category = await categoryRepository.read(categoryId);
    
        // If the category is not found, respond with HTTP 404 (Not Found)
        // Otherwise, respond with the category in JSON format
        if (category == null) {
          res.sendStatus(404);
        } else {
          res.json(category);
        }
      } catch (err) {
        // Pass any errors to the error-handling middleware
        next(err);
      }
    };
    
  4. 4

    Repository

    Dans server/src/modules/category/categoryRepository.ts :

    typescript
    async read(id: number) {
      // Execute the SQL SELECT query to retrieve a specific category by its ID
      const [rows] = await databaseClient.query<Rows>(
          `
          select 
            category.*, 
            JSON_ARRAYAGG(
              JSON_OBJECT(
                "id", program.id, "title", program.title
              )
            ) as programs 
          from 
            category 
            left join program on program.category_id = category.id 
          where 
            category.id = ? 
          group by 
            category.id
          `,
        [id],
      );
    
      // Return the first row of the result, which represents the category
      return rows[0] as Category;
    }
    
    Important

    Quelle est cette diablerie ?!? Qu'est devenu le select * from category where id = ? ?

    Rassure-toi : tu auras rarement besoin d'une requĂȘte aussi complexe. Nous avons volontairement provoquĂ© le cas pour que tu le rencontres pendant cette quĂȘte.

    Sur la page détaillée d'une ressource, tu peux vouloir afficher des ressources associées par une clé étrangÚre. Ici par exemple, le code SQL permet de récupérer une catégorie avec toutes ses séries.

    C'est le rĂŽle d'une jointure SQL : rĂ©cupĂ©rer des donnĂ©es Ă  travers plusieurs tables avec une seule requĂȘte. Une premiĂšre version serait (avec un left join, pour rĂ©cupĂ©rer toutes les catĂ©gories, mĂȘme celles qui n'ont pas encore de sĂ©ries associĂ©es) :

    sql
    select 
      category.*, 
      program.id as program_id, 
      program.title 
    from 
      category 
      left join program on program.category_id = category.id;
    

    Tous les champs de la table category sont sélectionnés avec category.*. L'id de la table program est renommé avec l'alias program_id pour éviter une collision de nom avec l'id de la table category.

    Si une catĂ©gorie est associĂ©e Ă  plusieurs sĂ©ries, la requĂȘte produira un tableau "applati" avec les donnĂ©es de la catĂ©gorie duppliquĂ©es comme dans cet exemple :

    json
    [
      { "id": 1, "name": "Comédie", "program_id": 1, "title": "The Good Place" },
      { "id": 1, "name": "Comédie", "program_id": 3, "title": "The office" },
      { "id": 2, "name": "Science-Fiction", "program_id": 2, "title": "Dark" }
    ]
    

    Tu peux produire une version beaucoup mieux structurée pour ton client en regroupant les séries par catégories. Quelque chose comme ça :

    json
    [
      {
        "id": 1,
        "name": "Comédie",
        "programs": [
          { "id": 1, "title": "The Good Place" },
          { "id": 3, "title": "The office" }
        ]
      },
      {
        "id": 2,
        "name": "Science-Fiction",
        "programs": [{ "id": 2, "title": "Dark" }]
      }
    ]
    

    C'est ce que te permet l'option group by en SQL (la requĂȘte suivante ne marche pas) :

    sql
    select 
      category.* 
      program.id as program_id, 
      program.title 
    from 
      category 
      left join program on program.category_id = category.id 
    group by 
      category.id
    

    Mais dans ce regroupement, tu obtiens plusieurs sĂ©ries Ă  "faire tenir sur la mĂȘme ligne" pour chaque catĂ©gorie. C'est lĂ  que tu dois expliquer en SQL comment construire une agrĂ©gation. MySQL fournit des fonctions pour rĂ©unir plusieurs champs en un objet JSON puis plusieurs objets en un tableau : JSON_OBJECT et JSON_ARRAYAGG. Avec toutes les piĂšces du puzzle, tu obtiens :

    sql
    select 
      category.*, 
      JSON_ARRAYAGG(
        JSON_OBJECT(
          "id", program.id, "title", program.title
        )
      ) as programs 
    from 
      category 
      left join program on program.category_id = category.id 
    group by 
      category.id
    

Edit

Nom de l'opĂ©ration MĂ©thode HTTP Chemin d'URL Corps de la requĂȘte OpĂ©ration SQL Statut de la rĂ©ponse Corps de la rĂ©ponse
Edit (Update) PUT /categories/:id Données mises à jour UPDATE 204 No Content (rien)
  1. 1

    Fetch

    Dans client/src/pages/CategoryEdit.tsx :

    typescript
    import { useEffect, useState } from "react";
    import { useNavigate, useParams } from "react-router-dom";
    
    import CategoryForm from "../components/CategoryForm";
    
    type Category = {
      id: number;
      name: string;
    };
    
    function CategoryEdit() {
      // ...
    
      return (
        category && (
          <CategoryForm
            defaultValue={category}
            onSubmit={(categoryData) => {
              fetch(
                `${import.meta.env.VITE_API_URL}/api/categories/${category.id}`,
                {
                  method: "put",
                  headers: {
                    "Content-Type": "application/json",
                  },
                  body: JSON.stringify(categoryData),
                },
              ).then((response) => {
                if (response.status === 204) {
                  navigate(`/categories/${category.id}`);
                }
              });
            }}
          >
            Modifier
          </CategoryForm>
        )
      );
    }
    
    export default CategoryEdit;
    
  2. 2

    RequĂȘte envoyĂ©e, en attente d'une rĂ©ponse

    react et express dans l'architecture client serveur du web

  3. 3

    Activer le parsing de la requĂȘte

    Le parsing des requĂȘtes est nĂ©cessaire pour extraire les donnĂ©es envoyĂ©es par le client dans une requĂȘte HTTP. C'est le cas ici pour accĂ©der au corps de ta requĂȘte PUT.

    Dans server/src/app.ts, le code contient des commentaires pour activer différentes maniÚres d'extraire des données :

    • express.json() : analyse les requĂȘtes avec des donnĂ©es JSON.
    • express.urlencoded() : analyse les requĂȘtes avec des donnĂ©es codĂ©es en URL.
    • express.text() : analyse les requĂȘtes avec des donnĂ©es texte brutes.
    • express.raw() : analyse les requĂȘtes avec des donnĂ©es binaires brutes.

    Si ce n'est pas déjà fait, décommente une ou plusieurs de ces options selon le format des données envoyées par ton client :

    diff
    -// app.use(express.json());
    +app.use(express.json());
    // app.use(express.urlencoded());
    // app.use(express.text());
    // app.use(express.raw());
    
  4. 4

    Action

    Dans server/src/modules/category/categoryActions.ts :

    typescript
    const edit: RequestHandler = async (req, res, next) => {
      try {
        // Update a specific category based on the provided ID
        const category = {
          id: Number(req.params.id),
          name: req.body.name,
        };
    
        const affectedRows = await categoryRepository.update(category);
    
        // If the category is not found, respond with HTTP 404 (Not Found)
        // Otherwise, respond with the category in JSON format
        if (affectedRows === 0) {
          res.sendStatus(404);
        } else {
          res.sendStatus(204);
        }
      } catch (err) {
        // Pass any errors to the error-handling middleware
        next(err);
      }
    };
    
  5. 5

    Repository

    Dans server/src/modules/category/categoryRepository.ts :

    typescript
    async update(category: Category) {
      // Execute the SQL UPDATE query to update an existing category in the "category" table
      const [result] = await databaseClient.query<Result>(
        "update category set name = ? where id = ?",
        [category.name, category.id],
      );
    
      // Return how many rows were affected
      return result.affectedRows;
    }
    

    Note le <Result> derriĂšre databaseClient.query : il est en phase la requĂȘte UPDATE qui produit un rĂ©sultat (dont le nombre de lignes affectĂ©es : result.affectedRows) et pas des "rows" comme une requĂȘte SELECT.

Add

Nom de l'opĂ©ration MĂ©thode HTTP Chemin d'URL Corps de la requĂȘte OpĂ©ration SQL Statut de la rĂ©ponse Corps de la rĂ©ponse
Add (Create) POST /categories Données de la nouvelle catégorie INSERT 201 Created Id d'insertion
  1. 1

    Fetch

    Dans client/src/pages/CategoryNew.tsx :

    typescript
    import { useNavigate } from "react-router-dom";
    
    import CategoryForm from "../components/CategoryForm";
    
    function CategoryNew() {
      const navigate = useNavigate();
    
      const newCategory = {
        name: "",
      };
    
      return (
        <CategoryForm
          defaultValue={newCategory}
          onSubmit={(categoryData) => {
            fetch(`${import.meta.env.VITE_API_URL}/api/categories`, {
              method: "post",
              headers: {
                "Content-Type": "application/json",
              },
              body: JSON.stringify(categoryData),
            })
              .then((response) => response.json())
              .then((data) => {
                navigate(`/categories/${data.insertId}`);
              });
          }}
        >
          Ajouter
        </CategoryForm>
      );
    }
    
    export default CategoryNew;
    
  2. 2

    RequĂȘte envoyĂ©e, en attente d'une rĂ©ponse

    react et express dans l'architecture client serveur du web

  3. 3

    Activer le parsing de la requĂȘte

    Le parsing des requĂȘtes est nĂ©cessaire pour extraire les donnĂ©es envoyĂ©es par le client dans une requĂȘte HTTP. C'est le cas ici pour accĂ©der au corps de ta requĂȘte POST.

    Dans server/src/app.ts, le code contient des commentaires pour activer différentes maniÚres d'extraire des données :

    • express.json() : analyse les requĂȘtes avec des donnĂ©es JSON.
    • express.urlencoded() : analyse les requĂȘtes avec des donnĂ©es codĂ©es en URL.
    • express.text() : analyse les requĂȘtes avec des donnĂ©es texte brutes.
    • express.raw() : analyse les requĂȘtes avec des donnĂ©es binaires brutes.

    Si ce n'est pas déjà fait, décommente une ou plusieurs de ces options selon le format des données envoyées par ton client :

    diff
    -// app.use(express.json());
    +app.use(express.json());
    // app.use(express.urlencoded());
    // app.use(express.text());
    // app.use(express.raw());
    
  4. 4

    Action

    Dans server/src/modules/category/categoryActions.ts :

    typescript
    const add: RequestHandler = async (req, res, next) => {
      try {
        // Extract the category data from the request body
        const newCategory = {
          name: req.body.name,
        };
    
        // Create the category
        const insertId = await categoryRepository.create(newCategory);
    
        // Respond with HTTP 201 (Created) and the ID of the newly inserted item
        res.status(201).json({ insertId });
      } catch (err) {
        // Pass any errors to the error-handling middleware
        next(err);
      }
    };
    
  5. 5

    Repository

    Dans server/src/modules/category/categoryRepository.ts :

    typescript
    async create(category: Omit<Category, "id">) {
      // Execute the SQL INSERT query to add a new category to the "category" table
      const [result] = await databaseClient.query<Result>(
        "insert into category (name) values (?)",
        [category.name],
      );
    
      // Return the ID of the newly inserted item
      return result.insertId;
    }
    

    Note le <Result> derriĂšre databaseClient.query : il est en phase la requĂȘte UPDATE qui produit un rĂ©sultat (dont l'id gĂ©nĂ©rĂ© lors de l'insertion : result.insertId) et pas des "rows" comme une requĂȘte SELECT.

Destroy

Nom de l'opĂ©ration MĂ©thode HTTP Chemin d'URL Corps de la requĂȘte OpĂ©ration SQL Statut de la rĂ©ponse Corps de la rĂ©ponse
Destroy (Delete) DELETE /categories/:id (rien) DELETE 204 No Content (rien)
  1. 1

    Fetch

    Dans client/src/components/CategoryDeleteForm.tsx :

    typescript
    import type { ReactNode } from "react";
    import { useNavigate } from "react-router-dom";
    
    type CategoryDeleteFormProps = {
      id: number;
      children: ReactNode;
    };
    
    function CategoryDeleteForm({ id, children }: CategoryDeleteFormProps) {
      const navigate = useNavigate();
    
      return (
        <form
          onSubmit={(event) => {
            event.preventDefault();
    
            fetch(`${import.meta.env.VITE_API_URL}/api/categories/${id}`, {
              method: "delete",
            }).then((response) => {
              if (response.status === 204) {
                navigate("/categories");
              }
            });
          }}
        >
          <button type="submit">{children}</button>
        </form>
      );
    }
    
    export default CategoryDeleteForm;
    
  2. 2

    RequĂȘte envoyĂ©e, en attente d'une rĂ©ponse

    react et express dans l'architecture client serveur du web

  3. 3

    Action

    Dans server/src/modules/category/categoryActions.ts :

    typescript
    const destroy: RequestHandler = async (req, res, next) => {
      try {
        // Delete a specific category based on the provided ID
        const categoryId = Number(req.params.id);
    
        await categoryRepository.delete(categoryId);
    
        // Respond with HTTP 204 (No Content) anyway
        res.sendStatus(204);
      } catch (err) {
        // Pass any errors to the error-handling middleware
        next(err);
      }
    };
    
  4. 4

    Repository

    Dans server/src/modules/category/categoryRepository.ts :

    typescript
    async delete(id: number) {
      // Execute the SQL DELETE query to delete an existing category from the "category" table
      const [result] = await databaseClient.query<Result>(
        "delete from category where id = ?",
        [id],
      );
    
      // Return how many rows were affected
      return result.affectedRows;
    }
    

    Note le <Result> derriĂšre databaseClient.query : il est en phase la requĂȘte UPDATE qui produit un rĂ©sultat (dont le nombre de lignes affectĂ©es : result.affectedRows) et pas des "rows" comme une requĂȘte SELECT.

Récapitulatif

À ce stade, nous avons un CRUD / BREAD complet pour gĂ©rer les catĂ©gories de notre application. Tu as appris les conventions REST, activĂ© le parsing de JSON, et vu l'implĂ©mentation de chaque opĂ©ration avec une communication entre le client et le serveur.

Voici un récapitulatif des opérations effectuées :

  • Browse : RĂ©cupĂ©rer et afficher la liste des ressources.
  • Read : Lire et afficher les dĂ©tails d'une ressource spĂ©cifique.
  • Edit : Modifier une ressource existante.
  • Add : Ajouter une nouvelle ressource.
  • Destroy : Supprimer une ressource existante.

En t'appuyant sur les exemples fournis, tu devrais ĂȘtre capable de mettre en place un CRUD / BREAD fonctionnel pour gĂ©rer chaque ressource de ton application.

Challenge

ImplĂ©mente les mĂȘmes fonctionnalitĂ©s BREAD pour une autre ressource de ton projet Wild Series : les programs. Assure-toi de bien suivre les conventions REST et d'appliquer les bonnes pratiques vues dans cette quĂȘte.