Comment créer une API avec plusieurs actions dans Remix ?
Il y a quelques semaines, j'ai découvert un nouveau concept Typescript en lisant un article intéressant sur les Discriminated Union Types (merci encore Dimitri 😉).
Il m'a permis d'améliorer le code de mes applications Remix. Notamment sur la partie serveur, avec les actions.
Pourquoi utiliser le Discriminated Union Type ?
Je l'utilise parce qu'il me permet d'écrire une logique une seule fois. Au lieu de devoir écrire plusieurs fois la même logique de validation, pour des actions mineures comme la suppression, l'édition et la création d'une donnée, toute cette logique se retrouve dans le même fichier (et non pas dans 3-4 fichiers différents).
Comment créer une route API
Pour mieux comprendre le routing dans Remix, je vous conseille l'article sur les 6 types de routes à connaître dans Remix.
Il nous suffit de créer un fichier dans le dossier app/routes
, que nous allons nommer et qui prendra l'extension tsx
. Prenons par exemple une route que nous allons appeler actions
, qui sera accessible à l'URL http://localhost:3000/actions
.
Ensuite, nous avons besoin d'exporter une méthode nommée action (qui contient la logique serveur) :
La fonction action sera exécutée à chaque requête API de type POST, PUT, DELETE ... Toutes les méthodes HTTP sauf le GET.
Maintenant, nous allons rajouter un peu de logique, avec plusieurs actions possibles.
Comment déterminer l'action à exécuter ?
Dans cet exemple, nous souhaitons appeler cette route pour ajouter ou supprimer un utilisateur. Nous avons donc 2 mutations (ou actions) différentes dans la même méthode action.
Pour faire simple, nous allons soumettre une valeur dans le formulaire qui va changer en fonction de l'action à effectuer. Nous allons ensuite utiliser un switch pour déterminer laquelle des deux actions doit être exécutée.
C'est la valeur de la variable intent qui détermine quelle logique exécuter.
Cependant, c'est le composant React côté client qui va déterminer la valeur de la constante nommée intent. (Nous allons supprimer la déclaration de la variable intent dans la méthode action).
Ajoutons la logique côté client, en exportant par défaut un composant React (je te conseille cet article dans lequel on implémente un formulaire full typesafe avec Remix :
Mais je comprend pas, il est où ton Discriminated Union Type ?
Effectivement, nous y venons. Nous avons codé toute la logique serveur et client pour nous permettre d'effectuer deux mutations au sein du même composant : créer et supprimer un utilisateur.
Mais nous n'utilisons pas encore de Discriminated Union Type. On pourrait définir des types avec Typescript directement dans notre action, mais ça ne serait pas vraiment typesafe. Pour être sûr d'avoir des données typées, nous avons besoin d'utiliser la librairie Zod pour valider les données de formulaire. Je vous recommende également la librairie Conform pour améliorer l'expérience de vos utilisateurs.
Pour maîtriser deux ces librairies avec Remix, regardez cette vidéo
Utiliser le Discriminated Union Type avec Zod et Conform
Nous avons besoin d'installer trois librairies pour avoir des données typées (grâce à Zod), discriminer la mutation (grâce à Typescript) et améliorer l'UX du formulaire (grâce à Conform).
_10npm install zod @conform-to/zod @conform-to/react
Ensuite, nous devons définir deux schémas Zod :
- Un schéma pour créer l'utilisateur (CreateAction)
- Un schéma pour supprimer l'utilisateur (DeleteAction)
Chacun de ces schémas possède une clé en commun : la propriété intent, qui nous permettra de discriminer le schéma (savoir lequel des deux représente la donnée soumise par le formulaire)
Le code ressemble maintenant à ça :
Ce qu'il faut regarder, c'est la déclaration du schéma Actions :
const Actions = z.union([DeleteAction, CreateAction]);
Nous utilisons z.union, pour définir nos deux actions différentes en informant Zod qu'elles pourront être discriminées par la propriété intent.
Maintenant, si l'utilisateur soumet le formulaire avec l'intention de créer un utilisateur, Zod va valider les propriétés firstname, lastname et intent="create".
Si l'utilisateur a l'intention de supprimer l'utilisateur, Zod va valider la propriété intent="delete", et ne va pas causer d'erreur si les propriétés firstname et lastname sont absentes.
Qu'en as-tu pensé ? N'hésite pas à donner ton avis sur LinkedIn ou la chaîne YouTube.