Nous configurons un package.json à la racine du projet et installons Turborepo, pour orchestrer l'exécution de nos deux projets.
Pour configurer le monorepo, nous avons besoin de reculer d'un dossier.
1cd .. # Pour remonter d'un dossier, si vous êtes dans le dossier frontend
À l'intérieur du dossier stack-remix-nestjs
, nous allons instancier un nouveau fichier package.json
pour configurer notre monorepo.
1npm init -y # Pour créer un fichier package.json
Le fichier package.json
doit ressembler à ça :
1{2"name": "nestjs-remix-monorepo",3"version": "1.0.0",4"description": "",5"main": "index.js",6"scripts": {7"test": "echo \"Error: no test specified\" && exit 1"8},9"keywords": [],10"author": "",11"license": "ISC",12"packageManager": "npm@10.2.3"13}
Nous allons utiliser une librairie appelée Turborepo
pour gérer notre monorepo.
1npm install -D turbo
Nous pouvons aussi l'installer en global pour bénéficier de la commande
turbo
dans le terminal.
1npm install -g turbo
Ensuite, nous allons déclarer nos deux projets comme des workspaces dans le fichier package.json
.
Le fichier devrait maintenant ressembler à ça :
1{2"name": "nestjs-remix-monorepo",3"version": "1.0.0",4"description": "",5"main": "index.js",6"scripts": {7"test": "echo \"Error: no test specified\" && exit 1"8},9"keywords": [],10"author": "",11"license": "ISC",12"packageManager": "npm@10.2.3",13"workspaces": ["frontend", "backend"],14"devDependencies": {15"turbo": "^1.13.0"16}17}
Nous devons ensuite créer un fichier de configuration Turbo. Pour ce faire, nous pouvons créer un fichier nommé turbo.json
.
1touch turbo.json # Pour créer un fichier turbo.json
Ensuite, nous allons coller la configuration par défaut, qu'on retrouve sur la documentation officielle :
1{2"$schema": "https://turbo.build/schema.json",3"pipeline": {4"build": {5"outputs": ["dist/**"]6},7"type-check": {}8}9}
Nous pouvons ajouter un fichier .gitignore
pour éviter de versionner les fichiers volumineux.
1touch .gitignore # Pour créer un fichier .gitignore
1.turbo2node_modules3/dist4/build5/out
Les pipelines permettent de définir des commandes que turbo va exécuter pour chaque projet.
Exemple : Nous allons créer une pipeline nommée dev
pour exécuter l'application NestJS en environnement de développement.
D'abord, nous devons faire une petite modification aux fichiers package.json
respectifs de chaque projet.
Nous allons ajouter trois commandes dans le fichier backend/package.json
de notre application NestJS.
1"scripts": {2"dev": "run-p dev:compile dev:watch",3"dev:compile": "tsc --build --watch",4"dev:watch": "nodemon node dist/main.js",5}
Explication : La première commande va exécuter en parallèle les deux commandes suivantes.
dev:compile
va compiler le code source de NestJS en continu.
dev:watch
va surveiller les changements apportés au code et redémarrer le serveur à chaque modification.
Pour ce faire, nous devons également installer les dépendances nodemon
et npm-run-all
.
1cd backend # Pour se déplacer dans le dossier backend2npm install -D nodemon npm-run-all # Pour installer les dépendances
Ensuite, renommons la commande dev
par old-dev
dans le frontend/package.json
pour l'empêcher de s'exécuter.
1"scripts": {2"old-dev": "remix vite:dev"3}
Nous devons maintenant créer une pipeline dev
dans notre fichier de configuration turbo.json
1{2"$schema": "https://turbo.build/schema.json",3"pipeline": {4"build": {5"outputs": ["dist/**"]6},7"dev": {8"cache": false,9"persistent": true10}11}12}
Pour exécuter cette pipeline, nous devons lancer la commande turbo
dans le terminal, suivi du nom de la pipeline.
1turbo dev
Nous allons créer une commande dans le fichier package.json
de la racine pour exécuter cette pipeline.
1"scripts": {2"dev": "turbo dev"3}
Maintenant, lancer la commande npm run dev
dans le terminal exécutera l'application NestJS (qui s'occupera d'exécuter Remix.js).
On rencontre l'erreur suivante.
Pas de panique, c'est normal. Nous n'avons pas encore importé les méthodes définies dans le fichier frontend/index.cjs
.
Pour pouvoir importer ces méthodes, nous devons déclarer la librairie @virgile/frontend
dans le fichier backend/package.json
.
D'abord, renommons nos projets backend/package.json
et frontend/package.json
.
1"name": "@virgile/backend",
1"name": "@virgile/frontend",
Ces noms vont nous permettre d'utiliser la librairie @virgile/frontend
dans notre application NestJS.
Après avoir fait ce changement, il nous suffit d'ajouter la dépendance @virgile/frontend
en tant que dependencies
dans le fichier backend/package.json
.
1"dependencies": {2"@virgile/frontend": "*" // Nous informons le monorepo qu'il doit utiliser notre librairie frontend dans le projet backend3}
Attention. Comme nous venons de renommer notre librairie frontend
, nous devons nous assurer d'utiliser le même nom dans le fichier frontend/index.d.cts
1declare module '@virgile/backend' {2// <= Nom du module, à adapter selon le nom de votre projet3export function getPublicDir(): string;4export function getServerBuild(): Promise<any>;5export function startDevServer(app: any): Promise<void>;6}
Nous pouvons à présent importer la méthode getServerBuild
dans le fichier backend/src/remix/remix.controller.ts
.
Voici le fichier modifié :
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';56@Controller()7export class RemixController {8@All('*')9async handler(10@Req() request: Request,11@Res() response: Response,12@Next() next: NextFunction13) {14//15return createRequestHandler({16build: await getServerBuild(),17getLoadContext: () => ({18toto: 'Salut, ça va ?',19}),20})(request, response, next);21}22}
Il nous manque deux méthodes à utiliser avant de pouvoir utiliser Remix.js.
Nous allons importer les méthodes getPublicDir
et startDevServer
dans le fichier backend/src/main.ts
.
1import { NestFactory } from '@nestjs/core';2import { NestExpressApplication } from '@nestjs/platform-express';3import { getPublicDir, startDevServer } from '@virgile/frontend';4import { AppModule } from './app.module';56async function bootstrap() {7const app = await NestFactory.create<NestExpressApplication>(AppModule, {8bodyParser: false,9});1011await startDevServer(app);1213app.useStaticAssets(getPublicDir(), {14immutable: true,15maxAge: '1y',16index: false,17});1819const selectedPort = process.env.PORT ?? 3000;2021console.log(`Running on port http://localhost:${selectedPort}`);22await app.listen(selectedPort);23}24bootstrap();
Nous exécutons la méthode startDevServer
, qui va démarrer le serveur de développement de Remix.js en utilisant Vite. On n'aura donc pas besoin d'exécuter deux serveurs en parallèle, NestJS s'occupe de tout.
Ensuite, nous déclarons le dossier public
de Remix.js comme dossier statique pour que NestJS puisse servir les fichiers statiques (comme les images, le favicon, le CSS ...).
Nous pouvons à présent lancer le serveur de développement en exécutant la commande npm run dev
dans le terminal.
1npm run dev
Le serveur devrait démarrer sans erreur.