Nous testons l'intégration de Remix et NestJS.
Le but de cette stack est de pouvoir bénéficier de la puissance de Remix.js tout en utilisant l'architecture modulaire de NestJS. La logique métier sera contenu dans les services de NestJS. Ce que nous voulons faire, c'est vérifier que l'intégration entre les deux projets fonctionne correctement. Pour ce faire, nous allons créer une route Remix qui va appeler une méthode de service NestJS.
Pour créer une route, il nous suffit d'ajouter un fichier dans le dossier frontend/app/routes
(ce dossier contient toutes les routes de notre application Remix).
Nous l'avions vu dans l'article 6 Routes à connaître si tu utilises Remix.
Pour créer une route, il nous suffit d'ajouter un fichier dans le dossier app/routes
, qui exporte une méthode loader (pour créer une API) ou un composant React par défaut (pour créer une vue).
1import { json } from '@remix-run/node';2export const loader = () => {3return json({4status: 'ok',5});6};
Nous venons de créer une route API, communément appelée Ressource Route. Elle ne renvoie pas une vue car aucun composant React n'a été exporté par défaut. Mais elle renvoie une réponse JSON.
On peut lancer le projet à la racine avec la commande npm run dev
, et naviguer à l'URL localhost:3000/api
pour voir le résultat.
1{2"status": "ok"3}
Victoire ! Notre réponse JSON s'affiche bien. Essayons maintenant d'afficher une vue en créant un composant React dans le même fichier.
1import { json } from '@remix-run/node';2export const loader = () => {3return json({4status: 'ok',5});6};78export default function Page() {9return <h1>Hello</h1>;10}
Actualisons la page. Nous devrions voir le texte Hello
s'afficher.
Deuxième victoire ! Notre route Remix fonctionne correctement. Nous allons maintenant importer un service NestJS dans notre application Remix.
Il est temps de créer notre premier service NestJS. Il ne va pas contenir de logique métier à proprement parler, il nous permettra d'importer tous les autres services de notre application, qui eux la contiendront.
Créons un nouveau fichier remix.service.ts
dans le dossier backend/src/remix
.
1import { Injectable } from '@nestjs/common';23@Injectable()4export class RemixService {5public readonly getHello = (): string => {6return 'Hello World!';7};8}
Nous avons ajouté une méthode getHello
qui renvoie une chaîne de caractères. Nous allons ensuite appeler cette méthode dans Remix pour vérifier que tout fonctionne correctement.
N'oublions pas d'abord d'importer ce service dans le fichier app.module.ts
1import { Module } from '@nestjs/common';2import { RemixController } from './remix/remix.controller';3import { RemixService } from './remix/remix.service';45@Module({6imports: [],7controllers: [RemixController],8providers: [RemixService],9})10export class AppModule {}
Pour pouvoir détecter la déclaration des types de notre application NestJS, nous devons modifier le fichier package.json
. (On avait fait la même chose pour Remix.js, déclarant le point d'entrée, le fichier index.cjs
)
Il nous suffit de rajouter ces deux lignes dans le fichier backend/package.json
.
1{2"main": "./dist/remix/remix.service.js",3"types": "./dist/remix/remix.service.d.ts"4}
Pourquoi pointe-t-on vers les fichiers compilés du serveur Remix ? Parce que ce sont les seuls fichiers que nous allons exécuter depuis notre application Remix. Pour générer ces fichiers, nous devons lancer la commande npm run build
à la racine de notre projet.
1npm run build
Comment utiliser ce service ? Nous allons l'importer dans notre controlleur RemixController
.
Nous allons ensuite le renvoyer au contexte de Remix, tel quel.
1import { All, Controller, Next, Req, Res } from '@nestjs/common';2import { createRequestHandler } from '@remix-run/express';3import { getServerBuild } from '@virgile/frontend';4import { NextFunction, Request, Response } from 'express';5import { RemixService } from './remix.service';67@Controller()8export class RemixController {9constructor(private remixService: RemixService) {}1011@All('*')12async handler(13@Req() request: Request,14@Res() response: Response,15@Next() next: NextFunction16) {17//18return createRequestHandler({19build: await getServerBuild(),20getLoadContext: () => ({21toto: 'Salut, ça va ?',22remixService: this.remixService,23}),24})(request, response, next);25}26}
N'oublions pas de re-compiler ce code avec la commande npm run dev
, avant de passer à la suite.
Voici notre première route, nommée api.tsx
. Pour récupérer le service envoyé par NestJS, nous devons ajouter des arguments à notre méthode loader (que Remix se charge de nous fournir)
Nous pouvons ensuite extraire l'object context
, qui contient toute information qu'on souhaite partager depuis le serveur NestJS.
1import { json, type LoaderFunctionArgs } from '@remix-run/node';2export const loader = ({ context }: LoaderFunctionArgs) => {3return json({4status: 'ok',5});6};78export default function Page() {9return <h1>Hello</h1>;10}
Plus haut, notre fichier RemixController
renvoyait le service RemixService
dans le contexte, ainsi que la variable toto
.
Elles sont déjà accessibles dans Remix, bien qu'on ne possède pas encore l'autocomplétion.
Je ne peux plus me passer de l'autocomplétion. Nous allons donc l'ajouter.
Pour bénéficier de l'autocomplétion, nous devons écrire une déclaration de module (ce que nous avions fait tout à l'heure dans le fichier frontend/index.d.cts
)
Cette fois, nous allons la copier directement dans le fichier frontend/root.tsx
Il nous suffit de rajouter cette syntaxe n'importe où dans le fichier frontend/root.tsx
1declare module '@remix-run/node' {2interface AppLoadContext {3remixService: RemixService;4toto: string;5}6}
Bien sûr, nous avons également besoin d'importer le type RemixService
, en haut du fichier.
1import { type RemixService } from '@virgile/backend';
Cela nous donne le fichier suivant :
1import type { LoaderFunctionArgs } from '@remix-run/node';2import {3Links,4Meta,5Outlet,6Scripts,7ScrollRestoration,8} from '@remix-run/react';9import { type RemixService } from '@virgile/backend';1011declare module '@remix-run/node' {12interface AppLoadContext {13remixService: RemixService;14user: unknown;15}16}1718export function Layout({ children }: { children: React.ReactNode }) {19return (20<html lang='en' className='h-full'>21<head>22<meta charSet='utf-8' />23<meta name='viewport' content='width=device-width, initial-scale=1' />24<Meta />25<Links />26</head>27<body className='min-h-screen flex flex-col'>28{children}29<ScrollRestoration />30<Scripts />31</body>32</html>33);34}3536export default function App() {37return <Outlet />;38}
Grâce à cet ajout, nous possédons maintenant l'autocomplétion dans le context
de Remix. Regardez:
Nous pouvons maintenant utiliser le service RemixService
dans notre route api.tsx
. Nous allons appeler la méthode getHello
côté serveur (dans la méthode loader) pour bénéficier de sa valeur côté client.
Nous sommes donc en train de :
1import { json, type LoaderFunctionArgs } from '@remix-run/node';2export const loader = ({ context }: LoaderFunctionArgs) => {3return json({4message: context.remixService.getHello(),5});6};78export default function Page() {9const { message } = useLoaderData<typeof loader>();10return <h1>{message}</h1>;11}
Il nous reste plus qu'à configurer Turborepo ! Ensuite, nous pourrons passer au développement de notre application.