Le SGBDR

On veut maintenant établir une connexion à une base de données, et commencer à ajouter et supprimer les lignes au travers de notre API.

Dev Container et docker-compose.yml

On peut directement inclure une instance d'un SGBDR à notre dev-container grâce à Docker :

docker-compose.dev.yml
  ...

  dbms:
    image: mariadb
    restart: always
    ports:
      - "3309:3306"
    environment: 
      - MYSQL_ALLOW_EMPTY_PASSWORD=false
      - MYSQL_ROOT_PASSWORD=rootpassword
    command: [
      "--character-set-server=utf8mb4",
      "--collation-server=utf8mb4_unicode_ci",
    ]
    volumes:
      - ./.data:/var/lib/mysql
    networks:
      - api-network

Il faut faire le suivant :

  • Ajouter les lignes dessus à docker-compose.dev.yml sous l'option services.

  • Ajouter le dossier dbms

  • Rebuilder votre DevContainer dans VSCode.

  • Le SGBDR est désormais disponible.

Docker gère automatiquement les connexions et ports pour nous :

  • Le nom d'hôte est le nom du service (dbms)

  • Le port est automatiquement mappé par docker, pas besoin de le préciser dans notre code

  • Pour plus facilement identifier notre container dans Docker, on précise un nom avec container_name

Dans le terminal VSCode, vous pouvez connecter à votre SGBDR avec :

Ou bien, d'un terminal en dehors de VSCode (si vous êtes sur un serveur par exemple):

Ensuite, vous pouvez créer votre base de données, l'utilisateur pour notre api, et créer les premières tables :

Intégration NodeJS

Nous utilisons la librairie mysql2arrow-up-right pour communiquer avec notre base de données.

Normalement, notre API va ouvrir une connexion unique auprès du SGBDR pour chaque requête en cours. Ceci peut être lourd et chronophage, donc le créateur de la librairie a prévu les connection pools. C'est-à-dire, on va essayer de réutiliser les connexions déjà ouvertes.

Moi, je préfère créer une classe qui enveloppe l'objet principal, pour ne pas répéter du code (dans src/utility/ORM/DB.ts):

Ici, on crée une variable static, et on initialise notre pool avec les coordonnées tirées de l'environnement (ou des valeurs par défaut).

Opérations CRUD

Voici un exemple d'un set de endpoints pour la gestion de l'utilisateur :

Le formatage des requêtes SQL

Comme toutes les librairies, on évite l'injection SQL en utilisant des fonctionnalités pour échapper les données :

Pour les inserts, il est pratique de passer plutôt un objet, et laisser la librairie formuler la requête :

Accrocher les routes à la hiérarchie

On compose notre application principale par les routes qu'on vient de créer :

Notez bien la ligne app.use('/user', ROUTES_USER);.

circle-check

Middleware

Il arrive que l'on veuille répéter une certaine logique dans un certain nombre routes différents.

Par exemple, l'authentification d'un utilisateur avant l'exécution d'une opération.

Pour ce faire, nous utilisons un "middleware", une fonction intermédiaire qui est appelée avant le endpoint final.

Ajoutez le suivant au debut de notre fichier user.route.ts :

Cette fonction sera appelé systématiquement avant tous les endpoints de ce router !

Middlewares doivent obligatoirement signaler quand ils ont finis :

  • en invoquant la fonction next(), sans paramètre quand tout s'est bien passé

  • en invoquant la fonction next('message'), avec un paramètre, quand il y a une erreur. Le paramètre contient de l'information sur l'erreur

Si on oublie d'appeler next() notre serveur va bloquer dans cette fonction et ne jamais avancer.

circle-check

Mis à jour