Programmation multi-tiers avec Remix

Explication de l’architecture multi-tiers

Architecture 3-tiers

L’architecture 3-tiers est la plus répandue aujourd’hui en entreprise, un front qui va communiquer avec une ou plusieurs API qui sont sur un ou des serveurs, et qui vont elles-mêmes communiquer avec la base de données. Le désavantage principal de cette architecture, comme elle est utilisée aujourd’hui, est le nombre de chargements présents pour l’utilisateur, étant donné que la plupart des données viennent du serveur, les requêtes sont très fréquentes.

Architecture multi-tiers


L’idée de la programmation multi-tiers est d’avoir au même endroit la partie “Présentation”, donc le front, et la partie “Applicatif” soit le back. Les langages multi-tiers comme Remix vont automatiquement gérer ce qui va partir dans le client et ce qui va partir côté serveur. Pour plus d’informations, je vous conseille d’aller voir la conférence de Jordane Grenat sur la programmation multi-tiers "Développement sans Front-Tiers

Remix

Remix Stacks

Remix intègre des templates de projets pouvant choisir différentes stacks. Le projet contiendra :

  • une base de données ;
  • un pipeline de déploiement automatique pré-configuré ;
  • un système de connexion ;
  • une librairie de tests ;
  • ainsi qu’un linter, un Prettier et TypeScript pré-configuré.

A l’heure actuelle nous avons le choix entre trois stacks pour différents cas d’utilisation.

La Blues Stack : Déployée proche des utilisateurs (edge) sur un serveur Node.js et une base de données PostgreSQL. Destinée aux applications de production volumineuses et rapides desservant beaucoup d’utilisateurs.

La Indie Stack : Déployée sur un serveur Node.js et une base de données SQLite. Cette stack est idéale pour les sites avec des données dynamiques sur lesquelles nous avons la main (blogs, e-commerce …). C’est aussi une base avec très peu de complexité pour tout ce qui est MVPs, prototypes, POC, et qui offre la possibilité plus tard de migrer vers une Blues Stack facilement.

La Grunge Stack : Déployée sur une fonction serverless tournant sur Node.js avec DynamoDB. Destinée aux personnes qui souhaitent déployer une application de niveau production sur une infrastructure AWS desservant beaucoup d'utilisateurs.

Pour ma part j’ai réalisé leur tutoriel “Blog” disponible sur leur site. L’initialisation se fait sur la Indie Stack qui intègre aussi l’ORM Prisma et qui a aussi été une agréable découverte.

Loader/Action

Sur chaque route, il est possible de définir un “Loader” et une “Action”.

Loader

Le loader va être appelé avant le premier rendu pour donner les informations à la route. C’est une fonction qui est uniquement lancée sur le serveur, et qui va envoyer les données au document HTML. Sous la capuche, Remix utilise fetch, ce qui veut dire que l’on peut directement communiquer avec la base de données, utiliser les secrets API propres au serveur, etc.

Prenons un rapide exemple en utilisant l’ORM Prisma, et la fonction “json” fourni par Remix qui va servir à retourner les données reçues au bon format. Ici nous allons avoir la route Users, qui va retourner et afficher la liste de tous les utilisateurs enregistrés sur notre base de données :

import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";

import { prisma } from "../db";

export const loader = async () => {
  return json(await prisma.user.findMany());
};

export default function Users() {
  const data = useLoaderData();
  return (
    <ul>
      {data.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

Nous avons notre fonction loader, qui va demander à la base de données la liste de tous les utilisateurs, qui va être facilement récupérable par le front grâce au hook useLoaderData.

Action

Comme le loader, action est une fonction uniquement serveur, qui va permettre d'enregistrer des mutations ou d’autres actions. Les actions fonctionnent de la même manière que les loaders. La seule différence est le moment où elles sont appelées. L'avantage de ces deux fonctionnalités est d’avoir la totalité des informations liées à une route au même endroit que ce soit les données lues, l’affichage de ces données et leur modification.

Prenons l’exemple de la création d’une TodoList :

import { json, redirect } from "@remix-run/node";
import { Form } from "@remix-run/react";

import { fakeGetTodos, fakeCreateTodo } from "~/utils/db";
import { TodoList } from "~/components/TodoList";

export async function loader() {
  return json(await fakeGetTodos());
}

export async function action({ request }) {
  const body = await request.formData();
  const todo = await fakeCreateTodo({
    title: body.get("title"),
  });
  return redirect(`/todos/${todo.id}`);
}

export default function Todos() {
  const data = useLoaderData();
  return (
    <div>
      <TodoList todos={data} />
      <Form method="post">
        <input type="text" name="title" />
        <button type="submit">Create Todo</button>
      </Form>
    </div>
  );
}

Dans notre exemple, l’utilisation des actions se fait grâce au composant Form (il est aussi possible de le faire grâce au hook useSubmit) qui va aussi gérer la méthode HTTP que l’on veut utiliser, ici un POST. Lorsque le bouton est cliqué, l’action est appelée et va pouvoir utiliser les données rentrées dans le formulaire grâce au paramètre request qui lui est envoyé. Ensuite, la TodoList est créée et l'utilisateur redirigé dessus. Un autre avantage de la programmation multi-tiers est la possibilité de faire des redirections sur le front, côté serveur.

Il est possible de modifier les données de la requête comme par exemple le header, ou alors de retourner de nouvelles routes pour l’utilisateur.

Il est aussi possible de créer une route n’ayant pas d'éléments d’affichage mais simplement un loader pour faire une requête extérieure, par exemple une route logout qui contient les actions et les loaders nécessaires pour l’opération sans changer l’affichage.

Transition

Quand une action est appelée, il est possible de récupérer très facilement l’état de la requête grâce au hook useTransition. Ce hook va retourner quatre valeurs :

  • state qui va correspondre à l'état de la requête : idle quand aucune requête n’est lancée, submitting quand une requête est lancée et loading, uniquement pour les requêtes non GET, qui va indiquer le chargement de l’action. L’expérience utilisateur est très grandement et facilement améliorée grâce au contrôle de l’affichage par rapport aux états ;
  • type qui va être une version plus détaillée de state, pour les interfaces ayant besoin de plus d’informations ;
  • submission qui va permettre de récupérer les données envoyées dans l’action ;
  • location qui va contenir la destination de la prochaine page lors d’une redirection.

Routage automatique

Lors de la génération d’un projet Remix, un dossier routes est créé, et c’est ce dossier qui va gérer le routing de votre application. Lorsque l’on veut créer une route dans notre application, il suffit de créer un fichier (.js ou .tsx) dans ce dossier et la route va être automatiquement créée. Par exemple pour avoir une route sur le login (/login) il suffit d’ajouter dans ce dossier login.(js | tsx), et le contenu de ce fichier sera utilisé sur cette route. Que ce soient les éléments à rendre ou les fonctions API (loader et action) liées à cette page, tout devra se trouver dans ce fichier. Il est aussi possible de rajouter des routes dans des sous dossiers, par exemple admin/index va pointer sur /admin et admin/parameters va pointer sur /admin/parameters.


Il est aussi possible de créer des routes avec des variables, en utilisant un $ devant le nom du fichier. Par exemple, dans le tutoriel Developer Blog sur le site officiel, un fichier $slug.tsx est créé à la racine du dossier routes et quand on clique sur un article dans la liste d’articles, le slug de l’article est envoyé au composant “Link” qui va automatiquement afficher le contenu du fichier $slug.tsx.

Routes imbriquées

Il est aussi possible de faire des routes imbriquées : si nous reprenons notre exemple avec la page admin, il est possible d’avoir un fichier admin avec son interface et de rendre à un endroit précis de cette page, encadré dans une div, le contenu du dossier admi grâce au composant Remix Outlet, qui va par défaut rendre l’index du dossier. Mais il est aussi possible d’afficher d’autres pages du dossier admin à cet endroit grâce au composant Link de Remix. Voici une petite illustration pour mieux en comprendre le comportement :

Ici, il s'agit de la page admin. Dans le bloc en bas à droite, c’est ce que le composant Outlet affiche. Par défaut, le fichier index se situe dans le dossier admin. Et lorsque l'on clique sur le bouton utilisant le composant Link, c’est uniquement ce bloc qui est redirigé.

Cela nous permet d’accéder à l'onglet paramètres de la page admin sans avoir à recharger tout le reste.
L'un des intérêts de ce mode de fonctionnement est la gestion des erreurs : ici un crash sur la page admin/parameters ne ferait pas planter le reste de la page.

Prefetch

Une fonctionnalité aussi très intéressante de Remix, est de pouvoir précharger (“prefetch”) les données d’un lien, par exemple au survol de ce dernier. Il est possible de configurer dans le composant Link une règle de prefetch. Si l’on met la règle de pré-chargement des données au survol d’un lien, le temps de chargement lors du clic sur ce lien sera grandement réduit étant donné que les données auront été chargées avant que l’utilisateur ne le demande.

Mon avis sur Remix

Selon moi, Remix facilite grandement le développement d’applications web assez simples, par exemple un blog. J’ai trouvé beaucoup plus facile de créer un blog avec une page d’administration et les fonctionnalités de base sur Remix comparé à une architecture 3-tiers.

Cependant je me demande si pour des applications complexes (question que je creuserai dans un autre article), Remix ne pourrait pas être limité ? Énormément d'applications legacy en entreprise sont encore aujourd’hui en PHP, un langage qui présente plusieurs points communs avec Remix, ce qui me pousse à me poser des questions sur la véracité de Remix en entreprise.

Remix est encore assez jeune et nous réserve sûrement encore plein de surprises !

Personnellement j’ai adoré suivre leur tutoriel “Developer Blog” qui m’a fait beaucoup apprécier cette méthode de développement et Remix risque de prendre une place importante dans le choix de stack de mes side projects. Je conseille d’ailleurs à tout le monde de suivre ce rapide tutoriel !