As a developer, I remember the first time I came across the term monorepo. Now as much as the term in itself is as self explanatory as it possibly could be, I still hadn’t heard of it before and so it prompted me to do a little bit more digging into what exactly a monorepo is. I already knew mono is ‘single’ or ‘one’, and repo is short for repository (git) - that place where we store and manage code. So I guess that’s what a monorepo is? A single repository? Yes!
But there’s still a tad bit more to it. See, it's a single repo that contains mutliple projects or apps. Simple as that. Think about it this way — imagine your phone. You have different apps ranging from social, to banking, games, productivity, and so forth, right? And they all run on the same operating system whether iOS or Android, which (the OS) provides common services to them like notifications, internet access, and system updates even if all these apps all do different things.
So what exactly is a monorepo?
Well, it harbours different projects or apps but all within the same system, and they all share common tools, libraries and infrastructure. This makes it easier to update things in one place and ensure that all the projects work well together, just like how your phone updates all its apps through the same system. Interestingly enough, quite a few big names in tech use monorepos like Google, Microsoft, Uber, Airbnb, etc. Anyway, as I continued digging more, I came across the term monolithic application as well. This begged me to ask the question, what’s the difference between a monorepo and a monolithic application? Well, that’s a story for another day when we get into microservices as well. Main focus today is on monorepos, and how to set one up — for dummies! 😁
How to set up one — a monorepo
At this point, I’m assuming as much as you are a dummy like I was when first learning about monorepos, you still have some prerequisite knowledge on Git, package managers (npm, yarn, pnpm, etc.), CLI commands, and frameworks — Next.js and NestJS in this case. But just incase you don’t, you’ll need to have the following things checked off in your development environment setup list:
- Node installed - Download Node.js®
- Install and set up Git - Setting up Git
- Nest CLI installed globally:
npm i -g @nestjs/cli
Setting up NestJS
Alright, back to where we were! We’ll be setting up our monorepo from scratch, so we won’t exactly use tools like Nx, Turborepo or Lerna—we’re still dummies, one step at a time before we get to any kind of complexities surrounding the topic. Our monorepo will be called another-juan
, get it? Lol. So let’s start by creating the directory/folder:
mkdir another-juan && cd another-juan
Then run the npm init
command inside the another-juan
folder to create a package.json
file. After that, we’re going to create an apps
directory inside the root of our repository (another-juan):
mkdir apps && cd apps
Inside the apps
directory, we’ll create a new NestJS app—let’s call it backend
. In addition, we won’t initialize git as we’ll do that in the root level of our repository. Lastly, we’re going to use npm
as our package manager—simply due to the fact that it's one of the most, if not most popular package managers. The following command will help us with this:
nest new backend --strict --skip-git --package-manager=npm
The --strict
flag adds TypeScript strict mode options to the project that will help us catch more potential errors early. Once the project has successfully been created, navigate to the src/
directory and locate the main.ts
file.
It should look like this once you open it:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();
By default, our backend app is hosted on port 3000
. We need to modify this and have it on a different port as 3000
is where our Next.js app will be hosted. We can change it to 3001
to avoid conflict of our local servers. Just to add, we can also use an environment variable for the port and set 3001
as a fallback. This is because during deployment, your hosting provider will likely assign a port for you automatically.
Our main.ts
file should now look like this:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(process.env.PORT || 3001);
}
bootstrap();
To start the project, simply run the following command in your terminal:
npm run start
In your browser, head over to the url bar and go to http://localhost:3001
. If everything’s gone right up to this point, you should see a “Hello World!” message displayed as seen below:
Setting up Next.js
Awesome! So far so good! With our NestJS app set up, we can now move on to creating our Next.js app inside our apps
directory, which we'll name—drumroll—frontend
. All we have to do is run the following command to create the project:
npx create-next-app@latest frontend --typescript
Similar to our NestJS app, we also want to use TypeScript in our Next.js app. After running the command, you'll be presented with configuration options. I've opted to stick with the default config settings, but feel free to customize them based on your requirements.
Once installation and set up is done, navigate into the frontend
directory, delete the .git
folder and .gitignore
file—we'll set up git in the root level of our repository, and start a server to run our newly created Next app:
cd frontend && npm run dev
Our Next.js app is now running at http://localhost:3000
. Remember when we mentioned port 3000
earlier? It’s the default port for Next.js applications. Open your browser and enter http://localhost:3000
in the url bar. You should see the following:
Looks great! Here's how our directory tree looks like now with both our NestJS and Next.js apps set up:
Running Both Apps Together
Right now, both apps are up and running, but separately. We’re aiming for teamwork here though—we want to launch them together with a single command from the root directory. To make that happen, we’ll need to tweak a few settings. Once we’ve done that, boom—one command, both apps up and running in sync.
To achieve this, we'll need to install a package called concurrently
in the package.json
file at the root level of our repository.
concurrently
, as its name insinuates, allows us to run multiple commands simultaneously in a single terminal window. This is particularly useful for monorepos where you want to start multiple services or apps at once. By using concurrently, we can run both the NestJS and Next.js apps in parallel, making it easy to manage them with a single command. Let's install it by running the following command:
npm i concurrently
We still need to tweak one or two things before we can run both our apps together using a single command. In the package.json
file in our project root, we'll need to add the following lines in the "scripts"
section.
"dev": "concurrently \"npm:dev:backend\" \"npm:dev:frontend\"",
"dev:backend": "cd apps/backend && npm run start:dev",
"dev:frontend": "cd apps/frontend && npm run dev",
Our package.json
file should now look like this:
{
"name": "another-juan",
"version": "1.0.0",
"description": "This is a project aimed at providing more insight on how to set up a monolithic repository that consists of both Next.js and NestJS apps.",
"main": "index.js",
"scripts": {
"dev": "concurrently \"npm:dev:backend\" \"npm:dev:frontend\"",
"dev:backend": "cd apps/backend && npm run start:dev",
"dev:frontend": "cd apps/frontend && npm run dev",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Shaquille Ndunda",
"license": "ISC",
"dependencies": {
"concurrently": "^9.0.1"
}
}
And voila! Let's execute the following command in our project/repository root:
npm run dev
In your browser, go to http://localhost:3000
for the frontend app, and http://localhost:3001
for the backend app. Works like a charm. Check out our terminal:
Initializing Git and Pushing to GitHub
Now that we've successfully set up our monorepo, last thing we're going to do is initialize git in our monorepo. Make sure you at least have git set up in your local development environment, and a GitHub account.
To set up git, run the following command in the project root:
git init
This will automatically create a .git
folder in your root. We however do not want to push everything to our remote repository and so we need to create a .gitignore
file—still in our project root. Inside it, add the following:
node_modules
dist
build
out
.env
.next
*.tsbuildinfo
next-env.d.ts
.pnp
.pnp.js
.yarn/install-state.gz
.DS_Store
*.pem
npm-debug.log*
yarn-debug.log*
yarn-error.log*
Then head over to your GitHub account and create a new repository. Choose an owner, add a description, select visibility (public or private), and add a README.md
file (useful especially if you're in a team or collaborating with someone else). Click on Create repository
.
Back in your local environment, inside your project root, run the following command to link your local repository to the remote repository you just created on GitHub:
git remote add origin [repository_url]
Run this to pull the remote repository into your local repository:
git pull origin main
Finally, stage your files, commit them, and push.
git add .
git commit -m [commit message]
git push -u origin main
Next Steps
Congratulations! You just created and set up a Next.js and NestJS monorepo. In the next article, we'll explore more into custom configuration for both apps, build a simple CRUD operations project, get into database integration, and UI development.
If you want to follow through this tutorial with the source code:
shaqdeff / another-juan
This is a project aimed at providing more insight on how to set up a monolithic repository that consists of both Next.js and NestJS apps.
Next.js and NestJS Monorepo
This project demonstrates a monorepo setup combining Next.js for the frontend and NestJS for the backend. It's designed to provide insights on how to structure and manage a full-stack TypeScript application using these two powerful frameworks.
Project Structure
-
apps/frontend
: Next.js application -
apps/backend
: NestJS application - Root level configuration for the monorepo
Description
This monorepo showcases:
- A Next.js frontend with Tailwind CSS for styling
- A NestJS backend API
- Concurrent development of both frontend and backend
- Shared configuration and dependencies management
Getting Started
Prerequisites
- Node.js (version 14 or later recommended)
- npm or yarn
Installation
-
Clone the repository:
git clone https://github.com/shaqdeff/another-juan.git cd another-juan
-
Install dependencies:
npm install
Running the Development Environment
To start both frontend and backend concurrently:
npm run dev
- This command utilizes the script defined in the root
package.json
file to run thedev
script in bothapps/frontend
andapps/backend
directories.
Accessing the Applications
- Frontend: http://localhost:3000
- …
You can also follow me here on Dev.to to stay updated on the next articles, and other tutorials/articles.
Top comments (1)