el enfoque es similar a nextjs, ya tendra sun fronten de react y un backend con nestjs, todo con typescript.

Un monorepo tiene las ventajas:

  • un solo pipeline
  • configuracion y codigo compartido
  • nestjs puede servir react
mkdir nestjs-react-monorepo
cd nestjs-react-monorepo

instalacion de turbo

npm init -y
npm i -D turbo
mkdir apps
cd apps
nest new api
npm

Puedes cancelar la instalacion de modulos de npm de nestjs, porque mas adelante instalaremos tanto los modulos de backend y frontend juntos

Luego dentro de la misma carpeta apps, ejecuta:

npm create vite

react y typescript npm

cd ..

luego ubicado en el root del proyecto instala todas las dependencias

configurando turbo repo

crea un archivo en la raiz del proyecto llamado turbo.json

{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "dev": {
        "cache": false
    }
  },
  
}

https://turbo.build/repo/docs/reference/configuration

Luego en nestjs cambia el script start:dev, por solo dev, para que tanto el frontnd como el backend tengan el mismo nombre.

deja de esta forma el comando dev:

    "dev": "nest start --watch --preserveWatchOutput",

de esta forma nest no limpiara la consola borrando tambien los mensajes de vitesjs

Luego actualiza la configuracion de turbo asi

{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "dev": {
        "cache": false
    }
  },
  "scripts": {
    "dev": "npm run dev"
  }
}

luego actualiza el package.json de root:

{
  "scripts": {
    "dev": "turbo run dev"
  },
  "devDependencies": {
    "turbo": "^1.9.9"
  },
    "workspaces": [
    "apps/*"
  ]
}

el desliegue

cuando hagamos build de nestjs, luego esete se ejecutara y servira el build de react. esto quiere decir que en produccion las peticiones del frontend llegar al servidor bajo el mimso dominio

proxy

pero en desarrollo seran dos dominios

asi que para evitar colocar configuraciones de mas, podemos usar un proxy desde el frontend ya que estamos usando vitejs. en apps/client/vite.config.js:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      "/api": {
        target: "http://localhost:3000",
        changeOrigin: true,
      }
    }
  }  
})

Luego en nesjts tambien necesitamos añadir un prefijo llamado /api en main.ts:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.setGlobalPrefix('/api');
  await app.listen(3000);
}
bootstrap();

ahora todas las rutas empiezan con /api en el backend

Puedes probar esta configuracion usando esto:

function App() {
  return (
    <div>
      <button
        onClick={() => {
          fetch("/api")
            .then((res) => res.text())
            .then((data) => console.log(data));
        }}
      >
        get data
      </button>
    </div>
  );
}

export default App;

build

{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "dev": {
        "cache": false
    },
    "build": {
      "dependsOn": [
        "^build"
      ],
      "outputs": [
        "dist/**"
      ]
    }
  }
}

en package.json del root:

    "build": "turbo build"
npm run build

esto creara ambos build, de hecho si ejecutas de nuevo este usara la memoria cache para hacr el build de forma mas rapida

server archivos estaticos

en el root:

npm i --workspace api @nestjs/serve-static

luego en app.module.ts

import { ServeStaticModule } from '@nestjs/serve-static';
import { join } from 'path';

@Module({
  imports: [
    ServeStaticModule.forRoot({
      rootPath: join(__dirname, '../..', 'client', 'dist'),
    }),
  ],
  ...
})
export class AppModule {}

luego en api/packag.ejson

finalmente en el package.json de root:

"start": "node apps/api/dist/main.js","