Set up a Turborepo Monorepo with Vite, Typescript, Tailwind, Express, and React/Vue

Set up a Turoborepo monorepo for an Express back end and a React or Vue front end using TailwindCSS. Both backend and frontend are set up with Vite and Typescript. If you'd like to skip the instructions and just download the code, you can find it here.

Prerequisites

Before you begin, you should have Node and Turborepo installed.

Setting up Turborepo

First, initialize Turborepo. During installation, you can specify a directory where you'd like to set up your monorepo, you can use '.' to initialize it in your current directory. If you want remote caching, you can set it up by following the instructions during installation.

npx create-turbo@latest

You can check the installation instructions for yarn/pnpm commands. When setup is complete, you can delete both folders from the /apps directory and the ui folder from the /packages directory.

Setting up Express

This is a slightly unconventional way to set up express and if you're not using Typescript you might prefer the simplicity of doing npm init and installing nodemon for live reloading. However, I've tried setting up a Typescript node server with ts-node and nodemon, and I found it reloaded very slowly, so in this tutorial I'll be using Vite to enable hot reloading.

The first step is to create a new folder in the /apps directory. You can call this whatever you like, but I'll be calling mine express. cd into your new directory and initialize a new project with your chosen package manager.

Next, you'll need to install the required packages:

npm i express cors
npm i -D vite vite-plugin-node typescript @types/express @types/node

You can also install vitest or jest here for unit testing. You may also want to look into the api package.

Next, create a vite.config.ts file at your express root, containing the following:

import { defineConfig } from "vite";
import { VitePluginNode } from "vite-plugin-node";

export default defineConfig({
    server: {
        port: 3000,
    },
    plugins: [
        ...VitePluginNode({
            adapter: "express",
            appPath: "./src/app.ts",
            exportName: "viteNodeApp",
            tsCompiler: "esbuild",
            swcOptions: {},
        }),
    ],
});

A tsconfig.json (note that here the config is being extended from the /packages/tsconfig/base.json file that was created during setup):

{
    "extends": "tsconfig/base.json",

    "compilerOptions": {
        "target": "es2016",
        "module": "ESNext",
        "types": ["node", "express", "vite/client"]
    }
}

Then create a file src/app.ts containing the following:

import express from "express";
const app = express();

app.get("/", (req, res) => res.send("It works!"));

if (import.meta.env.PROD)
  app.listen(3000);

export const viteNodeApp = app;

At this point, you may see an error Property 'env' does not exist on type ImportMeta. This seems to be an intermittent problem that some people experience, something to do with the VS Code language server. Some people have been able to fix it by adding a vite.d.ts (only the .d.ts extension actually matters, you can name it whatever you like) file with the following:

/// <reference types="vite/client" />

OR

import 'vite/client';

I found that this worked when I added the file or changed its contents, but after closing the workspace and reopening it, the error returned. I have not gone any further with troubleshooting this issue yet.

Finally, change the default scripts entry in your package.json to the following:

"scripts": {
    "dev": "vite",
    "build": "vite build",
    "test": "vitest"
},

You can now run npm run dev to start the express server with vite. If you navigate to localhost:3000, you should see the message 'It works!'.

React and Vue

Vite setup for React and Vue is very simple. First, cd to your /apps directory and then run npm create vite@latest for React and npm create vue@latest for Vue, then follow the prompts. I set up React using Typescript + swc and Vue with Typescript, Pinia, Vue Router, ESLint, Prettier, Vitest, and Cypress (no JSX support). Once you have the projects set up, cd into their directories and run npm install.

Adding Tailwind CSS

First install the dependencies:

npm install -D tailwindcss postcss autoprefixer

Then initialize Tailwind:

npx tailwindcss init -p

This will generate the tailwind.config.js and postcss.config.js files.

Edit your tailwind config file to change the content setting:

// React
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],

// Vue with JSX
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx,vue}"],

// Vue without JSX
content: ["./index.html", "./src/**/*.{js,ts,vue}"],

Add the tailwind directives to src/index.css (React) or src/assets/main.css (Vue):

@tailwind base;
@tailwind components;
@tailwind utilities;

I also like to add a temporary @apply rule to confirm that Tailwind is configured properly (in Vue you'll also need to remove the class="green" from the h1 in components/HelloWorld.vue):

@layer base {
    h1 {
        @apply text-red-600;
    }
}

Running the Front End and Back End Together

Turborepo takes care of this for you, just made sure you are in the Turborepo root and then run npm run dev. It will launch your front end and back end together.

Keeping Package Version in Sync

There are some dependencies (like vite) that will be present in multiple projects. To easily keep them in sync, you can use syncpack to keep them in sync.

Building for Production

Just run npm run build in the Turborepo root and you'll get a /dist folder in each one of your apps. Deployment will vary based on the host you're using.