March 14, 2024
A simple setup that can help you get started with a monorepo using React and Express (SPA and API).
You can find the repo on GitHub.
It also includes things like ESLint, Prettier, Jest (Testing Library on the frontend and Supertest on the backend), and Tailwind.
Create a new directory and navigate to it. Then run npm init
in the terminal.
npm init
This should create a package.json
file.
Install the first dependency - concurrently
- it would help you run the backend and the frontend at the same time.
npm install concurrently -D
in the package.json
file, define the workspaces.
"workspaces": [
"backend",
"frontend"
],
Your package.json
should look something like this:
{
"name": "react-express-starter",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"workspaces": ["backend", "frontend"],
"devDependencies": {
"concurrently": "^8.2.2"
}
}
If you are using VS Code, you can create a workspace config file for the editor.
${nameOfYourRepo}.code-workspace
- in this case react-express-starter.code-workspace
{
"folders": [
{
"path": "./",
"name": "project-root"
},
{
"path": "./backend",
"name": "backend"
},
{
"path": "./frontend",
"name": "frontend"
}
]
// you can add other settings here
}
mkdir backend
cd backend
npm init -y
Go back to the root directory.
cd ..
Install express
in the backend folder.
npm install express -w backend
express
should appear in the package.json
file in the backend folder.
Install a few essential dev dependencies like ESLint, Prettier, and TypeScript stuff (types and ts-node)
(If you don't want to use ESLint, Prettier, and TypeScript, you can skip this step)
npm i -D typescript ts-node eslint prettier eslint-config-prettier eslint-plugin-node eslint-plugin-prettier @typescript-eslint/eslint-plugin @typescript-eslint/parser @types/express -w backend
Add config files for ESLint, Prettier, and TypeScript. You can find them in the GitHub repo.
Now you can add some backend code, and set up the Express app.
This example contains a basic folder structure that can help you start when building something a bit more complex than an app with one or two routes.
mkdir services controllers routes
Create a service file that returns some dummy data and simulates a delay as if we are fetching data from an API or a database.
Create a services/book.service.ts
file.
import books from '../booksData'
export const getAllBooks = async () => {
// simulate a delay
const data = await new Promise(resolve => setTimeout(() => resolve(books), 500))
return data
}
You can find the booksData
file in the GitHub repo.
Create a controllers/book.controller.ts
file.
import { Request, Response } from 'express'
import { getAllBooks } from '../services/book.service'
export const all = async (req: Request, res: Response) => {
try {
const result = await getAllBooks()
return res.status(200).send(result)
} catch (error) {
console.log(error)
return res.status(500).send({ message: 'Something went wrong' })
}
}
Create a routes/book.route.ts
file.
import express from 'express'
import { all } from '../controllers/book.controller'
const router = express.Router()
router.get('/', all)
export default router
The final pieces are app.ts
and server.ts
files, and we can run the backend code.
Create a app.ts
file.
import express from 'express'
import bookRouter from './routes/book.route'
const app = express()
app.use(express.json())
app.use('/books', bookRouter)
export default app
Create a server.ts
file.
import app from './app'
const port = 4000
app.listen(port, () => {
console.log(`App listening at http://localhost:${port}`)
})
Add a dev
script in the backend
package.json
file.
"scripts": {
"dev": "ts-node server.ts"
}
Now you can run it.
npm run dev -w backend
Open http://localhost:4000/books
in the browser.
If you change some file, you will have to restart the server manually to see updates. You can fix this with nodemon
or similar.
npm i -D nodemon -w backend
Create a nodemon.json
file in the backend
folder.
{
"exec": "ts-node --files server.ts",
"ext": ".ts,.js"
}
Update the dev
script in the backend
package.json
file.
"scripts": {
"dev": "nodemon"
}
This example uses Vite for the frontend.
In the root folder run:
npm create vite@latest frontend
Then install the dependencies.
npm install -w frontend
In the frontend
folder, you can see that ESLint is already set up. I also added Jest (Testing Library) and Tailwind, you can check it out in the GitHub repo.
Replace the code in the App.tsx
to test things out.
import { useEffect, useState } from 'react'
import './App.css'
type Book = {
id: string
title: string
}
function App() {
const [books, setBooks] = useState<Book[]>([])
useEffect(() => {
const getBooks = async () => {
const response = await fetch('http://localhost:4000/books')
const data = await response.json()
setBooks(data)
}
getBooks()
}, [])
return (
<>
<ul>
{books.map(book => (
<li key={book.id}>{book.title}</li>
))}
</ul>
</>
)
}
export default App
In the package.json
file in the root directory add these scripts:
"scripts": {
"backend": "npm run dev -w backend",
"frontend": "npm run dev -w frontend",
"start": "concurrently \"npm run backend\" \"npm run frontend\""
},
Now if you run npm start
in the root directory, that will run both the backend and the frontend.
if you open http://localhost:5173/
in the browser, you should see the list of books.
Visit the GitHub repo.