<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Raghvendra Awasthi</title>
    <description>The latest articles on DEV Community by Raghvendra Awasthi (@rawasthi231).</description>
    <link>https://dev.to/rawasthi231</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F903220%2F2e3aec20-150a-490f-a5ff-f427598e26f8.jpeg</url>
      <title>DEV Community: Raghvendra Awasthi</title>
      <link>https://dev.to/rawasthi231</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rawasthi231"/>
    <language>en</language>
    <item>
      <title>Build Containerized MERN App with Lerna Monorepo</title>
      <dc:creator>Raghvendra Awasthi</dc:creator>
      <pubDate>Mon, 09 Sep 2024 15:12:48 +0000</pubDate>
      <link>https://dev.to/rawasthi231/build-containerized-mern-app-with-lerna-monorepo-30d5</link>
      <guid>https://dev.to/rawasthi231/build-containerized-mern-app-with-lerna-monorepo-30d5</guid>
      <description>&lt;p&gt;In this article we are going to build a &lt;strong&gt;MERN&lt;/strong&gt; app using &lt;strong&gt;Lerna Monorepo&lt;/strong&gt; setup and also we will create a docker image of this complete application for a &lt;strong&gt;containerized&lt;/strong&gt; deployment.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I'm using windows system for this guide._&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Here is a breakdown of the process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Lerna repo setup_&lt;/li&gt;
&lt;li&gt;Setup workspaces (Frontend and Backend)_&lt;/li&gt;
&lt;li&gt;Creating demo feature (Show users list in a table)_&lt;/li&gt;
&lt;li&gt;Application containerization_&lt;/li&gt;
&lt;li&gt;Run the containerized app in docker environment_&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Before starting this guide, I'm assuming that you have some prior knowledge of MERN applications (ReactJS as frontend and ExpressJS as backend) and containerization concept and it's tools like Docker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NodeJS installed&lt;/li&gt;
&lt;li&gt;Docker desktop installed and working &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If not, you can get these from here:&lt;br&gt;
&lt;a href="https://docs.docker.com/desktop/install/windows-install/" rel="noopener noreferrer"&gt;Install Docker Desktop&lt;/a&gt;&lt;br&gt;
&lt;a href="https://nodejs.org/en/download/package-manager/current" rel="noopener noreferrer"&gt;Install NodeJS&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to learn more about these concepts in detail, please let me know in comment section, I'll make guide on those separately.&lt;/p&gt;

&lt;p&gt;Let's get started...&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Lerna repo setup&lt;/strong&gt;&lt;br&gt;
Firstly create a folder/directory with the name of your choice and inside that folder run &lt;code&gt;npx lerna init&lt;/code&gt; command in the terminal.&lt;br&gt;
This command will create a basic setup of a monorepo for JavaScript application.&lt;br&gt;
The folder structure should look like this.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;your-project-name&lt;/strong&gt;&lt;/em&gt;&lt;br&gt;
  | node_modules&lt;br&gt;
  | lerna.json&lt;br&gt;
  | .gitignore&lt;br&gt;
  | packege.json&lt;br&gt;
  | package-lock.json&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now replace the newly created &lt;code&gt;lerna.json&lt;/code&gt; code with the given code&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

{
  "packages": ["packages/*"],
  "version": "0.0.0",
  "npmClient": "npm",
  "useWorkspaces": true
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And replace the &lt;code&gt;package.json&lt;/code&gt; code with this&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

{
  "name": "root",
  "private": true,
  "workspaces": [
    "packages/*"
  ],
  "dependencies": {},
  "devDependencies": {
    "lerna": "^8.1.8"
  }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;2. Setup workspaces&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As this is going to a MERN application, so, we will be using 2 workspaces here Frontend and Backend. &lt;br&gt;
This is going to be the further division of the application.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;packages&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;backend&lt;/li&gt;
&lt;li&gt;frontend&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;i. Backend setup&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Inside your root folder run &lt;code&gt;mkdir -p packages/backend&lt;/code&gt; command to create packages folder and backend folder inside it.&lt;/p&gt;

&lt;p&gt;Now navigate to the newly created backend folder &lt;code&gt;cd packages/backend&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;npm init -y&lt;/code&gt; to create a minimal JS application.&lt;/p&gt;

&lt;p&gt;Add some dependencies to the backend application by running &lt;code&gt;npm install express cors node-fetch&lt;/code&gt; and &lt;code&gt;npm install --save-dev nodemon&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Create a file named &lt;code&gt;index.js&lt;/code&gt; and update the &lt;code&gt;package.json&lt;/code&gt; to run the script.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

{
  "name": "backend",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",
    "dev": "nodemon index.js"
  },
  "dependencies": {
    "cors": "^2.8.5",
    "express": "^4.18.2",
    "node-fetch": "^2.7.0"
  },
  "devDependencies": {
    "nodemon": "^3.1.1"
  }
}



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;em&gt;ii. Frontend setup&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now run &lt;code&gt;mkdir -p ../frontend&lt;/code&gt; to create new workspace folder and the navigate to it &lt;code&gt;cd ../frontend&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now create a React + Vite app using &lt;code&gt;npm create vite@latest . --template&lt;/code&gt; command. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F397oy5ie8wp0wy9at4oa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F397oy5ie8wp0wy9at4oa.png" alt="Vite app framework template" width="587" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Choose React from the template list.&lt;/p&gt;

&lt;p&gt;Next, choose the variant of your choice from the list.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5h7b7sc11z1ci1j5mk0d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5h7b7sc11z1ci1j5mk0d.png" alt="React + Vite variant" width="564" height="265"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm choosing &lt;u&gt;&lt;strong&gt;TypeScript + SWC&lt;/strong&gt;&lt;/u&gt; here.&lt;/p&gt;

&lt;p&gt;Go back to root folder and run &lt;code&gt;npm install concurrently --save-dev&lt;/code&gt; to run both frontend and backend applications simultaneously.&lt;/p&gt;

&lt;p&gt;Now your root &lt;code&gt;package.json&lt;/code&gt; should look like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

{
  "name": "root",
  "private": true,
  "workspaces": [
    "packages/*"
  ],
  "devDependencies": {
    "concurrently": "^8.2.2",
    "lerna": "^8.1.8"
  }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now, create basic API for fetching users list in backend application and put the below code into the index.js file in backend package.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;I'm using free &lt;strong&gt;jsonplaceholder&lt;/strong&gt; API here for demo purpose, you can modify your backend API based on your requirements.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

const express = require("express");
const cors = require("cors");

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

app.use(cors());

app.get("/users", async (req, res) =&amp;gt; {
  try {
    import("node-fetch")
      .then(async ({ default: fetch }) =&amp;gt; {
        const data = await fetch(
          "https://jsonplaceholder.typicode.com/users"
        ).then((response) =&amp;gt; response.json());
        res.send(data);
      })
      .catch((err) =&amp;gt; {
        console.log("Error in importing node-fetch: ", err);
      });
  } catch (error) {
    console.log("Error: ", error);
    res.status(500).send(error);
  }
});

app.listen(PORT, () =&amp;gt; console.log("Server running on port " + PORT));



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;3. Creating demo feature&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's start with our demo feature of showing users list in a table.&lt;br&gt;
Here in frontend application on the default rendered page (&lt;code&gt;App.tsx file&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Here I'm using a fully TypeScript supported, dynamic and smart table component with inbuilt Infinite Scroll and Pagination features. You can also try this out.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm i react-smart-table-component&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/react-smart-table-component" rel="noopener noreferrer"&gt;You can read more about this package here&lt;/a&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

import { useCallback, useEffect, useState } from "react";

import ReactSmartTableComponent from "react-smart-table-component";

import "./App.css";

interface User {
  id: number;
  name: string;
  username: string;
  email: string;
  address: Address;
  phone: string;
  website: string;
  company: Company;
}

interface Address {
  street: string;
  suite: string;
  city: string;
  zipcode: string;
}

interface Company {
  name: string;
  catchPhrase: string;
  bs: string;
}


function App() {
  const [users, setUsers] = useState&amp;lt;User[]&amp;gt;([]);
  const [loading, setLoading] = useState(false);

  const getUsers = useCallback(async () =&amp;gt; {
    try {
      setLoading(true);
      const response = await fetch("http://localhost:5000/users").then((res) =&amp;gt;
        res.json()
      );
      setUsers(response);
      setLoading(false);
    } catch (error) {
      console.log("Error fetching users", error);
      setUsers([]);
      setLoading(false);
    }
  }, []);

  useEffect(() =&amp;gt; {
    getUsers();
  }, [getUsers]);

  return (
    &amp;lt;&amp;gt;
      &amp;lt;ReactSmartTableComponent
        items={users}
        search
        searchableFields={["name", "email", "phone", "website"]}
        searchBoxPlaceholder="Search users"
        className="table"
        loading={loading}
        headings={[
          {
            fieldName: "name",
            title: "Name",
          },
          {
            fieldName: "email",
            title: "Email",
          },
          {
            fieldName: "phone",
            title: "Phone",
          },
          {
            fieldName: "website",
            title: "Website",
          },
          {
            fieldName: "company",
            title: "Company",
          },
          {
            fieldName: "address",
            title: "City",
          },
        ]}
        scopedFields={ 
         {
          company: (item) =&amp;gt; &amp;lt;td&amp;gt;{item.company.name}&amp;lt;/td&amp;gt;,
          address: (item) =&amp;gt; &amp;lt;td&amp;gt;{item.address.street}&amp;lt;/td&amp;gt;,
         } 
        }
      /&amp;gt;
    &amp;lt;/&amp;gt;
  );
}

export default App;



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To run the both frontend and backend applications, add the &lt;code&gt;run &amp;amp; build&lt;/code&gt; scripts to root package.json file.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{&lt;br&gt;
  "name": "root",&lt;br&gt;
  "private": true,&lt;br&gt;
  "workspaces": [&lt;br&gt;
    "packages/*"&lt;br&gt;
  ],&lt;br&gt;
  "scripts": {&lt;br&gt;
    "dev": "concurrently \"npm run dev --workspace=backend\" \"npm run dev --workspace=frontend\"",&lt;br&gt;
    "build:frontend": "npm run build --workspace=frontend",&lt;br&gt;
    "start": "npm start --workspace=backend"&lt;br&gt;
  },&lt;br&gt;
  "devDependencies": {&lt;br&gt;
    "concurrently": "^8.2.2",&lt;br&gt;
    "lerna": "^8.1.8"&lt;br&gt;
  }&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;By running &lt;code&gt;npm run dev&lt;/code&gt;, you can run both frontend and backend at same time in development environment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Application containerization&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My plan is to serve the frontend application through the backend endpoint. To achieve that I have to create a default endpoint in the backend application and put the frontend app's build files to the backend folder's public folder or we can directly use the relative import path &lt;code&gt;../frontend/dist/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In order to achieve this I'm using the first method.&lt;/p&gt;

&lt;p&gt;Now backend &lt;code&gt;index.js&lt;/code&gt; file should be like this.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

const express = require("express");
const cors = require("cors");
const path = require("path");

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

app.use(express.static(path.join(__dirname, "public")));
app.use(cors());

app.get("/users", async (req, res) =&amp;gt; {
  try {
    import("node-fetch")
      .then(async ({ default: fetch }) =&amp;gt; {
        const data = await fetch(
          "https://jsonplaceholder.typicode.com/users"
        ).then((response) =&amp;gt; response.json());
        res.send(data);
      })
      .catch((err) =&amp;gt; {
        console.log("Error in importing node-fetch: ", err);
      });
  } catch (error) {
    console.log("Error: ", error);
    res.status(500).send(error);
  }
});

app.get("*", (_, res) =&amp;gt; {
  res.sendFile(path.join(__dirname, "public", "index.html"));
});

app.listen(PORT, () =&amp;gt; console.log("Server running on port " + PORT));



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here, you might be thinking about the overhead of moving the build files from &lt;code&gt;packages/frontend/dist&lt;/code&gt; to &lt;code&gt;packages/backend/public&lt;/code&gt;, but this will not be an issue when we wrap this application within a &lt;code&gt;Docker container&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So, now let's start containerizing our application, for this create 2 files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;i.  Dockerfile&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;ii. .dockerignore&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Make sure to keep files name exact same, otherwise the won't work.&lt;/p&gt;

&lt;p&gt;Put &lt;strong&gt;node_modules&lt;/strong&gt; inside the &lt;code&gt;.dockerignore&lt;/code&gt; file and put below given code to your &lt;code&gt;Dockerfile&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

# Use an official Node.js runtime as a parent image
FROM node:20-alpine

# # Set the working directory
WORKDIR /app

# Copy package.json and package-lock.json
COPY package*.json ./

# Copy lerna.json
COPY lerna.json ./

# Copy the rest of the application code
COPY . .

# Install dependencies
RUN npm install

RUN npm run build:frontend

COPY packages/frontend/dist packages/backend/public

# Expose the backend port
EXPOSE 5000

# Define the command to run the application
CMD ["npm", "start"]



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here, in the Dockerfile, I'm using &lt;code&gt;node:20-alpine&lt;/code&gt; image as the runtime environment for our application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;/app&lt;/strong&gt; is going to the working directory of our application inside the docker container.&lt;/p&gt;

&lt;p&gt;Command &lt;code&gt;COPY packages/frontend/dist packages/backend/public&lt;/code&gt; will do that task for us to copy the app build to the backend's public folder.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;EXPOSE 5000&lt;/code&gt; will going to expose our application on port 5000 for the outer world.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CMD ["npm", "start"]&lt;/code&gt; will serve our backend application.&lt;/p&gt;

&lt;p&gt;Now, our application is ready to be containerized. To create the docker image run&lt;br&gt;
&lt;code&gt;docker build -t &amp;lt;your-application-name&amp;gt; .&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Make sure, your docker desktop is running at this time.&lt;/p&gt;

&lt;p&gt;After successful building the docker image, you can see your application image in the docker desktop inside images section with the name you provided while building the image.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Run the containerized app in docker environment&lt;/strong&gt;&lt;br&gt;
We have created the complete application as a docker image, now to deploy our application, we need to run the image within a container.&lt;/p&gt;

&lt;p&gt;To do this, run &lt;code&gt;docker run -dp 5000:5000 &amp;lt;your-application-name&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Above command will run the application within a docker container. You can check it in the docker desktop.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-dp&lt;/code&gt; - &lt;strong&gt;d&lt;/strong&gt; stands for detachable and &lt;strong&gt;p&lt;/strong&gt; for port&lt;/p&gt;

&lt;p&gt;This flag runs our application in a detachable mode and map the container's 5000 port with our local system's 5000 port.&lt;/p&gt;

&lt;p&gt;Now, you can test your application running on &lt;code&gt;http://localhost:5000&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsb26e721k2micbvlctcm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsb26e721k2micbvlctcm.png" alt="Demo feature: Show users list" width="800" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There might be UI differences as I added some &lt;code&gt;css&lt;/code&gt; for the table, you can add your own styles to that table component, that's fully customizable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Additionally:&lt;/strong&gt;&lt;br&gt;
You can put your docker image on the docker registries such as &lt;code&gt;Docker Hub&lt;/code&gt; or &lt;code&gt;AWS ECR (Elastic Container Registry)&lt;/code&gt; and deploy through CI/CD pipelines on the deployment services like &lt;code&gt;AWS ECS (Elastic Container Service)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Github Repository of the complete project:&lt;/em&gt;&lt;br&gt;
&lt;a href="https://github.com/rawasthi231/mern-monorepo-app.git" rel="noopener noreferrer"&gt;Click Here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, that's all about this guide.&lt;br&gt;
I hope you have enjoyed reading this article and learnt something from it. If yes, please comment and share this article and let me know what else I can share with you.&lt;/p&gt;

&lt;p&gt;Thanks,&lt;br&gt;
Raghvendra Awasthi&lt;/p&gt;

</description>
      <category>monorepo</category>
      <category>mern</category>
      <category>lerna</category>
      <category>containerization</category>
    </item>
  </channel>
</rss>
