React + Express Monorepo Environment Setup Guide: Achieving Efficient Development with Turborepo

  • react
    react
  • expressjs
    expressjs
  • typescript
    typescript
Published on 2025/02/05

Introduction

In this article, we will explain how to build a monorepo environment and set up an efficient development workflow using React, Express, GraphQL, and Turborepo. By leveraging the advantages of a monorepo structure and managing multiple projects in a single repository, you can improve dependency management, shorten build times, and speed up development. Let’s learn together how to set up an environment where you can efficiently develop both the frontend and backend using Turborepo.

Goal for This Tutorial

We will set up React as the frontend and Express as the backend in a single repository, and configure them so they can be started together using turborepo.

Image from Gyazo

After that, we will confirm that React can access Express.

Image from Gyazo

The directory structure may look complicated at first glance, so a detailed explanation is also included.

.
├── apps
│   ├── backend
│   │   ├── src
│   │   │   └── index.ts
│   │   ├── package.json
│   │   └── tsconfig.json
│   └── frontend
│       ├── dist
│       │   ├── assets
│       │   │   ├── index-D5Y6grW-.js
│       │   │   └── index-kQJbKSsj.css
│       │   ├── index.html
│       │   └── vite.svg
│       ├── public
│       │   └── vite.svg
│       ├── src
│       │   ├── assets
│       │   │   └── react.svg
│       │   ├── App.tsx
│       │   ├── main.tsx
│       │   └── vite-env.d.ts
│       ├── .gitignore
│       ├── README.md
│       ├── eslint.config.js
│       ├── index.html
│       ├── package.json
│       ├── tsconfig.app.json
│       ├── tsconfig.json
│       ├── tsconfig.node.json
│       └── vite.config.ts
├── package-lock.json
├── package.json
└── turbo.json

What is Turborepo?

Turborepo is a tool for managing monorepo structures, mainly used for JavaScript and TypeScript projects. A monorepo is an approach where multiple projects or packages are managed within a single repository. Turborepo is a tool designed to efficiently handle such monorepos.

Main Features of Turborepo

  • Faster build pipelines: Turborepo supports incremental builds to optimize dependency management. By rebuilding only the parts that have changed, it shortens build times and accelerates development.

  • Caching: By caching build results, you can avoid running the same processes repeatedly. This speeds up builds in both CI/CD and local development environments.

  • Dependency management: It efficiently manages dependencies between multiple packages within a monorepo. Turborepo automatically executes tasks and scripts in the correct order across dependent packages.

  • Distributed task execution: Even when multiple packages exist, Turborepo can run tasks in parallel to improve development speed.

  • Simple configuration: Turborepo uses a configuration file called turbo.json to easily manage project settings. It also integrates with package managers like npm, pnpm, and yarn, and works alongside standard toolchains.

  • CI/CD support: Turborepo helps you efficiently configure CI/CD pipelines. It builds only the parts that have changed, eliminating unnecessary work.

Setup

First, we’ll start with the overall project setup.

mkdir Documents/workspace/react-express-graphql-turborepo
cd Documents/workspace/react-express-graphql-turborepo
npm init -y

Install Turborepo so it can be used.

npm install turbo -D

Next, configure Turborepo.
You can define tasks for things like builds and starting in development mode.

turbo.json
{
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

When running with dev, it is generally a long-running process, and by adding "persistent": true, we ensure that both react and express are not interrupted by other tasks.

Next, modify the package.json at the repository root.

package.json
{
  "name": "react-express-graphql-turborepo",
+ "workspaces": ["apps/*", "packages/*"],
  "version": "1.0.0",
  "description": "",
- "main": "index.js",
+ "packageManager": "npm@10.9.2",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "turbo": "^2.4.0"
  }
}

The workspaces field in package.json is a setting used to build a monorepo. A monorepo is an approach where multiple packages or applications are managed within a single repository.

By using workspaces, you can efficiently manage different packages (applications and libraries) within the same repository, share dependencies, speed up installation, and improve development efficiency.

This completes the initial setup at the repository root.

Creating the Frontend (React + Vite)

From here, we’ll configure each application individually.
We’ll start with the frontend.

mkdir apps
cd apps
npm create vite@latest frontend
apps/frontend/package.json
{
-  "name": "frontend",
+  "name": "@react-express-graphql-turborepo/frontend",
  "private": true,

We’ll configure it as a scoped package.
This setting is not very important for this article, but we’ll set it up for now.

Now that the necessary Vite and React files have been downloaded, install the packages.

cd apps/frontend
npm install

Once installation is complete, start the app to confirm it works.

npm run dev

If you see the following and can access http://localhost:5173/, you’re good to go.

 VITE v6.1.0  ready in 319 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help

That’s it for the frontend setup with Vite + React.

Since the main focus here is the monorepo structure, the explanation of Vite and React is kept to a minimum, but if you’d like to learn more about these tools, please refer to the following article:

https://shinagawa-web.com/en/blogs/react-vite-setup-guide-for-fast-development

Creating the Backend (Express)

Next, we’ll configure Express as the backend.

First, install the packages.

mkdir apps/backend && cd apps/backend
npm init -y
npm i express cors dotenv
npm i -D  typescript ts-node @types/node @types/express @types/cors

Then configure the scoped package name and tsconfig.json.

apps/backend/package.json
{
- "name": "backend",
+ "name": "@react-express-graphql-turborepo/backend",
  "version": "1.0.0",
tsconfig.json
{
  "compilerOptions": {
    "outDir": "dist",
    "rootDir": "src",
    "strict": true,
    "module": "CommonJS",
    "target": "ES6",
    "esModuleInterop": true
  }
}

Once the environment to run Express is ready, create the actual entry file.

apps/backend/src/index.ts
import express from "express";
import cors from "cors";

const app = express();
const PORT = process.env.PORT || 4000;

app.use(cors());
app.use(express.json());

app.get("/", (req, res) => {
  res.send("Hello from Express!");
});

app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`);
});

First, we set it up so that when you access https://localhost:4000, it returns Hello from Express!.

After creating the file, define the start script in package.json.

package.json
- "main": "index.js",
+ "main": "index.ts",
  "scripts": {
+   "dev": "ts-node src/index.ts",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

Check that Express can start properly.

npm run dev
Server is running on http://localhost:4000

Once it’s running, check the response with curl.

curl http://localhost:4000
Hello from Express!

This completes the Express setup.

Again, the explanation of Express is kept minimal here, but if you’d like to learn more, please refer to the following article:

https://shinagawa-web.com/en/blogs/express-typescript-setup-guide

Accessing the Backend from the Frontend

We’ll use the fetch API to display the Hello from Express! response in the browser.

apps/frontend/src/App.tsx
import { useEffect, useState } from "react";

function App() {
  const [message, setMessage] = useState("");

  useEffect(() => {
    fetch("http://localhost:4000")
      .then((res) => res.text())
      .then((data) => setMessage(data));
  }, []);

  return (
    <div>
      <h1>React + Express</h1>
      <p>API Response: {message}</p>
    </div>
  );
}

export default App;

The default CSS is still there and affects the layout, so we’ll delete it for now.

rm apps/frontend/src/index.css
rm apps/frontend/src/App.css

Also update the file that imports the CSS.

apps/frontend/src/main.tsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
- import './index.css'
import App from './App.tsx'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

Now that everything is configured, we’ll start React and Express via Turborepo.
Run the following command at the repository root:

npm run dev

Confirm that both start successfully and that the API response is displayed in the browser.

Image from Gyazo

Image from Gyazo

About the Directory Structure

Basically, everything under apps is the main focus.
In this example, we prepared two directories, frontend and backend, and manage each separately.

tree -I node_modules -I .git -I .turbo --dirsfirst -a
.
├── apps
│   ├── backend
│   │   ├── src
│   │   │   └── index.ts
│   │   ├── package.json
│   │   └── tsconfig.json
│   └── frontend
│       ├── dist
│       │   ├── assets
│       │   │   ├── index-D5Y6grW-.js
│       │   │   └── index-kQJbKSsj.css
│       │   ├── index.html
│       │   └── vite.svg
│       ├── public
│       │   └── vite.svg
│       ├── src
│       │   ├── assets
│       │   │   └── react.svg
│       │   ├── App.tsx
│       │   ├── main.tsx
│       │   └── vite-env.d.ts
│       ├── .gitignore
│       ├── README.md
│       ├── eslint.config.js
│       ├── index.html
│       ├── package.json
│       ├── tsconfig.app.json
│       ├── tsconfig.json
│       ├── tsconfig.node.json
│       └── vite.config.ts
├── package-lock.json
├── package.json
└── turbo.json

Top-level Directories

.
├── package-lock.json
├── package.json
├── turbo.json
  1. package.json
    • Root package.json for the entire monorepo
    • Integrates dependency management for apps/backend and apps/frontend
    • Contains the workspace configuration
  2. package-lock.json
    • Locks the versions of installed dependencies
    • Automatically generated when you run npm install
  3. turbo.json
    • Turborepo configuration file
    • Defines which packages are included under apps and which scripts are run in parallel / cached

Application Directories (apps/)

├── apps
│   ├── backend
│   └── frontend
  • Inside apps/, backend (Express) and frontend (React + Vite) are separated
  • A standard directory structure for monorepos
  • backend and frontend are each managed as independent projects

Backend (apps/backend/)

│   ├── backend
│   │   ├── src
│   │   │   └── index.ts
│   │   ├── package.json
│   │   └── tsconfig.json
  1. src/index.ts
    • Entry point for Express
    • Main file that starts the server
  2. package.json
    • Manages backend dependencies and scripts

Frontend (apps/frontend/)

│   └── frontend
│       ├── dist
│       │   ├── assets
│       │   │   ├── index-D5Y6grW-.js
│       │   │   └── index-kQJbKSsj.css
│       │   ├── index.html
│       │   └── vite.svg
│       ├── public
│       │   └── vite.svg
│       ├── src
│       │   ├── assets
│       │   │   └── react.svg
│       │   ├── App.tsx
│       │   ├── main.tsx
│       │   └── vite-env.d.ts
│       ├── .gitignore
│       ├── README.md
│       ├── eslint.config.js
│       ├── index.html
│       ├── package.json
│       ├── tsconfig.app.json
│       ├── tsconfig.json
│       ├── tsconfig.node.json
│       └── vite.config.ts
  1. dist/
    • Contains files built by Vite
    • assets/ holds JS, CSS, images, and other build artifacts
  2. public/
    • Stores static files
    • vite.svg is Vite’s default icon
    • Files in public/ are copied into dist/
  3. src/
    • Frontend source code
  4. vite.config.ts
    • Configuration file for Vite
    • Contains settings such as the React plugin and alias configuration

Conclusion

Through this article, you’ve learned the basic setup for a monorepo using Turborepo and how to integrate a React + Vite frontend with an Express backend. With this kind of structure, you can efficiently manage multiple applications, speed up builds, and simplify dependency management. I hope this serves as a reference when you set up a development environment using a monorepo. By using Turborepo, you can greatly improve development productivity, so consider adopting it in your real-world projects.

Xでシェア
Facebookでシェア
LinkedInでシェア

Questions about this article 📝

If you have any questions or feedback about the content, please feel free to contact us.
Go to inquiry form