Démonstration du code

Nous testons l'intégration de Remix et NestJS.

6 min read

Ajout d'une route Remix de démonstration

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.

Création d'une route Remix

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).

frontend/app/routes/api.tsx
1
import { json } from '@remix-run/node';
2
export const loader = () => {
3
return json({
4
status: '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.

localhost:3000/api
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.

frontend/app/routes/api.tsx
1
import { json } from '@remix-run/node';
2
export const loader = () => {
3
return json({
4
status: 'ok',
5
});
6
};
7
8
export default function Page() {
9
return <h1>Hello</h1>;
10
}

Actualisons la page. Nous devrions voir le texte Hello s'afficher.

Notre page Remix affiche un composant Hello

Deuxième victoire ! Notre route Remix fonctionne correctement. Nous allons maintenant importer un service NestJS dans notre application Remix.

Ajout du RemixService (dans NestJS)

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.

backend/src/remix/remix.service.ts
1
import { Injectable } from '@nestjs/common';
2
3
@Injectable()
4
export class RemixService {
5
public readonly getHello = (): string => {
6
return '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

backend/src/app.module.ts
1
import { Module } from '@nestjs/common';
2
import { RemixController } from './remix/remix.controller';
3
import { RemixService } from './remix/remix.service';
4
5
@Module({
6
imports: [],
7
controllers: [RemixController],
8
providers: [RemixService],
9
})
10
export 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.

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.

Terminal
1
npm 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.

backend/src/remix/remix.controller.ts
1
import { All, Controller, Next, Req, Res } from '@nestjs/common';
2
import { createRequestHandler } from '@remix-run/express';
3
import { getServerBuild } from '@virgile/frontend';
4
import { NextFunction, Request, Response } from 'express';
5
import { RemixService } from './remix.service';
6
7
@Controller()
8
export class RemixController {
9
constructor(private remixService: RemixService) {}
10
11
@All('*')
12
async handler(
13
@Req() request: Request,
14
@Res() response: Response,
15
@Next() next: NextFunction
16
) {
17
//
18
return createRequestHandler({
19
build: await getServerBuild(),
20
getLoadContext: () => ({
21
toto: 'Salut, ça va ?',
22
remixService: 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.

De retour dans Remix.js

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.

frontend/app/routes/api.tsx
1
import { json, type LoaderFunctionArgs } from '@remix-run/node';
2
export const loader = ({ context }: LoaderFunctionArgs) => {
3
return json({
4
status: 'ok',
5
});
6
};
7
8
export default function Page() {
9
return <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.

Ajouter l'autocomplétion des méthodes de NestJS dans Remix

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

frontend/root.tsx
1
declare module '@remix-run/node' {
2
interface AppLoadContext {
3
remixService: RemixService;
4
toto: string;
5
}
6
}

Bien sûr, nous avons également besoin d'importer le type RemixService, en haut du fichier.

frontend/root.tsx
1
import { type RemixService } from '@virgile/backend';

Cela nous donne le fichier suivant :

frontend/root.tsx
1
import type { LoaderFunctionArgs } from '@remix-run/node';
2
import {
3
Links,
4
Meta,
5
Outlet,
6
Scripts,
7
ScrollRestoration,
8
} from '@remix-run/react';
9
import { type RemixService } from '@virgile/backend';
10
11
declare module '@remix-run/node' {
12
interface AppLoadContext {
13
remixService: RemixService;
14
user: unknown;
15
}
16
}
17
18
export function Layout({ children }: { children: React.ReactNode }) {
19
return (
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
}
35
36
export default function App() {
37
return <Outlet />;
38
}

Grâce à cet ajout, nous possédons maintenant l'autocomplétion dans le context de Remix. Regardez:

Notre context Remix.js possède maintenant l'autocomplétion depuis l'application NestJS

Utiliser le service RemixService dans notre route

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 :

  • Appeler une méthode NestJS côté serveur
  • Renvoyer son résultat côté client
  • Récupérer le résultat côté client dans notre composant React
frontend/app/routes/api.tsx
1
import { json, type LoaderFunctionArgs } from '@remix-run/node';
2
export const loader = ({ context }: LoaderFunctionArgs) => {
3
return json({
4
message: context.remixService.getHello(),
5
});
6
};
7
8
export default function Page() {
9
const { message } = useLoaderData<typeof loader>();
10
return <h1>{message}</h1>;
11
}

Notre route Remix réussit à appeler une méthode définie dans notre application NestJS

Il nous reste plus qu'à configurer Turborepo ! Ensuite, nous pourrons passer au développement de notre application.