<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Shubham Tech. Blog's]]></title><description><![CDATA[Problem Solver | Currently Working As a Full Stack Developer, Community Leader At @Dev_Matrix | Previously Contributor at @RealDevSquad, @TeamShiksha]]></description><link>https://blog.realdev.club</link><generator>RSS for Node</generator><lastBuildDate>Sun, 31 May 2026 09:35:19 GMT</lastBuildDate><atom:link href="https://blog.realdev.club/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Building a ShelfLife Household Inventory Tracker — Frontend ]]></title><description><![CDATA[In the previous blog, we designed and built the backend architecture for ShelfLife — a collaborative household inventory tracking application focused on reducing food waste.
Backend Blog: https://shub]]></description><link>https://blog.realdev.club/building-a-shelflife-household-inventory-tracker-frontend</link><guid isPermaLink="true">https://blog.realdev.club/building-a-shelflife-household-inventory-tracker-frontend</guid><category><![CDATA[React]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[react-router-dom]]></category><category><![CDATA[FrontendArchitecture]]></category><category><![CDATA[routing]]></category><category><![CDATA[lazy loading]]></category><category><![CDATA[developer experience]]></category><dc:creator><![CDATA[Shubham Kumar Singh]]></dc:creator><pubDate>Wed, 20 May 2026 16:56:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/1b66e1fe-26bb-4815-b8c5-51b8a769f39f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the previous blog, we designed and built the backend architecture for ShelfLife — a collaborative household inventory tracking application focused on reducing food waste.</p>
<p>Backend Blog: <a href="https://shubhamsinghbundela.hashnode.dev/building-a-shelflife-household-inventory-tracker-backend">https://shubhamsinghbundela.hashnode.dev/building-a-shelflife-household-inventory-tracker-backend</a></p>
<p>Now it’s time to build the frontend application that users will interact with daily.</p>
<hr />
<h3>Why ShelfLife?</h3>
<p>In many shared households:</p>
<ul>
<li><p>Multiple people buy groceries</p>
</li>
<li><p>Items are stored in different places</p>
</li>
<li><p>Expiry dates get ignored</p>
</li>
<li><p>Food gets wasted</p>
</li>
</ul>
<p>ShelfLife aims to solve this problem collaboratively.</p>
<hr />
<h3>Step 1: Setting Up the Project with Vite</h3>
<p>We start by creating the React application using Vite.</p>
<p>Why Vite?</p>
<p>Because modern frontend development requires fast feedback loops and better developer experience.</p>
<p>Compared to older tooling like Create React App, Vite provides:</p>
<ul>
<li><p>Extremely fast startup time</p>
</li>
<li><p>Instant Hot Module Replacement (HMR)</p>
</li>
<li><p>Lightweight configuration</p>
</li>
<li><p>Faster build.</p>
</li>
</ul>
<hr />
<p><strong>Create the project:</strong></p>
<pre><code class="language-javascript">npm create vite@latest
</code></pre>
<hr />
<p><strong>Start the development server:</strong></p>
<pre><code class="language-plaintext">npm run dev
</code></pre>
<hr />
<h3>Step2: Deciding the Folder Structure</h3>
<p>One of the most important frontend decisions is folder organization.</p>
<p>I followed ideas inspired by <a href="https://www.joshwcomeau.com/react/file-structure/">https://www.joshwcomeau.com/react/file-structure/</a> and adapted them for this project.</p>
<p>Current structure:</p>
<pre><code class="language-plaintext">src/
 ├── assets/
 ├── components/
 │    ├── auth/
 │    │     └── Login.jsx
 │    │
 │    ├── dashboard/
 │    │     └── Dashboard.jsx
 │
 ├── routes/
 │    └── index.js
 │
 ├── App.jsx
 └── main.jsx
</code></pre>
<p><strong>Why This Structure?</strong></p>
<p>Instead of grouping everything by file type only, I grouped components by feature.</p>
<p><strong>Benefits:</strong></p>
<ul>
<li><p>Better scalability</p>
</li>
<li><p>Components remain discoverable</p>
</li>
<li><p>Features stay isolated</p>
</li>
</ul>
<p>For example:</p>
<pre><code class="language-plaintext">components/auth
</code></pre>
<p>contains all authentication-related components.</p>
<p>Similarly:</p>
<pre><code class="language-plaintext">components/dashboard
</code></pre>
<p>contains dashboard-related components.</p>
<p>This structure becomes extremely helpful as the application grows.</p>
<hr />
<h3><strong>Step3 : Understanding</strong> <code>@</code> <strong>Alias Imports in React + Vite</strong></h3>
<p>In this project, I’m using the <code>@</code> alias everywhere instead of relative paths, so before moving forward, let’s understand <strong>what</strong> <code>@</code> <strong>actually means ?</strong></p>
<p><code>@</code> is simply an alias that points to the <code>src</code> folder.</p>
<p>Meaning:<br /><code>@</code> -&gt; <code>src</code></p>
<p>So:</p>
<pre><code class="language-plaintext">@/components/auth/Login
</code></pre>
<p>actually means:</p>
<pre><code class="language-plaintext">src/components/auth/Login
</code></pre>
<p><strong>Why Use Alias Imports?</strong></p>
<p>Without aliases, imports can become messy very quickly.</p>
<p>Example:</p>
<pre><code class="language-plaintext">../../../components/auth/Login
</code></pre>
<p>As projects grow, relative paths become difficult to read and maintain.</p>
<p>Using aliases keeps imports clean and predictable:</p>
<pre><code class="language-plaintext">@/components/auth/Login
</code></pre>
<p>Benefits:</p>
<ul>
<li><p>Cleaner imports</p>
</li>
<li><p>Easier refactoring</p>
</li>
<li><p>Better readability</p>
</li>
<li><p>Simpler navigation in large projects</p>
</li>
</ul>
<p><strong>Configuring</strong> <code>@</code> <strong>Alias in Vite</strong></p>
<p>To make alias imports work, we need to configure Vite.</p>
<p>Inside:</p>
<pre><code class="language-plaintext">vite.config.js
</code></pre>
<p>add:</p>
<pre><code class="language-javascript">import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import path from "path";

export default defineConfig({
  plugins: [react()],

  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
});
</code></pre>
<p><strong>Understanding the Configuration</strong></p>
<pre><code class="language-plaintext">"@": path.resolve(__dirname, "./src")
</code></pre>
<p>means:</p>
<p>Whenever React sees <code>"@",</code><br />replace it with the src folder path.</p>
<p>So:</p>
<pre><code class="language-plaintext">@/components/dashboard/Dashboard
</code></pre>
<p>becomes:</p>
<pre><code class="language-plaintext">src/components/dashboard/Dashboard
</code></pre>
<p>automatically.</p>
<hr />
<h3>Step 4: Installing React Router DOM</h3>
<p>Since ShelfLife is a multi-page application, we need routing.</p>
<p>Install React Router DOM:</p>
<pre><code class="language-plaintext">npm install react-router-dom
</code></pre>
<p><a href="https://reactrouter.com/">React Router DOM</a> helps us create client-side routing without full page refreshes.</p>
<p>Example:</p>
<pre><code class="language-plaintext">/         -&gt; Join Household Page
/items    -&gt; Items Dashboard
</code></pre>
<p>Before setting up routes, I had to decide which routing approach I wanted to use.</p>
<p>React Router provides multiple routing modes, but for ShelfLife I decided to use:</p>
<pre><code class="language-plaintext">&lt;BrowserRouter&gt;
</code></pre>
<p>which is part of React Router’s Declarative Mode.</p>
<hr />
<p><strong>Why I Chose BrowserRouter</strong></p>
<p>ShelfLife is primarily a dashboard-style application where most pages are accessed after authentication.</p>
<p>For this kind of application, client-side routing works extremely well because:</p>
<ul>
<li><p>Navigation feels instant</p>
</li>
<li><p>No full page refreshes</p>
</li>
<li><p>Simpler project structure</p>
</li>
<li><p>Easier mental model</p>
</li>
<li><p>Perfect for SPA (Single Page Applications)</p>
</li>
</ul>
<p>I did not need advanced framework features like:</p>
<ul>
<li><p>Server Side Rendering (SSR)</p>
</li>
<li><p>Route loaders/actions</p>
</li>
<li><p>Full-stack routing</p>
</li>
</ul>
<p>So using <code>BrowserRouter</code> kept the architecture simple and predictable.</p>
<p>The routing flow looks like:</p>
<pre><code class="language-plaintext">BrowserRouter
   ↓
Routes
   ↓
Outlet
   ↓
Child Pages
</code></pre>
<hr />
<h3>Step 5: Setting Up Centralized Routing</h3>
<p>I decided to use centralized route configuration.</p>
<p>Why?</p>
<p>Because I wanted:</p>
<ul>
<li><p>All routes visible in one place</p>
</li>
<li><p>Simpler mental model</p>
</li>
<li><p>Predictable architecture</p>
</li>
<li><p>Better scalability</p>
</li>
</ul>
<hr />
<p><strong>Creating the Route Configuration</strong></p>
<p>Inside:</p>
<pre><code class="language-plaintext">src/routes/index.js
</code></pre>
<p>we create all application routes.</p>
<pre><code class="language-javascript">import { lazy } from "react";

export const routes = [
  {
    path: "",
    component: lazy(() =&gt;
      import("@/components/joinHouseHold/JoinHouseHold")
    ),
  },
  {
    path: "/items",
    component: lazy(() =&gt;
      import("@/components/dashboard/items")
    ),
  },
];
</code></pre>
<p>Understanding lazy()</p>
<pre><code class="language-plaintext">lazy(() =&gt; import(...))
</code></pre>
<p>is React’s implementation of lazy loading.</p>
<p>Lazy loading is a performance optimization technique where components are downloaded only when they are actually needed instead of loading everything upfront.</p>
<p>This means components load only when required.</p>
<p>Example:</p>
<ul>
<li><p><code>JoinHouseHold</code> component loads only when user visits <code>/</code></p>
</li>
<li><p><code>Items</code> component loads only when user visits <code>/items</code></p>
</li>
</ul>
<p>Benefits:</p>
<ul>
<li><p>Smaller initial bundle size</p>
</li>
<li><p>Better performance</p>
</li>
<li><p>Faster initial page load</p>
</li>
<li><p>Reduced unnecessary JavaScript downloads</p>
</li>
</ul>
<hr />
<h3>Step 6: Creating a Shared Layout Route</h3>
<p>Most pages inside ShelfLife share the same layout structure:</p>
<ul>
<li><p>Navbar</p>
</li>
<li><p>Main Content</p>
</li>
<li><p>Footer</p>
</li>
</ul>
<p>Instead of repeating these components inside every page, I created a reusable layout route.</p>
<p>Inside:</p>
<pre><code class="language-plaintext">src/components/main/Body.jsx
</code></pre>
<pre><code class="language-javascript">import { Outlet } from "react-router-dom";
import Footer from "./Footer";
import Navbar from "./Navbar";

const Body = () =&gt; {
  return (
    &lt;div
      style={{
        minHeight: "100vh",
        display: "flex",
        flexDirection: "column",
      }}
    &gt;
      &lt;Navbar /&gt;

      &lt;div style={{ flex: 1 }}&gt;
        &lt;Outlet /&gt;
      &lt;/div&gt;

      &lt;Footer /&gt;
    &lt;/div&gt;
  );
};

export default Body;
</code></pre>
<p><strong>Understanding Outlet</strong></p>
<pre><code class="language-plaintext">&lt;Outlet /&gt;
</code></pre>
<p>is one of the most important concepts in React Router.</p>
<p>It acts as a placeholder where child routes render dynamically.</p>
<p>Example:</p>
<pre><code class="language-plaintext">/        -&gt; JoinHouseHold renders inside Outlet
/items   -&gt; Items page renders inside Outlet
</code></pre>
<p>This allows us to create reusable layouts very easily.</p>
<p>The final layout structure becomes:</p>
<pre><code class="language-plaintext">Navbar
   ↓
Outlet (Dynamic Page Content)
   ↓
Footer
</code></pre>
<hr />
<h3>Step 7: Configuring the App Router</h3>
<p>Inside:</p>
<pre><code class="language-plaintext">src/App.jsx
</code></pre>
<pre><code class="language-javascript">import { Suspense } from "react";
import "@/App.css";

import {
  BrowserRouter,
  Routes,
  Route,
} from "react-router-dom";

import Body from "@/components/main/Body";
import { routes } from "@/routes";

function App() {
  return (
    &lt;BrowserRouter&gt;
      &lt;Suspense fallback={&lt;h1&gt;Loading...&lt;/h1&gt;}&gt;
        &lt;Routes&gt;
          &lt;Route path="/" element={&lt;Body /&gt;}&gt;
            {routes.map((route) =&gt; {
              const Component = route.component;

              return (
                &lt;Route
                  key={route.path}
                  path={route.path}
                  element={&lt;Component /&gt;}
                /&gt;
              );
            })}
          &lt;/Route&gt;
        &lt;/Routes&gt;
      &lt;/Suspense&gt;
    &lt;/BrowserRouter&gt;
  );
}

export default App;
</code></pre>
<hr />
<p><strong>Understanding BrowserRouter</strong></p>
<pre><code class="language-plaintext">&lt;BrowserRouter&gt;
</code></pre>
<p>enables browser-based client-side routing using URLs.</p>
<p>Without it, React Router cannot manage page navigation.</p>
<hr />
<p><strong>Understanding Routes</strong></p>
<pre><code class="language-plaintext">&lt;Routes&gt;
</code></pre>
<p>acts as a container for all route definitions.</p>
<p>Understanding Route</p>
<pre><code class="language-javascript">&lt;Route path="/items" element={&lt;Items /&gt;} /&gt;
</code></pre>
<p>means:</p>
<pre><code class="language-plaintext">/items -&gt; render Items component
</code></pre>
<hr />
<p><strong>Understanding Suspense</strong></p>
<p>Since we are using lazy loading, React needs a fallback UI while components are downloading.</p>
<pre><code class="language-javascript">&lt;Suspense fallback={&lt;h1&gt;Loading...&lt;/h1&gt;}&gt;
</code></pre>
<p>shows loading content until the lazy-loaded component becomes available.</p>
<hr />
<h3>Step 8: Setting Up Material UI</h3>
<p>Now that the routing architecture was ready, the next step was building the actual user interface for ShelfLife.</p>
<p>For the frontend UI library, I decided to use Material UI.</p>
<hr />
<p><strong>Why Material UI?</strong></p>
<p>Because ShelfLife is a dashboard-oriented application and Material UI provides:</p>
<ul>
<li><p>Prebuilt production-ready components</p>
</li>
<li><p>Consistent design system</p>
</li>
<li><p>Faster UI development</p>
</li>
<li><p>Excellent form components</p>
</li>
<li><p>Responsive layouts</p>
</li>
<li><p>Theme customization support</p>
</li>
</ul>
<p>This allows us to focus more on application logic instead of spending excessive time building UI components from scratch.</p>
<hr />
<p><strong>Installing Material UI</strong></p>
<p>To install Material UI and its required styling dependencies:</p>
<pre><code class="language-plaintext">npm install @mui/material @emotion/react @emotion/styled
</code></pre>
<p><strong>Understanding Emotion</strong></p>
<p>You may notice two additional packages:</p>
<pre><code class="language-plaintext">@emotion/react
@emotion/styled
</code></pre>
<p>Material UI uses Emotion internally as its styling engine.</p>
<p>Emotion helps Material UI:</p>
<ul>
<li><p>Generate dynamic styles</p>
</li>
<li><p>Handle component-based styling</p>
</li>
<li><p>Support theming</p>
</li>
<li><p>Optimize CSS performance</p>
</li>
</ul>
<p>Without these packages, Material UI components will not work properly.</p>
<hr />
<p><strong>Creating the Global Theme</strong></p>
<p>Since ShelfLife is focused on freshness tracking and inventory management, I wanted the application to have a green-themed design system representing freshness and sustainability.</p>
<p>Inside:</p>
<pre><code class="language-plaintext">src/theme.js
</code></pre>
<p>we create a global Material UI theme.</p>
<pre><code class="language-javascript">import { createTheme } from "@mui/material/styles";

const theme = createTheme({
  palette: {
    primary: {
      main: "#2e7d32",
    },

    secondary: {
      main: "#66bb6a",
    },

    background: {
      default: "#f4f9f4",
      paper: "#ffffff",
    },
  },
});

export default theme;
</code></pre>
<p><strong>Understanding createTheme()</strong></p>
<pre><code class="language-plaintext">createTheme()
</code></pre>
<p>creates a centralized design system for the application.</p>
<p>This allows all Material UI components to automatically use the same:</p>
<ul>
<li><p>Colors</p>
</li>
<li><p>Typography</p>
</li>
<li><p>Spacing system</p>
</li>
<li><p>Component styling</p>
</li>
</ul>
<p>For example:</p>
<pre><code class="language-plaintext">&lt;Button color="primary" /&gt;
</code></pre>
<p>will automatically use:</p>
<pre><code class="language-plaintext">#2e7d32
</code></pre>
<p>from the theme configuration.</p>
<p><strong>Configuring ThemeProvider</strong></p>
<p>To apply the theme globally, we wrap the application with:</p>
<pre><code class="language-javascript">&lt;ThemeProvider&gt;
</code></pre>
<p>Inside:</p>
<pre><code class="language-plaintext">src/main.jsx
</code></pre>
<pre><code class="language-javascript">import React from "react";
import ReactDOM from "react-dom/client";

import App from "./App";

import {
  ThemeProvider,
} from "@mui/material/styles";

import CssBaseline from "@mui/material/CssBaseline";

import theme from "./theme";

ReactDOM.createRoot(
  document.getElementById("root")
).render(
  &lt;ThemeProvider theme={theme}&gt;
    &lt;CssBaseline /&gt;
    &lt;App /&gt;
  &lt;/ThemeProvider&gt;
);
</code></pre>
<p><strong>Understanding CssBaseline</strong></p>
<pre><code class="language-javascript">&lt;CssBaseline /&gt;
</code></pre>
<p>acts as a global CSS reset for Material UI applications.</p>
<p>It helps:</p>
<ul>
<li><p>Remove inconsistent browser styling</p>
</li>
<li><p>Apply cleaner default styles</p>
</li>
<li><p>Normalize margins and typography</p>
</li>
<li><p>Apply the theme background globally</p>
</li>
</ul>
<hr />
<h3>Step 9: Setting Up Global State Management with Redux Toolkit</h3>
<p>As ShelfLife started growing, managing shared application state became important.</p>
<p>For example:</p>
<ul>
<li><p>Logged-in user information</p>
</li>
<li><p>Authentication state</p>
</li>
<li><p>Household data</p>
</li>
<li><p>Shared inventory information</p>
</li>
</ul>
<p>Passing this data manually through props across multiple components would quickly become difficult to maintain.</p>
<p>To solve this, I decided to use Redux Toolkit.</p>
<hr />
<p><strong>Installing Redux Toolkit</strong></p>
<p>To set up Redux Toolkit, install:</p>
<pre><code class="language-plaintext">npm install @reduxjs/toolkit react-redux
</code></pre>
<p><strong>Understanding the Packages</strong></p>
<pre><code class="language-plaintext">@reduxjs/toolkit
</code></pre>
<p>provides the modern Redux APIs for creating stores and slices.</p>
<pre><code class="language-plaintext">react-redux
</code></pre>
<p>connects Redux with React components.</p>
<p>I followed the official Redux Toolkit Quick Start documentation:</p>
<p><a href="https://redux-toolkit.js.org/tutorials/quick-start?utm_source=chatgpt.com">Redux Toolkit Quick Start Guide</a></p>
<hr />
<p><strong>Why I Introduced Redux Toolkit</strong></p>
<p>As the application started interacting with the backend API, multiple components needed access to the same server-side data.</p>
<p>For example:</p>
<ul>
<li><p>Logged-in user information</p>
</li>
<li><p>Household details</p>
</li>
<li><p>Shared inventory items</p>
</li>
<li><p>Authentication state</p>
</li>
</ul>
<p>Without global state management, this data would need to be passed manually through props across many components, which quickly becomes difficult to maintain.</p>
<p>To solve this, I introduced Redux Toolkit as a centralized global store for managing server-driven application state.</p>
<p>The idea is simple:</p>
<pre><code class="language-plaintext">Backend API
    ↓
Frontend fetches data
    ↓
Redux Store
    ↓
Any Component Can Access It
</code></pre>
<p>This allows data coming from the server to be stored centrally and shared across the entire application efficiently.</p>
<p>Benefits of This Approach</p>
<ul>
<li><p>Avoids prop drilling</p>
</li>
<li><p>Centralized application state</p>
</li>
<li><p>Easier data sharing between components</p>
</li>
</ul>
<hr />
<p><strong>Creating the Global Store</strong></p>
<p>Inside:</p>
<pre><code class="language-plaintext">src/store/appStore.js
</code></pre>
<p>I created the main Redux store.</p>
<pre><code class="language-javascript">import { configureStore } from "@reduxjs/toolkit";
import userReducer from "./userSlice";

const appStore = configureStore({
  reducer: {
    user: userReducer,
  },
});

export default appStore;
</code></pre>
<p>The store acts as a centralized container for the entire application state.</p>
<p>Current store structure:</p>
<pre><code class="language-plaintext">store
 └── user
</code></pre>
<hr />
<p><strong>Creating the User Slice</strong></p>
<p>Redux Toolkit organizes state into smaller isolated pieces called slices.</p>
<p>Inside:</p>
<pre><code class="language-plaintext">src/store/userSlice.js
</code></pre>
<p>I created the user slice.</p>
<pre><code class="language-javascript">import { createSlice } from "@reduxjs/toolkit";

const userSlice = createSlice({
  name: "user",
  initialState: null,

  reducers: {
    addUser: (state, action) =&gt; {
      return action.payload;
    },

    removeUser: (state, action) =&gt; {
      return null;
    },
  },
});

export const { addUser, removeUser } = userSlice.actions;

export default userSlice.reducer;
</code></pre>
<hr />
<p><strong>Providing the Redux Store to the Application</strong></p>
<p>After creating the store, the next step was making it accessible throughout the entire application.</p>
<p>Inside:</p>
<pre><code class="language-plaintext">src/App.jsx
</code></pre>
<p>I wrapped the application using Redux Provider.</p>
<pre><code class="language-javascript">import { Provider } from "react-redux";
import appStore from "@/store/appStore";

&lt;Provider store={appStore}&gt;
  &lt;BrowserRouter&gt;
    &lt;App /&gt;
  &lt;/BrowserRouter&gt;
&lt;/Provider&gt;
</code></pre>
<p><strong>Understanding Provider</strong></p>
<pre><code class="language-plaintext">&lt;Provider&gt;
</code></pre>
<p>connects React with the Redux store.</p>
<p>Without it, components cannot access global state.</p>
<p>Once wrapped, any component inside the application can:</p>
<ul>
<li><p>Read state using <code>useSelector</code></p>
</li>
<li><p>Update state using <code>useDispatch</code></p>
</li>
</ul>
<hr />
<h3>Step 10: Setting Up Axios</h3>
<p>Now let’s connect the frontend with the backend</p>
<p>Axios helps simplify:</p>
<ul>
<li><p>API requests</p>
</li>
<li><p>Request configuration</p>
</li>
<li><p>Error handling</p>
</li>
<li><p>Interceptors</p>
</li>
</ul>
<hr />
<p><strong>Installing Axios</strong></p>
<p>Install Axios:</p>
<pre><code class="language-plaintext">npm install axios
</code></pre>
<hr />
<p><strong>Creating Environment Variables</strong></p>
<p>Instead of hardcoding backend URLs, I used environment variables.</p>
<p>Inside:</p>
<pre><code class="language-plaintext">.env
</code></pre>
<pre><code class="language-plaintext">VITE_API_URL=http://localhost:5000/api
</code></pre>
<p>Since this project uses Vite, environment variables must start with:</p>
<pre><code class="language-plaintext">VITE_
</code></pre>
<p>Otherwise, Vite will not expose them to the frontend application.</p>
<hr />
<p><strong>Creating the Axios Instance</strong></p>
<p>Inside:</p>
<pre><code class="language-plaintext">src/api/axios.js
</code></pre>
<pre><code class="language-javascript">import axios from "axios";

const api = axios.create({
  baseURL: import.meta.env.VITE_API_URL,

  withCredentials: true,

  timeout: 10000,
});

export default api;
</code></pre>
<p><strong>Understanding the Configuration</strong><br /><code>axios.create()</code></p>
<p>Creates a reusable Axios instance.</p>
<p>Instead of repeating configuration in every API request, we centralize everything in one place.</p>
<hr />
<h3><code>baseURL</code></h3>
<p>Sets the backend base URL globally.</p>
<p>So this:</p>
<pre><code class="language-plaintext">api.post("/auth/login")
</code></pre>
<p>automatically becomes:</p>
<pre><code class="language-plaintext">http://localhost:5000/api/auth/login
</code></pre>
<hr />
<h3><code>withCredentials: true</code></h3>
<p>Allows cookies to be sent automatically with requests.</p>
<p>This becomes important for refresh token authentication because refresh tokens are stored securely inside HTTP-only cookies.</p>
<hr />
<h3><code>timeout: 10000</code></h3>
<p>Axios will wait a maximum of:</p>
<pre><code class="language-plaintext">10000 milliseconds = 10 seconds
</code></pre>
<p>before aborting the request.</p>
<p>This prevents requests from hanging forever if the server does not respond.</p>
<hr />
<p><strong>Setting Up Token Utilities</strong></p>
<p>After login, the backend returns an access token.</p>
<p>To manage tokens cleanly, I created utility functions.</p>
<p>Inside:</p>
<pre><code class="language-plaintext">src/utils/token.js
</code></pre>
<pre><code class="language-javascript">export const getAccessToken = () =&gt;
  localStorage.getItem("accessToken");

export const setAccessToken = (token) =&gt;
  localStorage.setItem(
    "accessToken",
    token
  );

export const clearTokens = () =&gt; {
  localStorage.removeItem(
    "accessToken"
  );
};
</code></pre>
<p><strong>Why Create Token Utilities?</strong></p>
<p>Instead of directly using:</p>
<pre><code class="language-plaintext">localStorage.getItem()
</code></pre>
<p>everywhere, utility functions help:</p>
<ul>
<li><p>Centralize token logic</p>
</li>
<li><p>Improve readability</p>
</li>
<li><p>Reduce duplication</p>
</li>
</ul>
<hr />
<p><strong>Setting Up Axios Interceptors</strong></p>
<p>Authentication systems usually require automatic token handling.</p>
<p>To solve this, I used Axios interceptors.</p>
<p>Inside:</p>
<pre><code class="language-plaintext">src/api/axiosInterceptor.js
</code></pre>
<pre><code class="language-js">import { refreshToken } from "./auth.api";
import api from "./axios";
import { getAccessToken, setAccessToken, clearTokens } from "@/utils/token";

// REQUEST INTERCEPTOR
api.interceptors.request.use((config) =&gt; {
  const token = getAccessToken();

  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }

  return config;
});

// RESPONSE INTERCEPTOR
api.interceptors.response.use(
  (response) =&gt; response,

  async (error) =&gt; {
    const originalRequest = error.config;

    if (error.response?.status === 401 &amp;&amp; !originalRequest._retry) {
      originalRequest._retry = true;

      try {
        const res = await refreshToken();
        console.log(res);
        const newAccessToken = res.data.data.accessToken;

        setAccessToken(newAccessToken);

        api.defaults.headers.common.Authorization = `Bearer ${newAccessToken}`;

        originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;

        return api(originalRequest);
      } catch (error) {
        clearTokens();

        return Promise.reject(error);
      }
    }

    return Promise.reject(error);
  },
);
</code></pre>
<hr />
<p><strong>Importing the Axios Interceptor</strong></p>
<p>Axios interceptors only work after the interceptor file is imported.</p>
<p>So we need to import it once inside the main application entry file.</p>
<p>Inside:</p>
<pre><code class="language-plaintext">src/main.jsx
</code></pre>
<pre><code class="language-js">import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";

import "./api/axiosInterceptor";

ReactDOM.createRoot(
  document.getElementById("root")
).render(
  &lt;React.StrictMode&gt;
    &lt;App /&gt;
  &lt;/React.StrictMode&gt;
);
</code></pre>
<p><strong>Why Is This Import Important?</strong></p>
<p>The interceptor file does not export a component.</p>
<p>Its purpose is to execute this code immediately:</p>
<pre><code class="language-plaintext">api.interceptors.request.use()
</code></pre>
<p>and</p>
<pre><code class="language-plaintext">api.interceptors.response.use()
</code></pre>
<p>As soon as the file is imported, Axios registers both interceptors globally.</p>
<p>Without importing this file, the interceptors will never run.</p>
<p>That means:</p>
<ul>
<li><p>Tokens will not attach automatically</p>
</li>
<li><p>Refresh token logic will not work</p>
</li>
<li><p>Expired access tokens will not refresh automatically</p>
</li>
</ul>
<hr />
<h3>Step 11: Building the Shared Navigation Layout</h3>
<p>Most pages inside ShelfLife share a common structure:</p>
<ul>
<li><p>Navbar</p>
</li>
<li><p>Dynamic Page Content</p>
</li>
<li><p>Footer</p>
</li>
</ul>
<p>So instead of repeating them on every page, I created reusable layout components.</p>
<p><strong>Creating the Navbar</strong></p>
<p>Inside:</p>
<pre><code class="language-plaintext">src/components/main/Navbar.jsx
</code></pre>
<pre><code class="language-js">import * as React from "react";
import AppBar from "@mui/material/AppBar";
import Box from "@mui/material/Box";
import Toolbar from "@mui/material/Toolbar";
import IconButton from "@mui/material/IconButton";
import Typography from "@mui/material/Typography";
import Menu from "@mui/material/Menu";
import MenuIcon from "@mui/icons-material/Menu";
import Container from "@mui/material/Container";
import Avatar from "@mui/material/Avatar";
import Button from "@mui/material/Button";
import Tooltip from "@mui/material/Tooltip";
import MenuItem from "@mui/material/MenuItem";
import AdbIcon from "@mui/icons-material/Adb";
import { useDispatch, useSelector } from "react-redux";
import { logout } from "./api";
import { clearTokens } from "@/utils/token";
import { useNavigate } from "react-router-dom";
import { removeUser } from "@/store/userSlice";
import { toast } from "react-toastify";

const pages = ["Products", "Pricing", "Blog"];
const settings = ["Profile", "Logout"];

const Navbar = ({ setOpenAuthDialog }) =&gt; {
  const user = useSelector((store) =&gt; store.user);
  const dispatch = useDispatch();
  const navigate = useNavigate();
  const [anchorElNav, setAnchorElNav] = React.useState(null);
  const [anchorElUser, setAnchorElUser] = React.useState(null);

  const handleOpenNavMenu = (event) =&gt; {
    setAnchorElNav(event.currentTarget);
  };
  const handleOpenUserMenu = (event) =&gt; {
    setAnchorElUser(event.currentTarget);
  };

  const handleCloseNavMenu = () =&gt; {
    setAnchorElNav(null);
  };

  const handleCloseUserMenu = () =&gt; {
    setAnchorElUser(null);
  };

  const handleSettingsClick = async (setting) =&gt; {
    handleCloseUserMenu();

    if (setting === "Logout") {
      try {
        await logout(); // API call

        clearTokens();

        // remove redux user also
        dispatch(removeUser());

        toast.success("Logout Successful");
        navigate("/");
      } catch (error) {
        console.log(error);
      }
    }
  };

  return (
    &lt;AppBar position="static"&gt;
      &lt;Container maxWidth="xl"&gt;
        &lt;Toolbar
          disableGutters
          sx={{
            minHeight: "56px !important",
          }}
        &gt;
          &lt;Typography
            variant="h6"
            noWrap
            sx={{
              mr: 2,
              display: { xs: "none", md: "flex" },
              fontFamily: "monospace",
              fontWeight: 700,
              letterSpacing: ".3rem",
              color: "inherit",
              textDecoration: "none",
            }}
          &gt;
            ShelfLife
          &lt;/Typography&gt;

          &lt;Typography
            variant="h5"
            noWrap
            component="a"
            href="#app-bar-with-responsive-menu"
            sx={{
              mr: 2,
              display: { xs: "flex", md: "none" },
              flexGrow: 1,
              fontFamily: "monospace",
              fontWeight: 700,
              letterSpacing: ".3rem",
              color: "inherit",
              textDecoration: "none",
            }}
          &gt;
            ShelfLife
          &lt;/Typography&gt;
          &lt;Box
            sx={{ flexGrow: 1, display: "flex", justifyContent: "flex-end" }}
          &gt;
            {!user ? (
              &lt;Button
                variant="contained"
                onClick={() =&gt; setOpenAuthDialog(true)}
              &gt;
                Login
              &lt;/Button&gt;
            ) : (
              &lt;&gt;
                &lt;Tooltip title="Open settings"&gt;
                  &lt;IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}&gt;
                    &lt;Avatar
                      alt="Remy Sharp"
                      src="/static/images/avatar/2.jpg"
                    /&gt;
                  &lt;/IconButton&gt;
                &lt;/Tooltip&gt;
                &lt;Menu
                  sx={{ mt: "45px" }}
                  id="menu-appbar"
                  anchorEl={anchorElUser}
                  anchorOrigin={{
                    vertical: "top",
                    horizontal: "right",
                  }}
                  keepMounted
                  transformOrigin={{
                    vertical: "top",
                    horizontal: "right",
                  }}
                  open={Boolean(anchorElUser)}
                  onClose={handleCloseUserMenu}
                &gt;
                  {settings.map((setting) =&gt; (
                    &lt;MenuItem
                      key={setting}
                      onClick={() =&gt; handleSettingsClick(setting)}
                    &gt;
                      &lt;Typography sx={{ textAlign: "center" }}&gt;
                        {setting}
                      &lt;/Typography&gt;
                    &lt;/MenuItem&gt;
                  ))}
                &lt;/Menu&gt;
              &lt;/&gt;
            )}
          &lt;/Box&gt;
        &lt;/Toolbar&gt;
      &lt;/Container&gt;
    &lt;/AppBar&gt;
  );
};

export default Navbar;
</code></pre>
<p>I created the application navbar.</p>
<p>The navbar has two authentication states:</p>
<pre><code class="language-plaintext">User Not Logged In
        ↓
Show Login Button

User Logged In
        ↓
Show Profile Menu
</code></pre>
<p>This behavior is controlled using Redux global state.</p>
<pre><code class="language-plaintext">const user = useSelector((store) =&gt; store.user);
</code></pre>
<p><strong>Understanding useSelector()</strong></p>
<pre><code class="language-plaintext">useSelector()
</code></pre>
<p>allows React components to access Redux store data.</p>
<p>If no user exists:</p>
<pre><code class="language-plaintext">&lt;Button onClick={() =&gt; setOpenAuthDialog(true)}&gt;
  Login
&lt;/Button&gt;
</code></pre>
<p>the Login button appears.</p>
<p>Otherwise:</p>
<pre><code class="language-plaintext">&lt;Avatar /&gt;
</code></pre>
<p>shows the authenticated user profile menu.</p>
<p>This creates a dynamic authentication-aware navigation system.</p>
<p><strong>Handling Logout</strong></p>
<p>Inside the profile menu, I added logout functionality.</p>
<pre><code class="language-plaintext">dispatch(removeUser());
</code></pre>
<p>removes the authenticated user from Redux state.</p>
<p>At the same time:</p>
<pre><code class="language-plaintext">clearTokens();
</code></pre>
<p>removes stored access tokens from localStorage.</p>
<p>This instantly updates the entire UI because Redux state changes automatically trigger component re-renders.</p>
<img src="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/278b82ce-484c-486f-a759-b7227c0ee315.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/686f888b-7c2b-4e7d-a4eb-921ee7acd6be.png" alt="" style="display:block;margin:0 auto" />

<p><img src="align=%22center%22" alt="" /></p>
<hr />
<p><strong>Creating the Footer</strong></p>
<p>Inside:</p>
<pre><code class="language-plaintext">src/components/main/Footer.jsx
</code></pre>
<p>I created a shared footer component.</p>
<pre><code class="language-js">import { Box, Container, Typography } from "@mui/material";

const Footer = () =&gt; {
  return (
    &lt;Box
      component="footer"
      sx={{
        mt: "auto",
        py: 2,
        textAlign: "center",
        bgcolor: "primary.main",
        color: "white",
      }}
    &gt;
      &lt;Container maxWidth="xl"&gt;
        &lt;Typography variant="body2"&gt;
          © 2026 ShelfLife. All rights reserved.
        &lt;/Typography&gt;
      &lt;/Container&gt;
    &lt;/Box&gt;
  );
};

export default Footer;
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/13b29a49-d3b5-4fff-9217-54a83fad3070.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>Step12: Connecting Navbar with the Login Dialog</h3>
<p>Before building the Login component, I updated the shared layout component so the Navbar could control the authentication dialog.</p>
<p>Inside:</p>
<pre><code class="language-plaintext">src/components/main/Body.jsx
</code></pre>
<p>I added dialog state management.</p>
<pre><code class="language-plaintext">const [openAuthDialog, setOpenAuthDialog] =
  useState(false);
</code></pre>
<p>This state controls whether the authentication modal is open or closed.</p>
<hr />
<p><strong>Passing State to Navbar</strong></p>
<pre><code class="language-plaintext">&lt;Navbar
  setOpenAuthDialog={
    setOpenAuthDialog
  }
/&gt;
</code></pre>
<p>The <code>setOpenAuthDialog</code> function is passed to the Navbar component as a prop.</p>
<p>This allows the Navbar to open the Login dialog whenever the user clicks the Login button.</p>
<p>Example inside Navbar:</p>
<pre><code class="language-javascript">&lt;Button
  variant="contained"
  onClick={() =&gt;
    setOpenAuthDialog(true)
  }
&gt;
  Login
&lt;/Button&gt;
</code></pre>
<p>When clicked:</p>
<pre><code class="language-plaintext">setOpenAuthDialog(true);
</code></pre>
<p>opens the authentication modal instantly.</p>
<hr />
<p><strong>Sharing Dialog State with Nested Routes</strong></p>
<pre><code class="language-javascript">&lt;Outlet
  context={{
    openAuthDialog,
    setOpenAuthDialog,
  }}
/&gt;
</code></pre>
<p>Using React Router's <code>Outlet context</code>, nested pages can also access:</p>
<ul>
<li><p><code>openAuthDialog</code></p>
</li>
<li><p><code>setOpenAuthDialog</code></p>
</li>
</ul>
<p>This makes the authentication modal globally accessible across shared layout routes.</p>
<hr />
<p><strong>Authentication Dialog Flow</strong></p>
<pre><code class="language-plaintext">User Clicks Login
        ↓
Navbar Updates State
        ↓
openAuthDialog = true
        ↓
Login Component Opens
</code></pre>
<p>This creates a centralized modal authentication system controlled from the shared layout.</p>
<hr />
<h3>Step 13: Building the Signup Component</h3>
<p>Inside:</p>
<pre><code class="language-plaintext">src/components/auth/Signup.jsx
</code></pre>
<p>we create the signup form for new users.</p>
<p>This component handles:</p>
<ul>
<li><p>User registration form</p>
</li>
<li><p>Form validation</p>
</li>
<li><p>API integration</p>
</li>
<li><p>Error</p>
</li>
</ul>
<p><strong>Creating the Signup Component</strong></p>
<p>Inside</p>
<pre><code class="language-js">src/components/auth/Signup.jsx
</code></pre>
<pre><code class="language-javascript">import { useForm } from "react-hook-form";

import CloseIcon from "@mui/icons-material/Close";

import {
  Button,
  Dialog,
  DialogContent,
  DialogTitle,
  IconButton,
  TextField,
  Typography,
  Box,
} from "@mui/material";

import { toast } from "react-toastify";

import { signupUser } from "./api";

const Signup = ({
  open,
  handleClose,
  openLogin,
}) =&gt; {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm();

  const onSubmit = async (data) =&gt; {
    try {
      const res =
        await signupUser(data);

      if (res.success) {
        toast.success(
          "Signup Successful"
        );

        handleClose();

        openLogin();
      }
    } catch (err) {
      toast.error("Signup Failed");

      console.error(err);
    }
  };

  return (
    &lt;Dialog
      open={open}
      onClose={handleClose}
      fullWidth
      maxWidth="xs"
      disableScrollLock
    &gt;
      &lt;DialogTitle
        sx={{
          display: "flex",
          justifyContent:
            "space-between",
          alignItems: "center",
        }}
      &gt;
        Create Account

        &lt;IconButton
          onClick={handleClose}
        &gt;
          &lt;CloseIcon /&gt;
        &lt;/IconButton&gt;
      &lt;/DialogTitle&gt;

      &lt;DialogContent&gt;
        &lt;Box
          component="form"
          onSubmit={handleSubmit(
            onSubmit
          )}
        &gt;
          &lt;TextField
            margin="dense"
            label="First Name"
            fullWidth
            {...register(
              "firstName",
              {
                required:
                  "First name is required",
              }
            )}
            error={
              !!errors.firstName
            }
            helperText={
              errors.firstName
                ?.message
            }
          /&gt;

          &lt;TextField
            margin="dense"
            label="Last Name"
            fullWidth
            {...register(
              "lastName",
              {
                required:
                  "Last name is required",
              }
            )}
            error={
              !!errors.lastName
            }
            helperText={
              errors.lastName
                ?.message
            }
          /&gt;

          &lt;TextField
            margin="dense"
            label="Username"
            fullWidth
            {...register(
              "username",
              {
                required:
                  "Username is required",
              }
            )}
            error={
              !!errors.username
            }
            helperText={
              errors.username
                ?.message
            }
          /&gt;

          &lt;TextField
            margin="dense"
            label="Email"
            type="email"
            fullWidth
            {...register("email", {
              required:
                "Email is required",
            })}
            error={!!errors.email}
            helperText={
              errors.email?.message
            }
          /&gt;

          &lt;TextField
            margin="dense"
            label="Phone Number"
            type="tel"
            fullWidth
            {...register(
              "phoneNumber",
              {
                required:
                  "Phone number is required",
              }
            )}
            error={
              !!errors.phoneNumber
            }
            helperText={
              errors.phoneNumber
                ?.message
            }
          /&gt;

          &lt;TextField
            margin="dense"
            label="Password"
            type="password"
            fullWidth
            {...register(
              "password",
              {
                required:
                  "Password is required",

                minLength: {
                  value: 6,
                  message:
                    "Password must be at least 6 characters",
                },
              }
            )}
            error={
              !!errors.password
            }
            helperText={
              errors.password
                ?.message
            }
          /&gt;

          &lt;Typography
            variant="body2"
            sx={{
              mt: 2,
              cursor: "pointer",
              color: "primary.main",
              textAlign: "center",
            }}
            onClick={openLogin}
          &gt;
            Already have an account?
            Login
          &lt;/Typography&gt;

          &lt;Box
            sx={{
              mt: 3,
              display: "flex",
              justifyContent:
                "center",
            }}
          &gt;
            &lt;Button
              type="submit"
              variant="contained"
            &gt;
              Signup
            &lt;/Button&gt;
          &lt;/Box&gt;
        &lt;/Box&gt;
      &lt;/DialogContent&gt;
    &lt;/Dialog&gt;
  );
};

export default Signup;
</code></pre>
<hr />
<p><strong>Creating the Signup API</strong></p>
<p>Inside:</p>
<pre><code class="language-plaintext">src/components/auth/api.js
</code></pre>
<pre><code class="language-javascript">import api from "@/api/axios.js";

// SIGNUP USER
export const signupUser =
  async (data) =&gt; {
    const res = await api.post(
      "/auth/register",
      data
    );

    return res.data;
  };
</code></pre>
<hr />
<p><strong>After successful registration:</strong></p>
<pre><code class="language-plaintext">openLogin();
</code></pre>
<p>automatically opens the login dialog.</p>
<p>This creates a smooth connected authentication experience:</p>
<pre><code class="language-plaintext">Signup
   ↓
Account Created
   ↓
Open Login Dialog
   ↓
Login Dialog
</code></pre>
<p>Instead of forcing users to manually reopen the login modal, the application guides them directly into Login component.</p>
<img src="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/c405c06d-3d95-4fef-a10c-af354519ce06.gif" alt="" style="display:block;margin:0 auto" />

<hr />
<h3><strong>Step 14: Building the Login Component</strong></h3>
<p>Inside:</p>
<pre><code class="language-plaintext">src/components/auth/Login.jsx
</code></pre>
<p>we create the login form.</p>
<pre><code class="language-javascript">import { useForm } from "react-hook-form";
import { useDispatch } from "react-redux";
import { useNavigate } from "react-router-dom";

import {
  Button,
  Dialog,
  DialogContent,
  DialogTitle,
  IconButton,
  TextField,
  Typography,
  Box,
} from "@mui/material";
import { toast } from "react-toastify";

import CloseIcon from "@mui/icons-material/Close";
import { loginUser } from "./api";
import { addUser } from "@/store/userSlice";
import { setAccessToken } from "@/utils/token";

const Login = ({ open, handleClose, openSignup }) =&gt; {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm();

  const dispatch = useDispatch();
  const navigate = useNavigate();

  const onSubmit = async (data) =&gt; {
    try {
      const res = await loginUser(data);
      dispatch(addUser(res.data.user));
      setAccessToken(res.data.accessToken);
      toast.success("Login Successful");
      handleClose();
    } catch (err) {
      toast.error("Invalid Credentials");
      console.error(err);
    }
  };

  return (
    &lt;Dialog
      open={open}
      onClose={handleClose}
      fullWidth
      maxWidth="sm"
      disableScrollLock
    &gt;
      &lt;DialogTitle
        sx={{
          display: "flex",
          justifyContent: "space-between",
          alignItems: "center",
        }}
      &gt;
        Login
        &lt;IconButton onClick={handleClose}&gt;
          &lt;CloseIcon /&gt;
        &lt;/IconButton&gt;
      &lt;/DialogTitle&gt;

      &lt;DialogContent&gt;
        &lt;Box component="form" onSubmit={handleSubmit(onSubmit)}&gt;
          &lt;TextField
            margin="dense"
            label="Email"
            type="email"
            fullWidth
            variant="outlined"
            {...register("email", {
              required: "Email is required",
            })}
            error={!!errors.email}
            helperText={errors.email?.message}
          /&gt;

          &lt;TextField
            margin="dense"
            label="Password"
            type="password"
            fullWidth
            variant="outlined"
            {...register("password", {
              required: "Password is required",
              minLength: {
                value: 6,
                message: "Password must be at least 6 characters",
              },
            })}
            error={!!errors.password}
            helperText={errors.password?.message}
          /&gt;

          &lt;Typography
            variant="body2"
            sx={{
              mt: 2,
              cursor: "pointer",
              color: "primary.main",
              textAlign: "center",
            }}
            onClick={openSignup}
          &gt;
            Create new account
          &lt;/Typography&gt;

          &lt;Box
            sx={{
              mt: 3,
              display: "flex",
              justifyContent: "center",
            }}
          &gt;
            &lt;Button type="submit" variant="contained"&gt;
              Login
            &lt;/Button&gt;
          &lt;/Box&gt;
        &lt;/Box&gt;
      &lt;/DialogContent&gt;
    &lt;/Dialog&gt;
  );
};

export default Login;
</code></pre>
<hr />
<p><strong>Creating the Login API</strong></p>
<p>Inside:</p>
<pre><code class="language-plaintext">src/components/auth/api.js
</code></pre>
<pre><code class="language-javascript">import api from "@/api/axios.js";

// LOGIN
export const loginUser = async (data) =&gt; {
  const res = await api.post("/auth/login", data);
  return res.data;
};
</code></pre>
<hr />
<p><strong>After successful login :</strong></p>
<p>Instead of navigating users to a separate login page, I decided to use modal-based authentication.</p>
<p><strong>Why?</strong></p>
<p>Because it creates a smoother user experience. Users can authenticate without leaving their current page.</p>
<img src="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/32f1998f-4aef-48d1-8079-6e6744286e57.gif" alt="" style="display:block;margin:0 auto" />

<hr />
<h3><strong>Understanding React Hook Form</strong></h3>
<p>Instead of manually managing every input using:</p>
<pre><code class="language-plaintext">useState()
</code></pre>
<p>I used:</p>
<pre><code class="language-plaintext">useForm()
</code></pre>
<p>from React Hook Form.</p>
<p>Install:</p>
<pre><code class="language-plaintext">npm install react-hook-form
</code></pre>
<p>Inside the component:</p>
<pre><code class="language-javascript">const {
  register,
  handleSubmit,
  formState: { errors },
} = useForm();
</code></pre>
<p>This helps manage:</p>
<ul>
<li><p>Form state</p>
</li>
<li><p>Validation</p>
</li>
<li><p>Submission handling</p>
</li>
<li><p>Error handling</p>
</li>
</ul>
<p>with minimal re-renders.</p>
<hr />
<p><strong>Understanding register()</strong></p>
<pre><code class="language-plaintext">register("email")
</code></pre>
<p>connects the input field to React Hook Form.</p>
<p>This allows React Hook Form to automatically track:</p>
<ul>
<li><p>Input values</p>
</li>
<li><p>Validation state</p>
</li>
<li><p>Errors</p>
</li>
<li><p>Submission data</p>
</li>
</ul>
<p>without needing multiple state variables.</p>
<hr />
<p><strong>Understanding handleSubmit()</strong></p>
<pre><code class="language-plaintext">handleSubmit(onSubmit)
</code></pre>
<p>handles passing clean form data to the submit function.</p>
<hr />
<p><strong>Understanding Validation</strong></p>
<p>Validation rules are added directly during field registration.</p>
<p>Example:</p>
<pre><code class="language-javascript">register("password", {
  required: "Password is required",

  minLength: {
    value: 6,
    message:
      "Password must be at least 6 characters",
  },
})
</code></pre>
<p>This automatically validates the field before submission.</p>
<p>See:</p>
<img src="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/99c31644-c52a-4302-8c38-2d385dc2a78c.png" alt="" style="display:block;margin:0 auto" />

<hr />
<p><strong>Understanding Error Handling</strong></p>
<p>Material UI integrates nicely with React Hook Form.</p>
<p>Example:</p>
<pre><code class="language-javascript">error={Boolean(errors.email)}

helperText={errors.email?.message}
</code></pre>
<p><strong>error</strong></p>
<pre><code class="language-plaintext">error={Boolean(errors.email)}
</code></pre>
<p>puts the TextField into an error state if validation fails.</p>
<p><strong>helperText</strong></p>
<pre><code class="language-plaintext">helperText={errors.email?.message}
</code></pre>
<p>displays the actual validation message below the input field.</p>
<p>This creates a much better user experience compared to manual validation handling.</p>
<p>See:</p>
<img src="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/7f4cc2cf-e4a6-46c7-9cbd-a0a706398247.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>Step15: Rendering Login and Signup Components</h3>
<p>Inside:</p>
<pre><code class="language-plaintext">src/components/joinHousehold/JoinHouseHold.jsx
</code></pre>
<p>I rendered both authentication components.</p>
<pre><code class="language-js">import { useOutletContext } from "react-router-dom";

const JoinHouseHold = () =&gt; {
   const { openAuthDialog, setOpenAuthDialog } = useOutletContext();
   const [openSignup, setOpenSignup] = useState(false);

   return (
        &lt;&gt;
            {!openSignup ? (
               &lt;Login
                  open={openAuthDialog}
                  handleClose={() =&gt; setOpenAuthDialog(false)}
                  openSignup={() =&gt; {
                      setOpenAuthDialog(false);
                      setOpenSignup(true);
                   }}
                 /&gt;
              ) : (
                 &lt;Signup
                    open={openSignup}
                    handleClose={() =&gt; setOpenSignup(false)}
                    openLogin={() =&gt; {
                       setOpenSignup(false);
                       setOpenAuthDialog(true);
                     }}
                   /&gt;
                )}
         &lt;/&gt;
    )
};

export default JoinHouseHold;
</code></pre>
<p>Initially:</p>
<pre><code class="language-javascript">openSignup = false
</code></pre>
<p>So the Login component is rendered.</p>
<p>When users click:</p>
<pre><code class="language-javascript">Create new account
</code></pre>
<p>inside the Login dialog:</p>
<pre><code class="language-plaintext">setOpenAuthDialog(false);

setOpenSignup(true);
</code></pre>
<p>closes the Login modal and opens the Signup modal.</p>
<p>Similarly, after successful signup:</p>
<pre><code class="language-plaintext">openLogin();
</code></pre>
<p>inside the Signup component:</p>
<pre><code class="language-plaintext">setOpenSignup(false);

setOpenAuthDialog(true);
</code></pre>
<p>closes the Signup dialog and reopens the Login dialog.</p>
<p>This creates a connected modal-based authentication flow:</p>
<pre><code class="language-plaintext">Login Dialog
      ↓
Create Account
      ↓
Signup Dialog
      ↓
Account Created
      ↓
Login Dialog
</code></pre>
<hr />
<h3>Step 16: Persistent Authentication</h3>
<p>One major problem in frontend authentication is:</p>
<pre><code class="language-plaintext">Redux state resets after page refresh
</code></pre>
<p>To solve this, ShelfLife restores the user session whenever the application loads.</p>
<p>Inside:</p>
<pre><code class="language-plaintext">src/components/main/Body.jsx
</code></pre>
<p>I added authentication handling logic to manage:</p>
<ul>
<li><p>Persistent login</p>
</li>
<li><p>User fetching</p>
</li>
</ul>
<pre><code class="language-js">useEffect(() =&gt; {
  const token =
    localStorage.getItem(
      "accessToken"
    );

  if (token) {
    fetchUser();
  }
}, []);
</code></pre>
<p>The application first checks whether an access token already exists.</p>
<p>If a token is found:</p>
<pre><code class="language-plaintext">fetchUser();
</code></pre>
<p>calls the backend API to retrieve the authenticated user's data.</p>
<p><strong>Fetching the Authenticated User</strong></p>
<pre><code class="language-javascript">const fetchUser = async () =&gt; {
  try {
    const res = await getMe();

    dispatch(addUser(res.user));
  } catch (error) {
    toast.error(
      error?.response?.data?.message ||
        "Failed to fetch user"
    );
  }
};
</code></pre>
<p>Once the backend returns the user data:</p>
<pre><code class="language-plaintext">dispatch(addUser(res.user));
</code></pre>
<p>stores the user globally inside Redux.</p>
<p>This allows every component in the application to access authenticated user information.</p>
<h3>Why This Step Is Important</h3>
<p>Without this logic:</p>
<ul>
<li><p>User data disappears after refresh</p>
</li>
<li><p>Navbar loses authentication state</p>
</li>
<li><p>Protected routes stop working properly</p>
</li>
</ul>
<p>With this setup:</p>
<ul>
<li><p>Authentication persists across refreshes</p>
</li>
<li><p>Redux state gets restored automatically</p>
</li>
<li><p>User experience becomes seamless</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/08371a19-da1b-45f9-9fd8-73c5dfb41103.gif" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>Step 14: Building the Join Household Flow</h3>
<p>After completing authentication, I started building the household onboarding flow.</p>
<p>The main goal was:</p>
<pre><code class="language-plaintext">Only authenticated users should be able to join or create households
</code></pre>
<p>So before opening any household dialogs, I first check whether the user is logged in or not.</p>
<p>Inside:</p>
<pre><code class="language-plaintext">src/components/joinHousehold/JoinHouseHold.jsx
</code></pre>
<p>I created the complete household flow system.</p>
<pre><code class="language-js">import { useState } from "react";
import { useOutletContext } from "react-router-dom";
import { useSelector } from "react-redux";

import { Box, Button, Container, Typography, Paper } from "@mui/material";

import Login from "../auth/Login";
import Signup from "../auth/Signup";

import JoinWithInviteDialog from "./JoinWithInviteDialog";
import CreateHouseholdDialog from "./CreateHouseholdDialog";

const JoinHouseHold = () =&gt; {
  const user = useSelector((store) =&gt; store.user);

  const { openAuthDialog, setOpenAuthDialog } = useOutletContext();

  const [openSignup, setOpenSignup] = useState(false);

  const [openJoinDialog, setOpenJoinDialog] = useState(false);

  const [openCreateDialog, setOpenCreateDialog] = useState(false);

  const handleJoinHousehold = () =&gt; {
    const token = localStorage.getItem("accessToken");

    if (!token || !user) {
      setOpenAuthDialog(true);
      return;
    }

    setOpenJoinDialog(true);
  };

  return (
    &lt;&gt;
      &lt;Container maxWidth="md"&gt;
        &lt;Box
          sx={{
            minHeight: "80vh",
            display: "flex",
            justifyContent: "center",
            alignItems: "center",
          }}
        &gt;
          &lt;Paper
            elevation={4}
            sx={{
              p: 5,
              borderRadius: 4,
              textAlign: "center",
              width: "100%",
              maxWidth: "700px",
            }}
          &gt;
            &lt;Typography variant="h3" fontWeight="bold" gutterBottom&gt;
              Building a ShelfLife Household Inventory Tracker
            &lt;/Typography&gt;

            &lt;Typography
              variant="body1"
              color="text.secondary"
              sx={{
                mt: 2,
                mb: 4,
              }}
            &gt;
              Manage household items, track inventory, and collaborate with your
              family members easily.
            &lt;/Typography&gt;

            &lt;Button
              variant="contained"
              size="large"
              onClick={handleJoinHousehold}
            &gt;
              Join Household
            &lt;/Button&gt;
          &lt;/Paper&gt;
        &lt;/Box&gt;
      &lt;/Container&gt;

      {!openSignup ? (
        &lt;Login
          open={openAuthDialog}
          handleClose={() =&gt; setOpenAuthDialog(false)}
          openSignup={() =&gt; {
            setOpenAuthDialog(false);
            setOpenSignup(true);
          }}
        /&gt;
      ) : (
        &lt;Signup
          open={openSignup}
          handleClose={() =&gt; setOpenSignup(false)}
          openLogin={() =&gt; {
            setOpenSignup(false);
            setOpenAuthDialog(true);
          }}
        /&gt;
      )}

      &lt;JoinWithInviteDialog
        open={openJoinDialog}
        handleClose={() =&gt; setOpenJoinDialog(false)}
        openCreateDialog={() =&gt; {
          setOpenJoinDialog(false);
          setOpenCreateDialog(true);
        }}
      /&gt;

      &lt;CreateHouseholdDialog
        open={openCreateDialog}
        handleClose={() =&gt; setOpenCreateDialog(false)}
      /&gt;
    &lt;/&gt;
  );
};

export default JoinHouseHold;
</code></pre>
<hr />
<p><strong>Checking Authentication Before Joining</strong></p>
<pre><code class="language-javascript">const handleJoinHousehold = () =&gt; {
  const token =
    localStorage.getItem(
      "accessToken"
    );

  if (!token || !user) {
    setOpenAuthDialog(true);

    return;
  }

  setOpenJoinDialog(true);
};
</code></pre>
<p>When users click:</p>
<pre><code class="language-plaintext">Join Household
</code></pre>
<p>the application first checks:</p>
<ul>
<li><p>Does an access token exist?</p>
</li>
<li><p>Does authenticated user data exist in Redux?</p>
</li>
</ul>
<p>If authentication is missing:</p>
<pre><code class="language-plaintext">setOpenAuthDialog(true);
</code></pre>
<p>opens the Login dialog.</p>
<p>Otherwise:</p>
<pre><code class="language-plaintext">setOpenJoinDialog(true);
</code></pre>
<p>opens the Join Household dialog.</p>
<hr />
<p><strong>Understanding the Flow</strong></p>
<p>The complete flow works like this:</p>
<pre><code class="language-plaintext">User Clicks Join Household
            ↓
Check Authentication
            ↓
User Logged In?
      ↙      ↘
     No      Yes
     ↓        ↓
Open Login  Open Join Dialog
Dialog
</code></pre>
<p>This prevents unauthenticated users from accessing household functionality.</p>
<hr />
<p><strong>Join Household Dialog</strong></p>
<p>Inside:</p>
<pre><code class="language-plaintext">src/components/joinHousehold/JoinWithInviteDialog.jsx
</code></pre>
<p>I created the household joining dialog.</p>
<pre><code class="language-js">import {
  Box,
  Button,
  Dialog,
  DialogActions,
  DialogContent,
  DialogTitle,
  TextField,
  Typography,
  IconButton,
} from "@mui/material";

import { useForm } from "react-hook-form";
import { joinHousehold } from "./api";
import { toast } from "react-toastify";
import { useNavigate } from "react-router-dom";
import CloseIcon from "@mui/icons-material/Close";

const JoinWithInviteDialog = ({ open, handleClose, openCreateDialog }) =&gt; {
  const {
    register,
    handleSubmit,
    formState: { errors },
    reset,
  } = useForm({
    defaultValues: {
      inviteCode: "",
    },
  });

  const navigate = useNavigate();

  const onSubmit = async (data) =&gt; {
    try {
      const res = await joinHousehold(data);
      navigate("/items");
      reset();
      toast.success("Household join successfully");
      handleClose();
    } catch (err) {
      toast.error("Invalid Invite code");
      console.error(err);
    }
  };

  return (
    &lt;Dialog
      open={open}
      onClose={handleClose}
      fullWidth
      maxWidth="sm"
      disableScrollLock
    &gt;
      &lt;DialogTitle
        sx={{
          display: "flex",
          justifyContent: "space-between",
          alignItems: "center",
        }}
      &gt;
        Join Household
        &lt;IconButton onClick={handleClose}&gt;
          &lt;CloseIcon /&gt;
        &lt;/IconButton&gt;
      &lt;/DialogTitle&gt;

      &lt;DialogContent&gt;
        &lt;form onSubmit={handleSubmit(onSubmit)}&gt;
          &lt;TextField
            fullWidth
            label="Invite Code"
            margin="normal"
            error={!!errors.inviteCode}
            helperText={errors.inviteCode?.message}
            inputProps={{
              maxLength: 6,
            }}
            {...register("inviteCode", {
              required: "Invite code is required",

              pattern: {
                value: /^[0-9]{6}$/,
                message: "Invite code must be 6 digits",
              },

              onChange: (e) =&gt; {
                e.target.value = e.target.value.replace(/\D/g, "").slice(0, 6);
              },
            })}
          /&gt;

          &lt;Typography
            sx={{
              mt: 3,
              textAlign: "center",
            }}
          &gt;
            Want to create your own household?
          &lt;/Typography&gt;

          &lt;Box
            sx={{
              display: "flex",
              justifyContent: "center",
              mt: 2,
            }}
          &gt;
            &lt;Button variant="outlined" onClick={openCreateDialog}&gt;
              Create New Household
            &lt;/Button&gt;
          &lt;/Box&gt;

          &lt;DialogActions&gt;
            &lt;Button type="submit" variant="contained"&gt;
              Join
            &lt;/Button&gt;
          &lt;/DialogActions&gt;
        &lt;/form&gt;
      &lt;/DialogContent&gt;
    &lt;/Dialog&gt;
  );
};

export default JoinWithInviteDialog;
</code></pre>
<p>Users can:</p>
<ul>
<li><p>Enter an invite code</p>
</li>
<li><p>Join an existing household</p>
</li>
<li><p>Open the create household dialog</p>
</li>
</ul>
<hr />
<p><strong>JoinHouseHold API</strong></p>
<p>Inside:</p>
<pre><code class="language-plaintext">src/components/joinHouseHold/api.js
</code></pre>
<pre><code class="language-javascript">import api from "@/api/axios.js";

export const joinHousehold = async (data) =&gt; {
  const res = await api.post("/households/join", data);
  return res.data;
};
</code></pre>
<p><strong>Joining a Household</strong></p>
<pre><code class="language-js">const onSubmit = async (data) =&gt; {
  try {
    const res =
      await joinHousehold(data);

    navigate("/items");

    reset();

    toast.success(
      "Household join successfully"
    );

    handleClose();
  } catch (err) {
    toast.error(
      "Invalid Invite code"
    );

    console.error(err);
  }
};
</code></pre>
<p>After successfully joining:</p>
<ul>
<li><p>User is redirected to the items page</p>
</li>
<li><p>Form resets</p>
</li>
<li><p>Success message appears</p>
</li>
<li><p>Dialog closes automatically</p>
</li>
</ul>
<hr />
<p><strong>Creating a New Household Component</strong></p>
<p>Inside the Join dialog, users can also create their own household.</p>
<pre><code class="language-js">&lt;Button
  variant="outlined"
  onClick={openCreateDialog}
&gt;
  Create New Household
&lt;/Button&gt;
</code></pre>
<p>When clicked:</p>
<pre><code class="language-plaintext">Join Dialog Closes
        ↓
Create Household Dialog Opens
</code></pre>
<p>This creates a connected onboarding flow.</p>
<hr />
<p><strong>Create Household Dialog</strong></p>
<p>Inside:</p>
<pre><code class="language-plaintext">src/components/joinHousehold/CreateHouseholdDialog.jsx
</code></pre>
<p>I created the household creation form.</p>
<pre><code class="language-js">import {
  Button,
  Dialog,
  DialogActions,
  DialogContent,
  DialogTitle,
  TextField,
  IconButton,
} from "@mui/material";

import { useForm } from "react-hook-form";
import { toast } from "react-toastify";
import { createHousehold } from "./api";
import { useNavigate } from "react-router-dom";
import CloseIcon from "@mui/icons-material/Close";

const CreateHouseholdDialog = ({ open, handleClose }) =&gt; {
  const navigate = useNavigate();
  const {
    register,
    handleSubmit,
    formState: { errors },
    reset,
  } = useForm({
    defaultValues: {
      householdName: "",
      inviteCode: "",
    },
  });

  const onSubmit = async (data) =&gt; {
    try {
      const res = await createHousehold(data);
      navigate("/items");
      reset();
      toast.success("Houshold created successfully");
      handleClose();
    } catch (err) {
      toast.error("Something went wrong");
      console.error(err);
    }
    console.log(data);
  };

  return (
    &lt;Dialog
      open={open}
      onClose={handleClose}
      fullWidth
      maxWidth="sm"
      disableScrollLock
    &gt;
      &lt;DialogTitle
        sx={{
          display: "flex",
          justifyContent: "space-between",
          alignItems: "center",
        }}
      &gt;
        Create Household
        &lt;IconButton onClick={handleClose}&gt;
          &lt;CloseIcon /&gt;
        &lt;/IconButton&gt;
      &lt;/DialogTitle&gt;

      &lt;DialogContent&gt;
        &lt;form onSubmit={handleSubmit(onSubmit)}&gt;
          &lt;TextField
            fullWidth
            label="Household Name"
            margin="normal"
            error={!!errors.householdName}
            helperText={errors.householdName?.message}
            {...register("householdName", {
              required: "Household name is required",
            })}
          /&gt;

          &lt;TextField
            fullWidth
            label="Invite Code"
            margin="normal"
            error={!!errors.inviteCode}
            helperText={errors.inviteCode?.message}
            inputProps={{
              maxLength: 6,
            }}
            {...register("inviteCode", {
              required: "Invite code is required",

              pattern: {
                value: /^[0-9]{6}$/,
                message: "Invite code must be 6 digits",
              },

              onChange: (e) =&gt; {
                e.target.value = e.target.value.replace(/\D/g, "").slice(0, 6);
              },
            })}
          /&gt;

          &lt;DialogActions&gt;
            &lt;Button type="submit" variant="contained"&gt;
              Create
            &lt;/Button&gt;
          &lt;/DialogActions&gt;
        &lt;/form&gt;
      &lt;/DialogContent&gt;
    &lt;/Dialog&gt;
  );
};

export default CreateHouseholdDialog;
</code></pre>
<p>The form includes:</p>
<ul>
<li><p>Household name</p>
</li>
<li><p>6-digit invite code</p>
</li>
</ul>
<hr />
<p><strong>Creating the Create HouseHold API</strong></p>
<p>Inside:</p>
<pre><code class="language-plaintext">src/components/joinHouseHold/api.js
</code></pre>
<pre><code class="language-javascript">import api from "@/api/axios.js";

export const createHousehold = async (data) =&gt; {
  const res = await api.post("/households", data);
  return res.data;
};
</code></pre>
<p><strong>Creating a Household</strong></p>
<pre><code class="language-javascript">const onSubmit = async (data) =&gt; {
  try {
    const res =
      await createHousehold(data);

    navigate("/items");

    reset();

    toast.success(
      "Houshold created successfully"
    );

    handleClose();
  } catch (err) {
    toast.error(
      "Something went wrong"
    );

    console.error(err);
  }
};
</code></pre>
<p>After successful creation:</p>
<ul>
<li><p>Household gets created</p>
</li>
<li><p>User navigates to inventory items</p>
</li>
<li><p>Form resets</p>
</li>
<li><p>Success toast appears</p>
</li>
<li><p>Dialog closes automatically</p>
</li>
</ul>
<hr />
<h3>Step 17: Implementing Frontend Route Protection (Protected Routes)</h3>
<p>I used this article by Robin Wieruch as inspiration for understanding and implementing protected routes in React Router:</p>
<p><a href="https://www.robinwieruch.de/react-router-private-routes/?utm_source=chatgpt.com">React Router Private Routes Guide</a></p>
<p>After completing the authentication and household onboarding flow, the next important step was protecting application routes on the frontend.</p>
<p>At this point, ShelfLife already had:</p>
<ul>
<li><p>Authentication system</p>
</li>
<li><p>Persistent login</p>
</li>
<li><p>Household creation/join flow</p>
</li>
</ul>
<p>But there was still one major issue:</p>
<p>Users could manually access routes using URLs even if they were not authorized.</p>
<p>Example:</p>
<pre><code class="language-plaintext">/items
</code></pre>
<p>A user could directly type this URL in the browser.</p>
<p>To solve this, I implemented:</p>
<ul>
<li><p>Protected Routes</p>
</li>
<li><p>Public Only Routes</p>
</li>
</ul>
<p>using React Router.</p>
<hr />
<p><strong>Why Route Protection Is Important</strong></p>
<p>ShelfLife has two different application states.</p>
<p><strong>Public Area</strong></p>
<p>Accessible to everyone:</p>
<pre><code class="language-plaintext">/
</code></pre>
<p>This page contains:</p>
<ul>
<li><p>Login</p>
</li>
<li><p>Signup</p>
</li>
<li><p>Join household onboarding</p>
</li>
</ul>
<hr />
<p><strong>Protected Area</strong></p>
<p>Accessible only when:</p>
<ul>
<li><p>User is authenticated</p>
</li>
<li><p>User belongs to a household</p>
</li>
</ul>
<p>Example:</p>
<pre><code class="language-plaintext">/items
</code></pre>
<p>This contains the actual inventory dashboard.</p>
<p>So the rule becomes:</p>
<pre><code class="language-plaintext">User logged in + household joined
                ↓
Allow access to dashboard "/items"

Otherwise
                ↓
Redirect back to "/"
</code></pre>
<hr />
<p><strong>Understanding the Routing Architecture</strong></p>
<p>The routing flow now looks like:</p>
<pre><code class="language-plaintext">BrowserRouter
      ↓
Routes
      ↓
Body Layout
      ↓
Protected/Public Route Guards
      ↓
Actual Pages
</code></pre>
<hr />
<p><strong>Creating the Route Configuration</strong></p>
<p>Inside:</p>
<pre><code class="language-plaintext">src/routes/index.js
</code></pre>
<p>I added route metadata.</p>
<pre><code class="language-javascript">import { lazy } from "react";

export const routes = [
  {
    path: "",

    component: lazy(() =&gt;
      import("@/components/joinHouseHold/JoinHouseHold")
    ),

    isProtected: false,
  },

  {
    path: "/items",

    component: lazy(() =&gt;
      import("@/components/dashboard/items")
    ),

    isProtected: true,
  },
];
</code></pre>
<hr />
<p><strong>Creating the Protected Route Component</strong></p>
<p>Inside:</p>
<pre><code class="language-plaintext">src/routes/ProtectedRoute.jsx
</code></pre>
<p>I created the route guard.</p>
<pre><code class="language-javascript">import {
  Navigate,
  Outlet,
  useOutletContext,
} from "react-router-dom";

import { useSelector } from "react-redux";

const ProtectedRoute = ({ children }) =&gt; {
  const user = useSelector(
    (store) =&gt; store.user
  );

  const context = useOutletContext();

  // USER NOT LOGGED IN
  if (!user) {
    return &lt;Navigate to="/" replace /&gt;;
  }

  // USER HAS NOT JOINED HOUSEHOLD
  if (!user.householdId) {
    return &lt;Navigate to="/" replace /&gt;;
  }

  return children
    ? children
    : &lt;Outlet context={context} /&gt;;
};

export default ProtectedRoute;
</code></pre>
<hr />
<p><strong>Creating Public Only Routes</strong></p>
<p>Now we also need the opposite behavior.</p>
<p>If the user already joined a household:</p>
<pre><code class="language-plaintext">Do NOT allow access to "/"
</code></pre>
<p>Instead:</p>
<pre><code class="language-plaintext">Redirect directly to "/items"
</code></pre>
<p>Inside:</p>
<pre><code class="language-plaintext">src/routes/PublicOnlyRoute.jsx
</code></pre>
<pre><code class="language-javascript">import {
  Navigate,
  Outlet,
  useOutletContext,
} from "react-router-dom";

import { useSelector } from "react-redux";

const PublicOnlyRoute = ({
  children,
}) =&gt; {
  const user = useSelector(
    (store) =&gt; store.user
  );

  const context = useOutletContext();

  if (user?.householdId) {
    return (
      &lt;Navigate
        to="/items"
        replace
      /&gt;
    );
  }

  return children
    ? children
    : &lt;Outlet context={context} /&gt;;
};

export default PublicOnlyRoute;
</code></pre>
<p><strong>Understanding PublicOnlyRoute</strong></p>
<p>This component prevents authenticated household users from returning to onboarding pages.</p>
<p>Meaning:</p>
<pre><code class="language-plaintext">Already onboarded user
         ↓
Skip landing page
         ↓
Go directly to dashboard
</code></pre>
<p>This improves user experience significantl</p>
<hr />
<p><strong>Final Route Flow</strong></p>
<p>The application behavior now becomes:</p>
<pre><code class="language-plaintext">VISIT "/"
     ↓
User has household?
   ↙       ↘
 No         Yes
 ↓           ↓
Show       Redirect
Landing    to /items
Page
</code></pre>
<p>And:</p>
<pre><code class="language-plaintext">VISIT "/items"
       ↓
User authenticated?
   ↙          ↘
 No            Yes
 ↓              ↓
Redirect      Has Household?
to "/"        ↙         ↘
             No         Yes
             ↓           ↓
         Redirect     Allow Access
           to "/"
</code></pre>
<hr />
<p><strong>Configuring Protected Routes in App.jsx</strong></p>
<p>Inside:</p>
<pre><code class="language-plaintext">src/App.jsx
</code></pre>
<p>I dynamically wrapped routes using route metadata.</p>
<pre><code class="language-javascript">&lt;Routes&gt;
  &lt;Route path="/" element={&lt;Body /&gt;}&gt;
    {routes.map((route) =&gt; {
      const Component = route.component;

      // PROTECTED ROUTES
      if (route.isProtected) {
        return (
          &lt;Route
            key={route.path}
            element={&lt;ProtectedRoute /&gt;}
          &gt;
            &lt;Route
              path={route.path}
              element={&lt;Component /&gt;}
            /&gt;
          &lt;/Route&gt;
        );
      }

      // PUBLIC ROUTES
      return (
        &lt;Route
          key={route.path}
          element={&lt;PublicOnlyRoute /&gt;}
        &gt;
          &lt;Route
            path={route.path}
            element={&lt;Component /&gt;}
          /&gt;
        &lt;/Route&gt;
      );
    })}
  &lt;/Route&gt;
&lt;/Routes&gt;
</code></pre>
<p><strong>Understanding Nested Route Protection</strong></p>
<p>This structure:</p>
<pre><code class="language-plaintext">&lt;Route element={&lt;ProtectedRoute /&gt;}&gt;
  &lt;Route
    path="/items"
    element={&lt;Items /&gt;}
  /&gt;
&lt;/Route&gt;
</code></pre>
<p>means:</p>
<pre><code class="language-plaintext">Before rendering /items
       ↓
Run ProtectedRoute first
       ↓
If allowed → render Items
Else → redirect
</code></pre>
<p>This is called:</p>
<pre><code class="language-plaintext">Layout Route Protection
</code></pre>
<p>in React Router.</p>
<hr />
<p><strong>Why This Architecture Is Powerful</strong></p>
<p>This approach scales extremely well.</p>
<p>In future, adding new protected routes becomes very easy.</p>
<p>Example:</p>
<pre><code class="language-plaintext">{
   path: "/analytics",
   component: Analytics,
   isProtected: true
}
</code></pre>
<p>No additional protection logic needed.</p>
<p>The route automatically becomes protected.</p>
<hr />
<p><strong>Final Authentication Architecture</strong></p>
<p>The complete frontend authentication system now looks like:</p>
<pre><code class="language-plaintext">User Login
     ↓
Redux Store Updated
     ↓
Protected Routes Read Redux State
     ↓
Allow / Block Route Access
     ↓
React Router Redirects User
</code></pre>
<hr />
<h3>Step 18: Creating a Reusable Dashboard Layout with Nested Routes</h3>
<p>After implementing protected routes, the next improvement was organizing all authenticated pages inside a reusable dashboard layout.</p>
<p>At this point, ShelfLife had multiple authenticated pages like:</p>
<ul>
<li><p><code>/items</code></p>
</li>
<li><p><code>/members</code></p>
</li>
</ul>
<p>All these pages needed:</p>
<ul>
<li><p>A common sidebar</p>
</li>
<li><p>Shared dashboard structure</p>
</li>
<li><p>Consistent spacing/layout</p>
</li>
<li><p>Route navigation</p>
</li>
<li><p>Protected access</p>
</li>
</ul>
<p>Instead of repeating sidebar code on every page, I created a reusable <code>DashboardLayout</code>.</p>
<hr />
<p><strong>Creating DashboardLayout</strong></p>
<p>I created:</p>
<pre><code class="language-plaintext">components/layout/DashboardLayout.jsx
</code></pre>
<pre><code class="language-js">import {
  Outlet,
  useLocation,
  useNavigate,
  useOutletContext,
} from "react-router-dom";

import {
  Box,
  Drawer,
  List,
  ListItemButton,
  ListItemIcon,
  ListItemText,
  Toolbar,
} from "@mui/material";

import Inventory2Icon from "@mui/icons-material/Inventory2";
import GroupIcon from "@mui/icons-material/Group";

const drawerWidth = 240;

const menuItems = [
  {
    label: "Items",
    path: "/items",
    icon: &lt;Inventory2Icon /&gt;,
  },
  {
    label: "Members",
    path: "/members",
    icon: &lt;GroupIcon /&gt;,
  },
];

const DashboardLayout = () =&gt; {
  const navigate = useNavigate();

  const location = useLocation();

  const context = useOutletContext();

  return (
    &lt;Box sx={{ display: "flex", flex: 1 }}&gt;
      {/* SIDEBAR */}
      &lt;Drawer
        variant="permanent"
        sx={{
          width: drawerWidth,
          flexShrink: 0,

          [`&amp; .MuiDrawer-paper`]: {
            width: drawerWidth,
            boxSizing: "border-box",
            position: "relative",
            height: "calc(100vh - 64px)",
          },
        }}
      &gt;
        &lt;Toolbar /&gt;

        &lt;Box sx={{ overflow: "auto", mt: 2 }}&gt;
          &lt;List&gt;
            {menuItems.map((item) =&gt; {
              const isActive = location.pathname === item.path;

              return (
                &lt;ListItemButton
                  key={item.path}
                  selected={isActive}
                  onClick={() =&gt; navigate(item.path)}
                  sx={{
                    mx: 1,
                    borderRadius: 2,
                    mb: 1,
                  }}
                &gt;
                  &lt;ListItemIcon&gt;{item.icon}&lt;/ListItemIcon&gt;

                  &lt;ListItemText primary={item.label} /&gt;
                &lt;/ListItemButton&gt;
              );
            })}
          &lt;/List&gt;
        &lt;/Box&gt;
      &lt;/Drawer&gt;

      {/* PAGE CONTENT */}
      &lt;Box
        component="main"
        sx={{
          flexGrow: 1,
          p: 3,
          bgcolor: "#f5f5f5",

          width: `calc(100% - ${drawerWidth}px)`,

          overflowX: "auto",

          minWidth: 0,

          height: "calc(100vh - 64px)",

          overflowY: "auto",
        }}
      &gt;
        &lt;Outlet context={context} /&gt;
      &lt;/Box&gt;
    &lt;/Box&gt;
  );
};

export default DashboardLayout;
</code></pre>
<hr />
<p><strong>Why use Outlet?</strong></p>
<p>React Router’s <code>&lt;Outlet /&gt;</code> renders child routes inside the layout.</p>
<p>This allows all authenticated pages to share:</p>
<ul>
<li><p>Sidebar</p>
</li>
<li><p>Navigation</p>
</li>
</ul>
<p>while only changing the page content.</p>
<p>Example:</p>
<pre><code class="language-plaintext">/items
/members
</code></pre>
<p>Both pages now render inside the same dashboard layout.</p>
<hr />
<p><strong>Updating App.jsx with Nested Protected Routes</strong></p>
<p>Next, I wrapped all protected routes inside:</p>
<pre><code class="language-plaintext">&lt;DashboardLayout /&gt;
</code></pre>
<p>using nested routing.</p>
<pre><code class="language-js">&lt;Route
  element={
    &lt;ProtectedRoute&gt;
      &lt;DashboardLayout /&gt;
    &lt;/ProtectedRoute&gt;
  }
&gt;
  {routes
    .filter((route) =&gt; route.isProtected)
    .map((route) =&gt; {
      const Component = route.component;

      return (
        &lt;Route
          key={route.path}
          path={route.path}
          element={&lt;Component /&gt;}
        /&gt;
      );
    })}
&lt;/Route&gt;
</code></pre>
<hr />
<p><strong>Benefits of This Architecture</strong></p>
<p>This structure gives several advantages:</p>
<ul>
<li>Reusable Layout</li>
</ul>
<p>All dashboard pages automatically share the same layout.</p>
<ul>
<li>Cleaner Routing</li>
</ul>
<p>Protected routes are grouped together in one place.</p>
<ul>
<li>Better Scalability</li>
</ul>
<p>Adding new authenticated pages becomes very easy.</p>
<ul>
<li>Centralized Navigation</li>
</ul>
<p>Sidebar navigation is managed from one component.</p>
<ul>
<li>Better User Experience</li>
</ul>
<p>Users get a consistent dashboard UI across all pages.</p>
<img src="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/2c0e7f95-69c7-4993-9640-30e882f589a5.gif" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>Step 19: Building the Inventory Management Table using Material React Table</h3>
<p>After setting up the reusable dashboard layout, the next major feature was building the inventory management system.</p>
<p>The requirements for the inventory table were:</p>
<p><strong>Inventory Table Requirements</strong></p>
<p>Columns:</p>
<ul>
<li><p>Name</p>
</li>
<li><p>Category</p>
</li>
<li><p>Quantity</p>
</li>
<li><p>Expiry Date</p>
</li>
<li><p>Status</p>
</li>
<li><p>Actions</p>
</li>
</ul>
<p>Additional requirements:</p>
<ul>
<li><p>Add Item button</p>
</li>
<li><p>Edit item</p>
</li>
<li><p>Delete item</p>
</li>
<li><p>Search</p>
</li>
<li><p>Sorting</p>
</li>
<li><p>Pagination</p>
</li>
<li><p>Column filtering</p>
</li>
<li><p>Inline row editing</p>
</li>
<li><p>Future barcode scanning support</p>
</li>
<li><p>Responsive dashboard integration</p>
</li>
</ul>
<p>There was also a future flow planned for adding products:</p>
<pre><code class="language-plaintext">Add Item
    ↓
Open Scanner
    ↓
Scan Barcode
    ↓
Auto-fill Product Info
    ↓
User completes remaining fields
    ↓
Save Item
</code></pre>
<p>Because of all these requirements, I needed a powerful and scalable table solution.</p>
<hr />
<p><strong>Choosing Material React Table</strong></p>
<p>After researching different table libraries, I decided to use:</p>
<pre><code class="language-plaintext">material-react-table
</code></pre>
<p>The reason for choosing Material React Table was because it already provides many production-level features out of the box:</p>
<ul>
<li><p>Inline editing</p>
</li>
<li><p>Row actions</p>
</li>
<li><p>Pagination</p>
</li>
<li><p>Sorting</p>
</li>
<li><p>Filtering</p>
</li>
<li><p>Global search</p>
</li>
<li><p>Material UI integration</p>
</li>
<li><p>Flexible customization</p>
</li>
<li><p>Better developer experience</p>
</li>
</ul>
<p>This made it a perfect fit for the ShelfLife inventory system.</p>
<hr />
<p><strong>Creating the Items Page</strong></p>
<p>I created:</p>
<pre><code class="language-plaintext">items/Items.jsx
</code></pre>
<pre><code class="language-js">import { useEffect, useState } from "react";

import { Box, Button, Typography } from "@mui/material";

import AddIcon from "@mui/icons-material/Add";

import InventoryTable from "./InventoryTable.jsx";
import { toast } from "react-toastify";
import { getItems } from "./api.js";

const Items = () =&gt; {
  const [open, setOpen] = useState(false);

  const [editingItem, setEditingItem] = useState(null);

  const [items, setItems] = useState([]);

  const fetchItems = async () =&gt; {
    try {
      const res = await getItems();

      setItems(res.data.items);
    } catch (error) {
      toast.error(error.response?.data?.message || "Failed to fetch items");
    }
  };

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

  return (
    &lt;Box&gt;
      &lt;Box
        sx={{
          display: "flex",
          justifyContent: "space-between",
          alignItems: "center",
          mb: 3,
        }}
      &gt;
        &lt;Typography variant="h5" fontWeight={700}&gt;
          Inventory Items
        &lt;/Typography&gt;

        &lt;Button
          variant="contained"
          startIcon={&lt;AddIcon /&gt;}
          onClick={() =&gt; {
            setEditingItem(null);

            setOpen(true);
          }}
        &gt;
          Add Item
        &lt;/Button&gt;
      &lt;/Box&gt;

      &lt;InventoryTable
        items={items}
        fetchItems={fetchItems}
      /&gt;
    &lt;/Box&gt;
  );
};

export default Items;
</code></pre>
<hr />
<p><strong>Creating the Inventory Table</strong></p>
<p>Next, I created:</p>
<pre><code class="language-plaintext">items/InventoryTable.jsx
</code></pre>
<p>This component handles:</p>
<ul>
<li><p>Table rendering</p>
</li>
<li><p>Inline row editing</p>
</li>
<li><p>Validation</p>
</li>
<li><p>Status rendering</p>
</li>
<li><p>Table actions</p>
</li>
<li><p>API updates</p>
</li>
<li><p>Sorting/filtering/pagination</p>
</li>
</ul>
<pre><code class="language-js">import { useMemo, useState } from "react";

import {
  MaterialReactTable,
  useMaterialReactTable,
} from "material-react-table";

import { Box, Chip, IconButton, Tooltip, TextField } from "@mui/material";

import EditIcon from "@mui/icons-material/Edit";
import DeleteIcon from "@mui/icons-material/Delete";
import { toast } from "react-toastify";
import { updateItem } from "./api";

const getStatus = (expiryDate) =&gt; {
  const today = new Date();

  const expiry = new Date(expiryDate);

  const diffDays = Math.ceil((expiry - today) / (1000 * 60 * 60 * 24));

  if (diffDays &lt; 0) {
    return "expired";
  }

  if (diffDays &lt;= 3) {
    return "expiring-soon";
  }

  return "fresh";
};

const InventoryTable = ({ items, fetchItems }) =&gt; {
  const [validationErrors, setValidationErrors] = useState({});

  const columns = useMemo(
    () =&gt; [
      {
        accessorKey: "name",
        header: "Name",
        muiEditTextFieldProps: {
          required: true,
          error: !!validationErrors?.name,
          helperText: validationErrors?.name,
          onFocus: () =&gt;
            setValidationErrors({
              ...validationErrors,
              name: undefined,
            }),
        },
      },
      {
        accessorKey: "category",
        header: "Category",
        muiEditTextFieldProps: {
          required: true,
          error: !!validationErrors?.category,
          helperText: validationErrors?.category,
          onFocus: () =&gt;
            setValidationErrors({
              ...validationErrors,
              category: undefined,
            }),
        },
      },
      {
        accessorKey: "quantity",
        header: "Quantity",
        muiEditTextFieldProps: {
          type: "number",
          required: true,
          error: !!validationErrors?.quantity,
          helperText: validationErrors?.quantity,
          onFocus: () =&gt;
            setValidationErrors({
              ...validationErrors,
              quantity: undefined,
            }),
        },
      },
      {
        accessorKey: "expiryDate",
        header: "Expiry Date",
        Edit: ({ cell, column, row, table }) =&gt; (
          &lt;TextField
            type="date"
            value={cell.getValue()?.split("T")[0] || ""}
            onChange={(e) =&gt; (row._valuesCache[column.id] = e.target.value)}
            fullWidth
          /&gt;
        ),
        Cell: ({ cell }) =&gt; {
          const value = cell.getValue();

          if (!value) return "";

          return new Date(value).toLocaleDateString();
        },
      },
      {
        header: "Status",
        enableEditing: false,
        Cell: ({ row }) =&gt; {
          const status = getStatus(row.original.expiryDate);

          return (
            &lt;Chip
              label={status}
              color={
                status === "fresh"
                  ? "success"
                  : status === "expiring-soon"
                    ? "warning"
                    : "error"
              }
            /&gt;
          );
        },
      },
    ],
    [validationErrors],
  );

  const handleSaveRow = async ({ values, table, row }) =&gt; {
    const errors = validateItem(values);

    if (Object.values(errors).some(Boolean)) {
      setValidationErrors(errors);
      return;
    }

    try {
      setValidationErrors({});

      const payload = {
        name: values.name,
        category: values.category,
        quantity: values.quantity,
        expiryDate: values.expiryDate,
      };

      await updateItem(row.original._id, payload);

      await fetchItems();

      toast.success("Item updated successfully");

      table.setEditingRow(null);
    } catch (error) {
      toast.error(error.response?.data?.message || "Failed to update item");
    }
  };

  const handleDelete = (row) =&gt; {
    // if (window.confirm("Are you sure you want to delete this item?")) {
    //   setItems((prev) =&gt; prev.filter((item) =&gt; item._id !== row.original._id));
    // }
  };

  const table = useMaterialReactTable({
    columns,
    data: items,

    enableEditing: true,

    editDisplayMode: "row",

    getRowId: (row) =&gt; row._id,

    onEditingRowSave: handleSaveRow,

    onEditingRowCancel: () =&gt; setValidationErrors({}),

    renderRowActions: ({ row, table }) =&gt; (
      &lt;Box sx={{ display: "flex", gap: "8px" }}&gt;
        &lt;Tooltip title="Edit"&gt;
          &lt;IconButton onClick={() =&gt; table.setEditingRow(row)}&gt;
            &lt;EditIcon /&gt;
          &lt;/IconButton&gt;
        &lt;/Tooltip&gt;

        &lt;Tooltip title="Delete"&gt;
          &lt;IconButton color="error" onClick={() =&gt; handleDelete(row)}&gt;
            &lt;DeleteIcon /&gt;
          &lt;/IconButton&gt;
        &lt;/Tooltip&gt;
      &lt;/Box&gt;
    ),

    enableSorting: true,
    enablePagination: true,
    enableColumnFilters: true,
    enableGlobalFilter: true,

    positionGlobalFilter: "left",
  });

  return &lt;MaterialReactTable table={table} /&gt;;
};

export default InventoryTable;

const validateRequired = (value) =&gt; !!value?.toString().trim();

function validateItem(item) {
  return {
    name: !validateRequired(item.name) ? "Name is required" : "",

    category: !validateRequired(item.category) ? "Category is required" : "",

    quantity: !validateRequired(item.quantity) ? "Quantity is required" : "",
  };
}
</code></pre>
<hr />
<p><strong>Built-in Table Features</strong></p>
<p>Material React Table also made it very easy to enable advanced table features:</p>
<pre><code class="language-plaintext">enableSorting: true,
enablePagination: true,
enableColumnFilters: true,
enableGlobalFilter: true,
</code></pre>
<p>This instantly added:</p>
<ul>
<li><p>Sorting</p>
</li>
<li><p>Pagination</p>
</li>
<li><p>Search</p>
</li>
<li><p>Filtering</p>
</li>
</ul>
<p>without building custom logic manually.</p>
<img src="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/f3d0a16a-e748-4745-b74d-17e8a0eb6a95.gif" alt="" style="display:block;margin:0 auto" />

<hr />
<p><strong>Fetching Inventory Items from Backend</strong></p>
<p>After creating the inventory table UI, the next step was fetching real inventory data from the backend.</p>
<p>For API communication, I was already using Axios with a centralized Axios instance.</p>
<hr />
<p><strong>Creating the API Function</strong></p>
<p>I created a reusable API function for fetching inventory items.</p>
<p><strong>api.js</strong></p>
<pre><code class="language-javascript">import api from "@/api/axios.js";

// GET ITEMS
export const getItems = async () =&gt; {
  const res = await api.get("/items/");

  return res.data;
};
</code></pre>
<p>This keeps API logic separate from UI components, which helps maintain cleaner architecture and better scalability.</p>
<hr />
<p><strong>Fetching Items Inside Items.jsx</strong></p>
<p>Inside the <code>Items</code> component, I created local state for storing inventory items.</p>
<pre><code class="language-plaintext">const [items, setItems] = useState([]);
</code></pre>
<p>Then I created a reusable <code>fetchItems()</code> function.</p>
<pre><code class="language-javascript">const fetchItems = async () =&gt; {
  try {
    const res = await getItems();

    setItems(res.data.items);
  } catch (error) {
    toast.error(
      error.response?.data?.message ||
        "Failed to fetch items"
    );
  }
};
</code></pre>
<p>This function:</p>
<ul>
<li><p>Calls backend API</p>
</li>
<li><p>Fetches inventory items</p>
</li>
<li><p>Updates React state</p>
</li>
<li><p>Handles API errors gracefully</p>
</li>
</ul>
<hr />
<p><strong>Loading Data on Component Mount</strong></p>
<p>To fetch inventory data when the page loads, I used <code>useEffect</code>.</p>
<pre><code class="language-javascript">useEffect(() =&gt; {
  fetchItems();
}, []);
</code></pre>
<p>Because the dependency array is empty:</p>
<pre><code class="language-plaintext">[]
</code></pre>
<p>the API runs only once when the component mounts.</p>
<p>This is useful for:</p>
<ul>
<li><p>Initial dashboard load</p>
</li>
<li><p>Inventory page refresh</p>
</li>
<li><p>Fetching latest database data</p>
</li>
</ul>
<hr />
<p><strong>Making Rows Editable</strong></p>
<p>I enabled editing using:</p>
<pre><code class="language-plaintext">enableEditing: true,
editDisplayMode: "row",
</code></pre>
<p>This allowed users to click the edit icon and instantly convert the entire row into editable input fields.</p>
<hr />
<p><strong>Adding Editable Columns</strong></p>
<p>For editable columns like:</p>
<ul>
<li><p>Name</p>
</li>
<li><p>Category</p>
</li>
<li><p>Quantity</p>
</li>
<li><p>Expiry Date</p>
</li>
</ul>
<p>I configured:</p>
<pre><code class="language-plaintext">muiEditTextFieldProps
</code></pre>
<p>Example:</p>
<pre><code class="language-plaintext">{
  accessorKey: "name",
  header: "Name",
  muiEditTextFieldProps: {
    required: true,
    error: !!validationErrors?.name,
    helperText: validationErrors?.name,
  },
}
</code></pre>
<p>This gave:</p>
<ul>
<li><p>Validation support</p>
</li>
<li><p>Error messages</p>
</li>
</ul>
<hr />
<p><strong>Custom Expiry Date Input</strong></p>
<p>For expiry dates, I customized the edit field using a Material UI date input.</p>
<pre><code class="language-plaintext">Edit: ({ cell, column, row }) =&gt; (
  &lt;TextField
    type="date"
    value={cell.getValue()?.split("T")[0] || ""}
    onChange={(e) =&gt;
      (row._valuesCache[column.id] = e.target.value)
    }
    fullWidth
  /&gt;
)
</code></pre>
<p>This improved date editing significantly compared to plain text editing.</p>
<hr />
<p><strong>Status Field Was Read-Only</strong></p>
<p>The inventory status:</p>
<ul>
<li><p>fresh</p>
</li>
<li><p>expiring-soon</p>
</li>
<li><p>expired</p>
</li>
</ul>
<p>was automatically calculated from the expiry date.</p>
<p>Because of that, users should not edit status manually.</p>
<p>So I disabled editing for the status column.</p>
<pre><code class="language-plaintext">{
  header: "Status",
  enableEditing: false,
}
</code></pre>
<p>The status was displayed using colored Material UI chips.</p>
<pre><code class="language-javascript">&lt;Chip
  label={status}
  color={
    status === "fresh"
      ? "success"
      : status === "expiring-soon"
        ? "warning"
        : "error"
  }
/&gt;
</code></pre>
<p>This created a cleaner and more user-friendly inventory UI.</p>
<hr />
<p><strong>Saving Updated Rows</strong></p>
<p>Material React Table provides:</p>
<pre><code class="language-js">onEditingRowSave
</code></pre>
<p>which runs automatically when the user clicks the save button.</p>
<p>I implemented:</p>
<pre><code class="language-js">
  const handleSaveRow = async ({ values, table, row }) =&gt; {
    const errors = validateItem(values);

    if (Object.values(errors).some(Boolean)) {
      setValidationErrors(errors);
      return;
    }

    try {
      setValidationErrors({});

      const payload = {
        name: values.name,
        category: values.category,
        quantity: values.quantity,
        expiryDate: values.expiryDate,
      };

      await updateItem(row.original._id, payload);

      await fetchItems();

      toast.success("Item updated successfully");

      table.setEditingRow(null);
    } catch (error) {
      toast.error(error.response?.data?.message || "Failed to update item");
    }
  };
</code></pre>
<hr />
<p><strong>Frontend Validation Before Save</strong></p>
<p>Before sending updates to the backend, I validated the edited fields.</p>
<pre><code class="language-javascript">const errors = validateItem(values);

if (Object.values(errors).some(Boolean)) {
  setValidationErrors(errors);
  return;
}
</code></pre>
<p>This prevented invalid inventory updates.</p>
<hr />
<p><strong>Calling Update API</strong></p>
<p>After validation passed, I created the update payload.</p>
<pre><code class="language-plaintext">const payload = {
  name: values.name,
  category: values.category,
  quantity: values.quantity,
  expiryDate: values.expiryDate,
};
</code></pre>
<p>Then I called the backend update API.</p>
<pre><code class="language-plaintext">await updateItem(row.original._id, payload);
</code></pre>
<p>One important thing I learned here:</p>
<p><code>values</code> only contains editable/accessor fields.</p>
<p>So:</p>
<pre><code class="language-plaintext">values._id
</code></pre>
<p>was undefined.</p>
<p>The correct way was:</p>
<pre><code class="language-plaintext">row.original._id
</code></pre>
<p>because the original row still contains the MongoDB document ID.</p>
<hr />
<p><strong>Refetching Inventory After Save</strong></p>
<p>After updating an item successfully:</p>
<pre><code class="language-plaintext">await fetchItems();
</code></pre>
<p>I refetched inventory items from the backend instead of manually updating frontend state.</p>
<p>This approach helped maintain:</p>
<ul>
<li><p>Fresh backend data</p>
</li>
<li><p>Better consistency</p>
</li>
<li><p>Simpler state management</p>
</li>
<li><p>Cleaner architecture</p>
</li>
</ul>
<hr />
<p><strong>Adding Delete Row Functionality</strong></p>
<p>After implementing inline editing, the next important feature was deleting inventory items directly from the table.</p>
<p>Material React Table made this very easy using:</p>
<pre><code class="language-plaintext">renderRowActions
</code></pre>
<p>I added a delete icon button inside the row actions section.</p>
<pre><code class="language-plaintext">&lt;Tooltip title="Delete"&gt;
  &lt;IconButton
    color="error"
    onClick={() =&gt; handleDelete(row)}
  &gt;
    &lt;DeleteIcon /&gt;
  &lt;/IconButton&gt;
&lt;/Tooltip&gt;
</code></pre>
<p>When the user clicks the delete icon, the selected row is passed into:</p>
<pre><code class="language-plaintext">handleDelete(row)
</code></pre>
<hr />
<p><strong>Creating the Delete Handler</strong></p>
<p>I implemented the delete logic inside:</p>
<pre><code class="language-plaintext">handleDelete
</code></pre>
<pre><code class="language-plaintext">const handleDelete = async (row) =&gt; {
  try {
    await deleteItem(row.original._id);

    await fetchItems();

    toast.success("Item deleted successfully");
  } catch (error) {
    toast.error(
      error.response?.data?.message ||
        "Failed to delete item"
    );
  }
};
</code></pre>
<p>This function:</p>
<ul>
<li><p>Gets the MongoDB item ID</p>
</li>
<li><p>Calls backend delete API</p>
</li>
<li><p>Refetches latest inventory items</p>
</li>
<li><p>Shows success/error toast notifications</p>
</li>
</ul>
<hr />
<p><strong>Creating Delete API Function</strong></p>
<p>Inside:</p>
<pre><code class="language-plaintext">api.js
</code></pre>
<p>I created a reusable API function.</p>
<pre><code class="language-csharp">export const deleteItem = async (itemId) =&gt; {
  const res = await api.delete(`/items/${itemId}`);

  return res.data;
};
</code></pre>
<p>This keeps API logic reusable and separated from UI components.h backend data</p>
<ul>
<li><p>Better consistency</p>
</li>
<li><p>Simpler state management</p>
</li>
<li><p>Cleaner architecture</p>
</li>
</ul>
<hr />
<h3>Step 22: Implementing Server-Side Pagination &amp; Search using Material React Table</h3>
<p>After building the inventory management table, the next major improvement was implementing:</p>
<ul>
<li><p>Server-side pagination</p>
</li>
<li><p>Server-side global search</p>
</li>
</ul>
<p>This became important because inventory data can grow very large over time.</p>
<p>Fetching all items at once is not scalable.</p>
<p>Instead, the frontend should only request:</p>
<ul>
<li><p>the current page</p>
</li>
<li><p>current page size</p>
</li>
<li><p>current search query</p>
</li>
</ul>
<p>from the backend.</p>
<p>This improves:</p>
<ul>
<li><p>performance</p>
</li>
<li><p>scalability</p>
</li>
<li><p>API efficiency</p>
</li>
<li><p>database querying</p>
</li>
</ul>
<hr />
<p><strong>Managing Pagination State</strong></p>
<p>Inside <code>Items.jsx</code>, I created pagination state.</p>
<pre><code class="language-plaintext">const [pagination, setPagination] = useState({
  pageIndex: 0,
  pageSize: 10,
});
</code></pre>
<p>Material React Table uses:</p>
<ul>
<li><p><code>pageIndex</code> → current page</p>
</li>
<li><p><code>pageSize</code> → rows per page</p>
</li>
</ul>
<hr />
<p><strong>Tracking Total Database Rows</strong></p>
<p>Since pagination is handled on the backend, the frontend also needs to know:</p>
<ul>
<li>how many total rows exist in the database</li>
</ul>
<p>This is required so Material React Table can correctly render:</p>
<ul>
<li><p>total pages</p>
</li>
<li><p>pagination controls</p>
</li>
</ul>
<pre><code class="language-plaintext">const [rowCount, setRowCount] = useState(0);
</code></pre>
<hr />
<p><strong>Adding Global Search State</strong></p>
<p>Next, I added global search state.</p>
<pre><code class="language-plaintext">const [globalFilter, setGlobalFilter] = useState("");
</code></pre>
<p>This state stores the search value entered by the user.</p>
<hr />
<p><strong>Adding Debounced Search</strong></p>
<p>Without debouncing, every keystroke would trigger an API request.</p>
<p>Example:</p>
<pre><code class="language-plaintext">a
ap
app
appl
apple
</code></pre>
<p>This would cause 5 API calls.</p>
<p>To prevent unnecessary API requests, I implemented debouncing.</p>
<pre><code class="language-plaintext">const [debouncedGlobalFilter, setDebouncedGlobalFilter] = useState("");
</code></pre>
<pre><code class="language-plaintext">useEffect(() =&gt; {
  const timeout = setTimeout(() =&gt; {
    setDebouncedGlobalFilter(globalFilter);
  }, 500);

  return () =&gt; clearTimeout(timeout);
}, [globalFilter]);
</code></pre>
<p>Now the API only runs after the user stops typing for 500ms.</p>
<p>This significantly improves:</p>
<ul>
<li><p>performance</p>
</li>
<li><p>API efficiency</p>
</li>
<li><p>user experience</p>
</li>
</ul>
<hr />
<p><strong>Fetching Paginated Inventory Items</strong></p>
<p>I updated the API request to send:</p>
<ul>
<li><p>current page</p>
</li>
<li><p>page size</p>
</li>
<li><p>search query</p>
</li>
</ul>
<p>to the backend.</p>
<pre><code class="language-plaintext">const fetchItems = async () =&gt; {
  try {
    const res = await getItems(
      pagination.pageIndex + 1,
      pagination.pageSize,
      debouncedGlobalFilter,
    );

    setItems(res.data.items);

    setRowCount(res.data.totalItems);
  } catch (error) {
    toast.error(
      error.response?.data?.message ||
      "Failed to fetch items"
    );
  }
};
</code></pre>
<hr />
<p><strong>Refetching Data When Pagination Changes</strong></p>
<p>Then I added a <code>useEffect</code> to automatically refetch inventory items whenever:</p>
<ul>
<li><p>page changes</p>
</li>
<li><p>page size changes</p>
</li>
<li><p>search query changes</p>
</li>
</ul>
<pre><code class="language-plaintext">useEffect(() =&gt; {
  fetchItems();
}, [
  pagination.pageIndex,
  pagination.pageSize,
  debouncedGlobalFilter,
]);
</code></pre>
<p>This keeps the table synchronized with backend data.</p>
<hr />
<p><strong>Resetting to First Page During Search</strong></p>
<p>One important UX improvement was resetting pagination when the search query changes.</p>
<p>Without this:</p>
<ul>
<li><p>user may stay on page 5</p>
</li>
<li><p>search results may only contain 1 page</p>
</li>
<li><p>table could appear empty</p>
</li>
</ul>
<p>To fix this:</p>
<pre><code class="language-plaintext">useEffect(() =&gt; {
  setPagination((prev) =&gt; ({
    ...prev,
    pageIndex: 0,
  }));
}, [debouncedGlobalFilter]);
</code></pre>
<p>Now every new search starts from page 1.</p>
<hr />
<p><strong>Enabling Manual Pagination in Material React Table</strong></p>
<p>Inside <code>InventoryTable.jsx</code>, I enabled manual pagination.</p>
<pre><code class="language-plaintext">manualPagination: true,
</code></pre>
<p>This tells Material React Table:</p>
<blockquote>
<p>"Pagination is controlled by the backend."</p>
</blockquote>
<p>So MRT stops doing client-side pagination automatically.</p>
<hr />
<p><strong>Enabling Manual Server-Side Search</strong></p>
<p>I also enabled:</p>
<pre><code class="language-plaintext">manualFiltering: true,
</code></pre>
<p>This disables client-side searching/filtering.</p>
<p>Now the backend becomes responsible for filtering inventory items.</p>
<p>This is important because users expect search to work across the entire database — not just the currently loaded page.</p>
<hr />
<p><strong>Connecting Pagination State to Material React Table</strong></p>
<pre><code class="language-plaintext">onPaginationChange: setPagination,
</code></pre>
<p>This automatically updates React state whenever:</p>
<ul>
<li><p>page changes</p>
</li>
<li><p>rows per page changes</p>
</li>
</ul>
<hr />
<p><strong>Connecting Search State to Material React Table</strong></p>
<pre><code class="language-plaintext">onGlobalFilterChange: setGlobalFilter,
</code></pre>
<p>Whenever the user types into the search bar:</p>
<ul>
<li><p>MRT updates <code>globalFilter</code></p>
</li>
<li><p>debounce logic runs</p>
</li>
<li><p>backend API gets called</p>
</li>
</ul>
<hr />
<p><strong>Passing Controlled Table State</strong></p>
<pre><code class="language-plaintext">state: {
  pagination,
  globalFilter,
},
</code></pre>
<p>This allows React state to fully control Material React Table state.</p>
<hr />
<p><strong>Providing Total Row Count</strong></p>
<pre><code class="language-plaintext">rowCount,
</code></pre>
<p>This is extremely important for server-side pagination.</p>
<p>Without <code>rowCount</code>, Material React Table cannot determine:</p>
<ul>
<li><p>total pages</p>
</li>
<li><p>pagination range</p>
</li>
<li><p>last page</p>
</li>
</ul>
<hr />
<p><strong>Final Material React Table Configuration</strong></p>
<pre><code class="language-javascript">const table = useMaterialReactTable({
  columns,
  data: items,

  manualPagination: true,
  manualFiltering: true,

  rowCount,

  onPaginationChange: setPagination,
  onGlobalFilterChange: setGlobalFilter,

  state: {
    pagination,
    globalFilter,
  },

  enablePagination: true,
  enableGlobalFilter: true,
  enableColumnFilters: true,
  enableSorting: true,
});
</code></pre>
<hr />
<p><strong>Result</strong></p>
<p>After implementing server-side pagination and global search:</p>
<p>the inventory system became:</p>
<ul>
<li><p>scalable</p>
</li>
<li><p>faster</p>
</li>
<li><p>production-ready</p>
</li>
<li><p>database-driven</p>
</li>
</ul>
<p>Users can now:</p>
<ul>
<li><p>search across all inventory items</p>
</li>
<li><p>navigate large datasets efficiently</p>
</li>
<li><p>load data page-by-page</p>
</li>
<li><p>reduce frontend memory usage</p>
</li>
</ul>
<p>This created a much better experience for the ShelfLife inventory dashboard.</p>
<hr />
<p><strong>Updating the Frontend API for Pagination &amp; Search</strong></p>
<p>Previously, the frontend simply fetched all inventory items.</p>
<pre><code class="language-plaintext">export const getItems = async () =&gt; {
  const res = await api.get("/items/");

  return res.data;
};
</code></pre>
<p>But now the backend needed additional query parameters:</p>
<ul>
<li><p>current page</p>
</li>
<li><p>rows per page</p>
</li>
<li><p>search query</p>
</li>
</ul>
<p>So I updated the API function.</p>
<pre><code class="language-plaintext">export const getItems = async (
  page,
  limit,
  search,
) =&gt; {
  const res = await api.get(
    `/items?page=\({page}&amp;limit=\){limit}&amp;search=${search}`,
  );

  return res.data;
};
</code></pre>
<p>Now the frontend dynamically sends:</p>
<ul>
<li><p><code>page</code> → current pagination page</p>
</li>
<li><p><code>limit</code> → number of rows per page</p>
</li>
<li><p><code>search</code> → global search value</p>
</li>
</ul>
<p>Example request:</p>
<pre><code class="language-plaintext">/items?page=1&amp;limit=10&amp;search=milk
</code></pre>
<p>This allows the backend to:</p>
<ul>
<li><p>paginate database results</p>
</li>
<li><p>filter inventory items</p>
</li>
<li><p>return only required rows</p>
</li>
</ul>
<p>instead of sending the entire database to the frontend.</p>
<p>This approach is significantly more scalable and production-ready for large datasets.</p>
]]></content:encoded></item><item><title><![CDATA[Building a ShelfLife Household Inventory Tracker - Backend ]]></title><description><![CDATA[In many shared households, managing groceries becomes messy over time.
Multiple people live together,

Everyone buys food

Items get stored in different places

Expiry dates are ignored

Food gets was]]></description><link>https://blog.realdev.club/building-a-shelflife-household-inventory-tracker-backend</link><guid isPermaLink="true">https://blog.realdev.club/building-a-shelflife-household-inventory-tracker-backend</guid><category><![CDATA[Node.js]]></category><category><![CDATA[Express]]></category><category><![CDATA[MongoDB]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[backend]]></category><category><![CDATA[backend developments]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[MERN Stack]]></category><category><![CDATA[coding]]></category><dc:creator><![CDATA[Shubham Kumar Singh]]></dc:creator><pubDate>Tue, 19 May 2026 10:05:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/f93b0c40-2cec-4f2c-8bae-d647de130533.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In many shared households, managing groceries becomes messy over time.</p>
<p>Multiple people live together,</p>
<ul>
<li><p>Everyone buys food</p>
</li>
<li><p>Items get stored in different places</p>
</li>
<li><p>Expiry dates are ignored</p>
</li>
<li><p>Food gets wasted</p>
</li>
</ul>
<p>The real problem isn’t buying food — it’s <strong>tracking and managing it efficiently as a group</strong>.</p>
<hr />
<h3>What are we building?</h3>
<p><strong>ShelfLife</strong> is a collaborative expiry tracking application designed for shared households.</p>
<p>It helps roommates:</p>
<ul>
<li><p>Track groceries in one place</p>
</li>
<li><p>Get notified before items expire</p>
</li>
<li><p>Collaborate within a household</p>
</li>
<li><p>Compete to reduce food waste</p>
</li>
</ul>
<hr />
<h3>Goal</h3>
<p>To reduce food wastage by bringing <strong>visibility, accountability, and collaboration</strong> into everyday household management.</p>
<p>Instead of:</p>
<blockquote>
<p>“Who bought this?”<br />“Is this still good?”<br />“Why is this expired again?”</p>
</blockquote>
<p>We move to:</p>
<blockquote>
<p>“We know what we have.”<br />“We know when it expires.”<br />“We waste less.”</p>
</blockquote>
<hr />
<h3><strong>Core Features</strong></h3>
<ol>
<li><p><strong>Authentication &amp; User Management</strong><br />Every user starts here:</p>
<ul>
<li><p>Users can <strong>sign up</strong> with credentials</p>
</li>
<li><p>Users can <strong>log in securely</strong></p>
</li>
</ul>
<p>This ensures every action is tied to an authenticated user.</p>
</li>
<li><p><strong>Household System</strong></p>
<p>Why household?</p>
<p>Because this is <strong>collaborative</strong>, not individual.</p>
<ul>
<li><p>One user creates a household</p>
</li>
<li><p>Gets a <strong>6-char invite code</strong></p>
</li>
<li><p>Others join using code</p>
</li>
<li><p>User can <strong>view all other household members</strong>.</p>
</li>
<li><p>Any member has the flexibility to <strong>leave the household</strong> at any time.</p>
</li>
</ul>
</li>
<li><p><strong>Inventory Management</strong></p>
<p>Users can manage grocery items within a household:</p>
<ul>
<li><p>Add items with:</p>
<ul>
<li><p><strong>name</strong></p>
</li>
<li><p><strong>category</strong></p>
</li>
<li><p><strong>expiry date</strong></p>
</li>
<li><p><strong>quantity</strong></p>
</li>
</ul>
</li>
<li><p>The system automatically assigns a <strong>status</strong> to each item:</p>
<ul>
<li><p><strong>Fresh</strong> → expiry date is far away</p>
</li>
<li><p><strong>Expiring Soon</strong> → within 3 days</p>
</li>
<li><p><strong>Expired</strong> → past expiry date</p>
</li>
</ul>
</li>
<li><p>Users can:</p>
<ul>
<li><p><strong>Edit items</strong></p>
</li>
<li><p><strong>Delete items</strong></p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>Automated Status Updates (Cron Job)</strong></p>
<p>Item status changes over time, so it cannot be static.</p>
<p>Example:</p>
<ul>
<li><p>Today → Fresh</p>
</li>
<li><p>After 2 days → Expiring Soon</p>
</li>
<li><p>After expiry → Expired</p>
</li>
</ul>
<p>To handle this automatically:</p>
<ul>
<li><p>A <strong>daily cron job</strong> runs in the background</p>
</li>
<li><p>It checks all items in the database</p>
</li>
<li><p>Updates their status based on the current date and expiry date</p>
</li>
</ul>
<p>This ensures:</p>
<ul>
<li><p>Status is always <strong>accurate</strong></p>
</li>
<li><p>No manual updates are required</p>
</li>
<li><p>Users get <strong>real-time visibility</strong> of item conditions</p>
</li>
</ul>
</li>
</ol>
<hr />
<h3>Step 1: Initialize Project</h3>
<pre><code class="language-javascript">npm init -y
</code></pre>
<p>It Creates <code>package.json</code></p>
<hr />
<h3>Step 2: Install Dependencies</h3>
<pre><code class="language-plaintext">npm install express
npm install --save-dev nodemon
npm install dotenv // This package is used to load environment variables from the .env file.
</code></pre>
<hr />
<h3>Step 3: Setup App Entry (src/app.js)</h3>
<p>Create <code>src/app.js</code></p>
<pre><code class="language-javascript">import express from "express";

const app = express();

app.use(express.json());

export default app;
</code></pre>
<p>This file is responsible for:</p>
<ul>
<li><p>Initializing express app</p>
</li>
<li><p>Adding global middlewares</p>
</li>
</ul>
<hr />
<h3>Step 4: Environment Variables</h3>
<p>Create <code>.env</code> file:</p>
<pre><code class="language-javascript">PORT=3000
NODE_ENV=development
MONGODB_URI=your_mongodb_connection_string
</code></pre>
<hr />
<h3>Step 5: Server Entry Point (server.js)</h3>
<p>create <code>server.js</code></p>
<pre><code class="language-javascript">import "dotenv/config"; //This automatically loads .env variables into process.env
import app from "./src/app.js";

const PORT = process.env.PORT || 3000;

const start = async () =&gt; {
    app.listen(PORT, () =&gt; {
        console.log(`Server is running at \({PORT} in \){process.env.NODE_ENV} mode`);
    });
};

start().catch((err) =&gt; {
    console.error("Failed to start server", err);
    process.exit(1);
});
</code></pre>
<p>This file is responsible for:</p>
<ul>
<li><p>Starting the server</p>
</li>
<li><p>Handling startup errors</p>
</li>
</ul>
<hr />
<h3>Step 6: Scripts Setup</h3>
<p>Update <code>package.json</code>:</p>
<pre><code class="language-javascript">"scripts": {
  "start": "node server.js",
  "dev": "nodemon server.js"
}
</code></pre>
<p>Use:</p>
<pre><code class="language-plaintext">npm run dev
</code></pre>
<hr />
<h3>Step 7: Setup Database Connection</h3>
<p>Create:</p>
<pre><code class="language-plaintext">src/common/config/db.js
</code></pre>
<pre><code class="language-js">import mongoose from "mongoose";
import dns from "dns";

dns.setServers(["1.1.1.1", "8.8.8.8"]);

const connectDB = async () =&gt; {
    const conn = await mongoose.connect(process.env.MONGODB_URI);

    console.log(`MongoDB connected: ${conn.connection.host}`);
};

export default connectDB;
</code></pre>
<p>Initially, I tried connecting to <strong>MongoDB (cloud)</strong> but faced a DNS issue</p>
<p><strong>Problem:</strong></p>
<p>MongoDB connection was failing due to DNS resolution.</p>
<p><strong>Fix:</strong></p>
<p>After watching a YouTube video: , I added this:</p>
<pre><code class="language-javascript">const dns = require("dns");
dns.setServers(["1.1.1.1", "8.8.8.8"]);
</code></pre>
<p>This fixed the issue and I was able to connect to cloud MongoDB.</p>
<p>Update <code>server.js</code> :</p>
<pre><code class="language-javascript">import "dotenv/config"; //This automatically loads .env variables into process.env
import app from "./src/app.js";

import connectDB from "./src/common/config/db.js"

const PORT = process.env.PORT || 3000;

const start = async () =&gt; {
    await connectDB();
    app.listen(PORT, () =&gt; {
        console.log(`Server is running at \({PORT} in \){process.env.NODE_ENV} mode`);
    });
};

start().catch((err) =&gt; {
    console.error("Failed to start server", err);
    process.exit(1);
});
</code></pre>
<hr />
<h3>Step 8: Standard API Response Structure</h3>
<p><strong>Create:</strong></p>
<pre><code class="language-plaintext">src/common/utils/api-response.js
</code></pre>
<pre><code class="language-javascript">class ApiResponse {
    static ok(res, message, data = null) {
        return res.status(200).json({
            success: true,
            message,
            data
        });
    }
}

export default ApiResponse;
</code></pre>
<p><strong>Why This is Important</strong></p>
<p>Instead of writing:</p>
<pre><code class="language-javascript">res.status(200).json({ message: "Success" });
</code></pre>
<p>To avoid repeating response logic across multiple APIs, I implemented a reusable utility using the DRY (Don't Repeat Yourself) principle:</p>
<pre><code class="language-javascript">ApiResponse.ok(res, "Success", data);
</code></pre>
<hr />
<h3>Step 9: Standard API Error Structure</h3>
<p>While building APIs, handling errors properly is <strong>as important as handling success responses</strong>.</p>
<ol>
<li><strong>Create Custom Error Class</strong></li>
</ol>
<pre><code class="language-js">src/common/utils/api-error.js
</code></pre>
<pre><code class="language-javascript">class ApiError extends Error {
    // Inherit from built-in JavaScript Error
    constructor(statusCode, message) {
        super(message);

        this.statusCode = statusCode;

        // Capture clean stack trace
        Error.captureStackTrace(this, this.constructor);
    }

    static badRequest(message = "Bad Request") {
        return new ApiError(400, message);
    }

    static forbidden(message = "Forbidden") {
        return new ApiError(403, message);
    }

    static notFound(message = "Not Found") {
        return new ApiError(404, message);
    }
}

export default ApiError;
</code></pre>
<p><strong>Why Extend</strong> <code>Error</code><strong>?</strong></p>
<p>JavaScript provides built-in errors like:</p>
<ul>
<li><p><code>Error</code></p>
</li>
<li><p><code>TypeError</code></p>
</li>
<li><p><code>ReferenceError</code></p>
</li>
</ul>
<p>But they <strong>don’t include HTTP status codes</strong>, which are required in APIs.</p>
<p><strong>2. Global Error Middleware</strong></p>
<p>Create:</p>
<pre><code class="language-plaintext">src/common/middleware/error.middleware.js
</code></pre>
<pre><code class="language-javascript">import ApiError from "../utils/api-error.js";

const errorHandler = (err, req, res, next) =&gt; {

    // Handle known (custom) errors
    if (err instanceof ApiError) {
        return res.status(err.statusCode).json({
            success: false,
            message: err.message
        });
    }

    // Handle unknown errors
    return res.status(500).json({
        success: false,
        message: "Internal Server Error"
    });
};

export default errorHandler;
</code></pre>
<p><strong>3. Register Middleware in App</strong></p>
<p>Update <code>src/app.js</code>:</p>
<pre><code class="language-javascript">import express from "express";
import authRoute from "./modules/auth/auth.routes.js";
import errorHandler from "./common/middleware/error.middleware.js";

const app = express();
app.use(express.json());

app.use(errorHandler); 

export default app;
</code></pre>
<p>This must be the <strong>last middleware</strong> in the app.</p>
<p><strong>Final Error Flow</strong></p>
<pre><code class="language-plaintext">Request → Route → Controller → Service → throws ApiError → Passes to errorHandler middleware → JSON response 
</code></pre>
<hr />
<h3>Step 10: Authentication Module (Modular Structure)</h3>
<p>Now that we have:</p>
<ul>
<li><p>Clean folder structure</p>
</li>
<li><p>Database setup</p>
</li>
<li><p>Standard response &amp; error handling</p>
</li>
</ul>
<p>Next step is to build a <strong>modular authentication system</strong></p>
<hr />
<p><strong>Module Structure</strong></p>
<p>Inside <code>src/modules/auth/</code>, I created:</p>
<pre><code class="language-plaintext">auth/
├── auth.controller.js
├── auth.middleware.js
├── auth.model.js
├── auth.routes.js
├── auth.service.js
</code></pre>
<hr />
<p><strong>Why This Structure?</strong></p>
<p>Each layer has a clear responsibility:</p>
<ul>
<li><p><strong>Model</strong> → Database schema</p>
</li>
<li><p><strong>Routes</strong> → Define API endpoints</p>
</li>
<li><p><strong>Controller</strong> → Handle request/response</p>
</li>
<li><p><strong>Service</strong> → Business logic</p>
</li>
<li><p><strong>Middleware</strong> → Authentication / validation</p>
</li>
</ul>
<p>This makes code <strong>clean, maintainable, and scalable</strong></p>
<hr />
<p><strong>Create User Schema (</strong><code>auth.model.js</code><strong>)</strong></p>
<pre><code class="language-javascript">import mongoose from "mongoose";

const userSchema = new mongoose.Schema(
  {
    firstName: {
      type: String,
      required: true,
      trim: true,
    },

    lastName: {
      type: String,
      required: true,
      trim: true,
    },

    username: {
      type: String,
      required: true,
      unique: true,
      trim: true,
    },

    email: {
      type: String,
      required: true,
      unique: true,
      lowercase: true,
      trim: true,
    },

    phoneNumber: {
      type: String,
      required: true,
      trim: true,
    },

    password: {
      type: String,
      required: true,
    },
  },
  { timestamps: true },
);

const userModel = mongoose.model("users", userSchema);

export default userModel;
</code></pre>
<p><code>timestamps</code> automatically adds:</p>
<ul>
<li><p><code>createdAt</code></p>
</li>
<li><p><code>updatedAt</code></p>
</li>
</ul>
<hr />
<p><strong>Register Auth Routes in App.js</strong></p>
<p>Update <code>src/app.js</code>:</p>
<pre><code class="language-javascript">import express from "express";
import authRoute from "./modules/auth/auth.routes.js";
import errorMiddleware from "./common/middleware/error.middleware.js";

const app = express();

app.use(express.json());

// Register routes
app.use("/api/auth", authRoute);

// Global error handler
app.use(errorMiddleware);

export default app;
</code></pre>
<p>All auth APIs will now be available under:</p>
<pre><code class="language-plaintext">/api/auth/*
</code></pre>
<hr />
<p>All auth APIs will now be available under:</p>
<pre><code class="language-plaintext">/api/auth/*
</code></pre>
<hr />
<p><strong>Define Routes (</strong><code>auth.routes.js</code><strong>)</strong></p>
<pre><code class="language-javascript">import { Router } from "express";
import * as controller from "./auth.controller.js";

const router = Router();

router.post("/register", controller.signup);

export default router;
</code></pre>
<hr />
<p><strong>Controller Layer (</strong><code>auth.controller.js</code><strong>)</strong></p>
<pre><code class="language-javascript">import ApiResponse from "../../common/utils/api-response.js";
import * as authService from "./auth.service.js";

const register = async (req, res, next) =&gt; {
  try {
    const user = await authService.register(req.body);

    ApiResponse.ok(res, "User get Created", user);
  } catch (error) {
    next(error); // pass error to global error middleware
  }
};
export { register };
</code></pre>
<p><strong>Responsibility of Controller</strong></p>
<ul>
<li><p>Calls service layer</p>
</li>
<li><p>Sends response using <code>ApiResponse</code></p>
</li>
<li><p>Keeps logic minimal</p>
</li>
</ul>
<hr />
<p><strong>Business Logic (</strong><code>auth.service.js</code><strong>)</strong></p>
<pre><code class="language-javascript">import bcrypt from 'bcrypt';
import userModel from "./auth.model.js";
import ApiError from "../../common/utils/api-error.js";

const register = async ({
  firstName,
  lastName,
  username,
  email,
  phoneNumber,
  password,
}) =&gt; {
  const userExist = await userModel.findOne({
    email,
  });

  if (userExist) {
    throw ApiError.forbidden("User Already Exists");
  }

  const hashedPassword = await bcrypt.hash(password, 10);

  const newUser = await userModel.create({
    firstName,
    lastName,
    username,
    email,
    phoneNumber,
    password: hashedPassword,
  });

  return {
    userId: newUser._id,
    firstName: newUser.firstName,
    lastName: newUser.lastName,
    username: newUser.username,
    email: newUser.email,
    phoneNumber: newUser.phoneNumber,
  };
};
</code></pre>
<hr />
<p><strong>Final Result: Testing /api/auth/register API Using Postman</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/8e4d54f5-cd97-4a48-ae00-852175973f8e.png" alt="" style="display:block;margin:0 auto" />

<hr />
<p><strong>Request Flow</strong></p>
<pre><code class="language-plaintext">POST /api/auth/register
  ↓
auth.routes.js
  ↓
auth.controller.js
  ↓
auth.service.js
  ↓
MongoDB
</code></pre>
<hr />
<h3>Step 11: Implement Login</h3>
<p>Now that the register API is ready, the next step is to allow users to login securely using JWT-based authentication.</p>
<p>Instead of using a single token, we improve security by implementing:</p>
<ul>
<li><p>Access Token</p>
</li>
<li><p>Refresh Token</p>
</li>
</ul>
<p>This follows modern authentication practices used in production applications.</p>
<hr />
<p><strong>Why Use Access Token + Refresh Token?</strong></p>
<p>Previously, authentication systems often used a single JWT token.</p>
<p>Problem:</p>
<p>If that token gets stolen, the attacker can access protected APIs until the token expires.</p>
<p>To improve security, authentication is usually split into two tokens:</p>
<table>
<thead>
<tr>
<th>Token</th>
<th>Purpose</th>
<th>Expiry</th>
</tr>
</thead>
<tbody><tr>
<td>Access Token</td>
<td>Access protected APIs</td>
<td>Short-lived (10–15 mins)</td>
</tr>
<tr>
<td>Refresh Token</td>
<td>Generate new access tokens</td>
<td>Long-lived (7–30 days)</td>
</tr>
</tbody></table>
<hr />
<p><strong>Why Access Token Is Short-Lived</strong></p>
<p>Even if an attacker steals the access token:</p>
<ul>
<li><p>it expires quickly</p>
</li>
<li><p>attacker loses access soon</p>
</li>
</ul>
<p>But refresh tokens live much longer, so they require stronger protection.</p>
<hr />
<p><strong>JWT Utility Functions</strong></p>
<p>Instead of writing JWT logic multiple times, I created reusable utility functions.</p>
<p>Create:</p>
<p><code>src/common/utils/jwt.utils.js</code></p>
<pre><code class="language-js">import jwt from "jsonwebtoken";

const generateAccessToken = (payload) =&gt; {
  return jwt.sign(payload, process.env.JWT_ACCESS_SECRET, {
    expiresIn: process.env.JWT_ACCESS_EXPIRES_IN || "15m",
  });
};

const generateRefreshToken = (payload) =&gt; {
  return jwt.sign(payload, process.env.JWT_REFRESH_SECRET, {
    expiresIn: process.env.JWT_REFRESH_EXPIRES_IN || "7d",
  });
};

const verifyAccessToken = (token) =&gt; {
  return jwt.verify(token, process.env.JWT_ACCESS_SECRET);
};

export {
  generateAccessToken,
  generateRefreshToken,
  verifyAccessToken,
};
</code></pre>
<hr />
<p><strong>Update Environment Variables</strong></p>
<p>Update <code>.env</code></p>
<pre><code class="language-plaintext">JWT_ACCESS_SECRET=your_access_secret
JWT_REFRESH_SECRET=your_refresh_secret

JWT_ACCESS_EXPIRES_IN=15m
JWT_REFRESH_EXPIRES_IN=7d
</code></pre>
<hr />
<p><strong>Define Routes</strong></p>
<p>Inside <code>auth.route.js</code>, we define the login route:</p>
<pre><code class="language-javascript">import { Router } from "express";
import * as controller from "./auth.controller.js";

const router = Router();

router.post("/register", controller.register);
router.post("/login", controller.login)

export default router;
</code></pre>
<p>This creates an endpoint:</p>
<pre><code class="language-plaintext">POST /api/auth/login
</code></pre>
<hr />
<p><strong>Controller Layer</strong></p>
<p>Now we handle the request in the controller <code>auth.controller.js</code>.</p>
<pre><code class="language-js">import ApiResponse from "../../common/utils/api-response.js";

import * as authService from "./auth.service.js";

const login = async (req, res, next) =&gt; {
  try {
    const { accessToken, refreshToken, user } = await authService.login(
      req.body,
    );
    res.cookie("refreshToken", refreshToken, {
      httpOnly: true,
      secure: process.env.NODE_ENV === "production",
      maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
    });
    ApiResponse.ok(res, "Login Successful", { accessToken, user });
  } catch (error) {
    next(error);
  }
};

export { login };
</code></pre>
<hr />
<p><strong>What Are Cookies?</strong></p>
<p>Cookies are small pieces of data stored by the browser for a website.</p>
<hr />
<p><strong>Why Cookies Exist</strong></p>
<p>HTTP is stateless.</p>
<p>That means every request is independent.</p>
<p>Example:</p>
<pre><code class="language-plaintext">Request 1 → Login

Request 2 → Fetch Profile
</code></pre>
<p>Normally, the server cannot remember that both requests came from the same user.</p>
<p>Cookies solve this problem.</p>
<hr />
<p><strong>How Cookies Work :</strong></p>
<p><strong>Step 1: Server Sends Cookie</strong></p>
<p>Backend response:</p>
<pre><code class="language-plaintext">Set-Cookie: refreshToken=abc123
</code></pre>
<p>Browser stores it automatically.</p>
<p><strong>Step 2: Browser Sends It Automatically</strong></p>
<p>Next request:</p>
<pre><code class="language-plaintext">Cookie: refreshToken=abc123
</code></pre>
<p>Now the server can identify the user.</p>
<hr />
<p><strong>Important Cookie Security Options</strong></p>
<p><strong>1. httpOnly</strong></p>
<pre><code class="language-plaintext">httpOnly: true
</code></pre>
<p>JavaScript cannot access the cookie.</p>
<p>Protects against XSS attacks.</p>
<p><strong>2. secure</strong></p>
<pre><code class="language-plaintext">secure: true
</code></pre>
<p>if <code>secure: true</code> Cookie is sent only over HTTPS and if <code>secure: false</code> cookies is send over HTTP</p>
<p><strong>3. sameSite</strong></p>
<pre><code class="language-plaintext">sameSite: "strict"
</code></pre>
<p>Controls cross-site cookie sharing.</p>
<p>Helps prevent CSRF attacks.</p>
<hr />
<p><strong>Responsibility of Controller</strong></p>
<ul>
<li><p>Receive request data (<code>req.body</code>)</p>
</li>
<li><p>Call service layer</p>
</li>
<li><p>Store refresh token in cookies</p>
</li>
<li><p>Send access token in response</p>
</li>
<li><p>Pass errors to global error handler</p>
</li>
</ul>
<hr />
<p><strong>Service Layer (Business Logic)</strong></p>
<p>Now the actual authentication logic lives in <code>auth.service.js</code>:</p>
<pre><code class="language-javascript">import bcrypt from "bcrypt";

import userModel from "./auth.model.js";

import ApiError from "../../common/utils/api-error.js";

import {
  generateAccessToken,
  generateRefreshToken,
} from "../../common/utils/jwt.utils.js";

const login = async ({ email, password }) =&gt; {
  const userExist = await userModel.findOne({
    email: email,
  });

  if (!userExist) {
    throw ApiError.forbidden("User Not Found");
  }

  const correctPassword = await bcrypt.compare(password, userExist.password);

  if (correctPassword) {
    const accessToken = generateAccessToken({ userId: userExist._id });
    const refreshToken = generateRefreshToken({ userId: userExist._id });
    return {
      accessToken,
      refreshToken,
      user: {
        userId: userExist._id,
        firstName: userExist.firstName,
        lastName: userExist.lastName,
        username: userExist.username,
        email: userExist.email,
        phoneNumber: userExist.phoneNumber,
      },
    };
  } else {
    throw ApiError.forbidden("Password is invalid");
  }
};

export {
  login
};
</code></pre>
<hr />
<p><strong>Final Result: Testing /api/auth/login API Using Postman</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/db3a06bf-6d1d-457a-b1b4-66b48c0a8b97.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>Step 12: Refresh Access Token</h3>
<p>Previously, during login:</p>
<ul>
<li><p>Access Token was returned in the response</p>
</li>
<li><p>Refresh Token was stored securely in HTTP-only cookies</p>
</li>
</ul>
<p>But access tokens are short-lived.</p>
<p>Example:</p>
<ul>
<li><p>Access Token → expires in 15 minutes</p>
</li>
<li><p>Refresh Token → expires in 7 days</p>
</li>
</ul>
<p>So after 15 minutes, protected APIs stop working because the access token becomes invalid.</p>
<p>Instead of asking the user to log in again, we use the refresh token to generate a new access token automatically.</p>
<hr />
<p><strong>Why Refresh Tokens Are Important</strong></p>
<p>Without refresh tokens:</p>
<ul>
<li><p>User would need to login repeatedly</p>
</li>
<li><p>Poor user experience</p>
</li>
<li><p>More authentication requests</p>
</li>
</ul>
<p>With refresh tokens:</p>
<ul>
<li><p>Access tokens remain short-lived (more secure)</p>
</li>
<li><p>Users stay logged in smoothly</p>
</li>
<li><p>Better production-level authentication flow</p>
</li>
</ul>
<hr />
<h1>Authentication Flow</h1>
<p>Login<br />↓<br />Access Token generated<br />↓<br />Refresh Token stored in cookie<br />↓<br />Access Token expires<br />↓<br />Frontend calls /refresh<br />↓<br />Backend verifies refresh token<br />↓<br />New Access Token generated</p>
<hr />
<p><strong>Update JWT Utility Functions</strong></p>
<p>Previously, we only added:</p>
<ul>
<li><p>generateAccessToken()</p>
</li>
<li><p>generateRefreshToken()</p>
</li>
</ul>
<p>Now we also need:</p>
<ul>
<li>verifyRefreshToken()</li>
</ul>
<p>Update:</p>
<p><code>src/common/utils/jwt.utils.js</code></p>
<pre><code class="language-js">import jwt from "jsonwebtoken";

const generateAccessToken = (payload) =&gt; {
  return jwt.sign(
    payload,
    process.env.JWT_ACCESS_SECRET,
    {
      expiresIn:
        process.env.JWT_ACCESS_EXPIRES_IN || "15m",
    }
  );
};

const generateRefreshToken = (payload) =&gt; {
  return jwt.sign(
    payload,
    process.env.JWT_REFRESH_SECRET,
    {
      expiresIn:
        process.env.JWT_REFRESH_EXPIRES_IN || "7d",
    }
  );
};

const verifyAccessToken = (token) =&gt; {
  return jwt.verify(
    token,
    process.env.JWT_ACCESS_SECRET
  );
};

const verifyRefreshToken = (token) =&gt; {
  return jwt.verify(
    token,
    process.env.JWT_REFRESH_SECRET
  );
};

export {
  generateAccessToken,
  generateRefreshToken,
  verifyAccessToken,
  verifyRefreshToken,
};
</code></pre>
<hr />
<p><strong>Define Refresh Route</strong></p>
<p>Update:</p>
<p><code>src/modules/auth/auth.routes.js</code></p>
<pre><code class="language-javascript">import { Router } from "express";
import * as controller from "./auth.controller.js";

const router = Router();

router.post("/register", controller.register);

router.post("/login", controller.login);

router.post("/refresh", controller.refresh);

export default router;
</code></pre>
<p>This creates:</p>
<pre><code class="language-plaintext">POST /api/auth/refresh
</code></pre>
<hr />
<p><strong>Controller Layer</strong></p>
<p>Now we handle refresh requests inside:</p>
<p><code>src/modules/auth/auth.controller.js</code></p>
<pre><code class="language-javascript">const refresh = async (req, res, next) =&gt; {
  try {
    const { accessToken } =
      await authService.refresh(
        req.cookies.refreshToken
      );

    ApiResponse.ok(
      res,
      "Token refreshed successfully",
      { accessToken }
    );
  } catch (error) {
    next(error);
  }
};
</code></pre>
<p><strong>Why Read Token From Cookies?</strong></p>
<p>Because refresh tokens are sensitive.</p>
<p>Storing them inside:</p>
<pre><code class="language-plaintext">httpOnly cookies
</code></pre>
<p>prevents JavaScript from accessing them.</p>
<p>This protects against:</p>
<ul>
<li><p>XSS attacks</p>
</li>
<li><p>Token theft through frontend scripts</p>
</li>
</ul>
<hr />
<p><strong>Service Layer (Business Logic)</strong></p>
<p>Now the actual refresh logic lives inside:</p>
<p><code>src/modules/auth/auth.service.js</code></p>
<pre><code class="language-javascript">const refresh = async (token) =&gt; {
  if (!token) {
    throw ApiError.unauthorized(
      "Refresh token missing"
    );
  }

  const decoded =
    verifyRefreshToken(token);

  const userExists =
    await userModel.findOne({
      _id: decoded.userId,
    });

  if (!userExists) {
    throw ApiError.notFound(
      "User Not found"
    );
  }

  const accessToken =
    generateAccessToken({
      userId: userExists._id,
    });

  return { accessToken };
};
</code></pre>
<hr />
<p><strong>Final Refresh Flow</strong></p>
<p>Frontend Request<br />↓<br />/api/auth/refresh<br />↓<br />Read refreshToken cookie<br />↓<br />Verify refresh token<br />↓<br />Find user<br />↓<br />Generate new access token<br />↓<br />Send response</p>
<hr />
<h3>Step 13: Get Current User &amp; Logout APIs</h3>
<p>After implementing:</p>
<ul>
<li><p>Register</p>
</li>
<li><p>Login</p>
</li>
<li><p>Refresh Token</p>
</li>
</ul>
<p>the next important step is handling:</p>
<ul>
<li><p>Fetching logged-in user details</p>
</li>
<li><p>Logging users out securely</p>
</li>
</ul>
<p>These are common authentication features used in almost every production application.</p>
<hr />
<p><strong>Why Do We Need GetMe API?</strong></p>
<p>After login, the frontend often needs user information like:</p>
<ul>
<li><p>Name</p>
</li>
<li><p>Email</p>
</li>
<li><p>Username</p>
</li>
<li><p>Phone number</p>
</li>
</ul>
<p>Instead of storing all user data in frontend state permanently, we create a dedicated API:</p>
<pre><code class="language-plaintext">GET /api/auth/getme
</code></pre>
<p>This endpoint:</p>
<ul>
<li><p>Verifies the access token</p>
</li>
<li><p>Identifies the logged-in user</p>
</li>
<li><p>Returns fresh user details</p>
</li>
</ul>
<p>This helps keep frontend authentication state synchronized with the backend.</p>
<hr />
<p><strong>Why Do We Need Logout?</strong></p>
<p>Even though JWT authentication is stateless, users still need a secure way to end their session.</p>
<p>During login:</p>
<ul>
<li><p>Access token is stored on frontend</p>
</li>
<li><p>Refresh token is stored inside cookies</p>
</li>
</ul>
<p>If logout is not implemented:</p>
<ul>
<li><p>Refresh token remains active</p>
</li>
<li><p>User session can continue unintentionally</p>
</li>
</ul>
<p>So during logout we:</p>
<ul>
<li><p>Clear refresh token cookie</p>
</li>
<li><p>End user session safely</p>
</li>
</ul>
<hr />
<p><strong>Define Routes</strong></p>
<p>Update:</p>
<p><code>src/modules/auth/auth.routes.js</code></p>
<pre><code class="language-javascript">import { Router } from "express";
import authMiddleware from "./auth.middleware.js";

import * as controller from "./auth.controller.js";

const router = Router();

router.post("/register", controller.register);

router.post("/login", controller.login);

router.post("/refresh", controller.refresh);

router.get(
  "/getme",
  authMiddleware,
  controller.getMe
);

router.get(
  "/logout",
  authMiddleware,
  controller.logout
);

export default router;
</code></pre>
<hr />
<p><strong>Controller Layer</strong></p>
<p>Update:</p>
<p><code>src/modules/auth/auth.controller.js</code></p>
<pre><code class="language-js">const getMe = async (req, res, next) =&gt; {
  try {
    const userId = req.userId;

    console.log("userId", userId);

    const { user } =
      await authService.getMe(userId);

    ApiResponse.ok(
      res,
      "User get successfully",
      { user }
    );
  } catch (error) {
    next(error);
  }
};

const logout = async (req, res, next) =&gt; {
  try {
    res.clearCookie("refreshToken");

    ApiResponse.ok(
      res,
      "Logout Success"
    );
  } catch (error) {
    next(error);
  }
};
</code></pre>
<hr />
<p><strong>What Does clearCookie() Do?</strong></p>
<pre><code class="language-plaintext">res.clearCookie("refreshToken");
</code></pre>
<p>This removes the refresh token from browser cookies.</p>
<p>Result:</p>
<ul>
<li><p>User can no longer refresh access tokens</p>
</li>
<li><p>Session is effectively terminated</p>
</li>
</ul>
<hr />
<h3>Service Layer (Business Logic)</h3>
<h3>Update:</h3>
<p><code>src/modules/auth/auth.service.js</code></p>
<pre><code class="language-javascript">const getMe = async (userId) =&gt; {
  if (!userId) {
    throw ApiError.notFound(
      "user not found"
    );
  }

  const userExist =
    await userModel.findOne({
      _id: userId,
    });

  if (!userExist) {
    throw ApiError.notFound(
      "User Not found"
    );
  }

  return {
    user: {
      userId: userExist._id,
      firstName: userExist.firstName,
      lastName: userExist.lastName,
      username: userExist.username,
      email: userExist.email,
      phoneNumber:
        userExist.phoneNumber,
    },
  };
};
</code></pre>
<hr />
<h3><strong>Step 14: Create household inside households Module</strong></h3>
<p>Now that authentication is complete, the next step is to allow users to create household</p>
<hr />
<p><strong>Requirements</strong></p>
<p>After logging in, a user should be able to:</p>
<ul>
<li><p><strong>Create a new household</strong> by providing:</p>
<ul>
<li><p>a valid <strong>household name</strong></p>
</li>
<li><p>a <strong>6-character invite code</strong> (chosen by the user)</p>
</li>
</ul>
</li>
<li><p>Only <strong>authenticated users</strong> are allowed to create a household.</p>
</li>
</ul>
<hr />
<p><strong>Module Structure</strong></p>
<p>Inside <code>src/modules/</code>, create a new folder:</p>
<pre><code class="language-plaintext">src/modules/households/
├── households.controller.js
├── households.middleware.js
├── households.model.js
├── households.routes.js
├── households.service.js
</code></pre>
<hr />
<p><strong>Create HouseHold Schema</strong></p>
<p>Create:</p>
<pre><code class="language-plaintext">src/modules/households/households.model.js
</code></pre>
<pre><code class="language-javascript">import mongoose from "mongoose";

const houseHoldSchema = new mongoose.Schema({
    name: String,
    inviteCode: String,
    members: [mongoose.Types.ObjectId],
    createdBy: mongoose.Types.ObjectId
}, { timestamps: true })

const houseHoldModel = mongoose.model("households", houseHoldSchema)

export default houseHoldModel;
</code></pre>
<hr />
<p><strong>Authentication Middleware</strong></p>
<p>To ensure only logged-in users can create organisations:</p>
<p>Inside:</p>
<pre><code class="language-plaintext">src/modules/auth/auth.middleware.js
</code></pre>
<pre><code class="language-javascript">import jwt from 'jsonwebtoken';

const authMiddleware = async (req, res, next) =&gt; {
    try{
        const token = req.headers.token;

        const decode = jwt.verify(token, process.env.JWT_SECRET);

        const userExists = await userModel.findOne({
            _id: decode.userId
        })

        if(!userExists){
            throw ApiError.notFound("User Not found");
        }
    
        req.userId = decode.userId;
    
        next();
    } catch (error){
        next(error)
    }
};

export default authMiddleware;
</code></pre>
<p><strong>Update Global Error Middleware</strong></p>
<p>Update your error middleware to handle JWT-specific errors:</p>
<pre><code class="language-js">src/common/middleware/error-middleware.js
</code></pre>
<pre><code class="language-js">import ApiError from "../utils/api-error.js";

const errorHandler = (err, req, res, next) =&gt; {

    // Handle known (custom) errors
    if (err instanceof ApiError) {
        return res.status(err.statusCode).json({
            success: false,
            message: err.message
        });
    }

    if (err.name === 'JsonWebTokenError') {
        return res.status(401).json({
            success: false,
            message: err.message
        });
    }



    // Handle unknown errors
    return res.status(500).json({
        success: false,
        message: "Internal Server Error"
    });
};

export default errorHandler;
</code></pre>
<hr />
<p><strong>Register Household Routes</strong></p>
<p>Update <code>src/app.js</code>:</p>
<pre><code class="language-javascript">import householdsRoute from './modules/households/households.routes.js'

app.use("/api/households", orgRoute);
</code></pre>
<p>Now all households APIs will be available under:</p>
<pre><code class="language-plaintext">/api/households/* 
</code></pre>
<hr />
<p><strong>Define Routes</strong></p>
<p><strong>Create:</strong></p>
<pre><code class="language-plaintext">src/modules/households/households.routes.js
</code></pre>
<pre><code class="language-javascript">import { Router } from "express";
import authMiddleware from "../auth/auth.middleware.js";
import * as controller from './households.controller.js';

const router = Router();

router.post('/', authMiddleware, controller.createHouseHold);

export default router;
</code></pre>
<hr />
<p><strong>Controller Layer</strong></p>
<p><strong>Create:</strong></p>
<pre><code class="language-plaintext">src/modules/households/households.controller.js
</code></pre>
<pre><code class="language-javascript">import ApiResponse from "../../common/utils/api-response.js";
import * as householdService from './households.service.js'

const createHouseHold = async (req, res, next) =&gt; {
    try {
        const data = await householdService.createHouseHold(req);
        ApiResponse.ok(res, "Household Created Successfully", data);
    } catch (err) {
        next(err);
    }
}

export {
    createHouseHold
}
</code></pre>
<p><strong>Responsibility of Controller</strong></p>
<ul>
<li><p>Calls service layer</p>
</li>
<li><p>Sends success response</p>
</li>
<li><p>Passes errors to global middleware</p>
</li>
</ul>
<hr />
<p><strong>Business Logic (Service Layer)</strong></p>
<p>Create:</p>
<pre><code class="language-plaintext">src/modules/households/households.service.js
</code></pre>
<pre><code class="language-javascript">const createHouseHold = async (req) =&gt; {
  const householdName = req.body.householdName;
  const inviteCode = req.body.inviteCode;
  const createdBy = req.userId;

  // CHECK IF INVITE CODE ALREADY EXISTS
  const inviteCodeExists = await houseHoldModel.findOne({
    inviteCode,
  });

  if (inviteCodeExists) {
    throw ApiError.badRequest("Invite code already exists");
  }

  const members = [req.userId];
  const newHouseHold = await houseHoldModel.create({
    householdName,
    inviteCode,
    members,
    createdBy,
  });

  // UPDATE USER WITH HOUSEHOLD ID
  await userModel.findByIdAndUpdate(createdBy, {
    householdId: newHouseHold._id,
  });

  return {
    householdId: newHouseHold._id,
    name: newHouseHold.name,
    members: newHouseHold.members,
    inviteCode: newHouseHold.inviteCode,
  };
};
</code></pre>
<hr />
<p><strong>Request Flow</strong></p>
<pre><code class="language-plaintext">POST /api/households/
   ↓
auth.middleware.js (verify token)
   ↓
households.controller.js
   ↓
households.service.js
   ↓
MongoDB
   ↓
Response
</code></pre>
<hr />
<p><strong>Final Result: Testing api/households/ API Using Postman</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/891b2c64-4804-470d-8d97-b0e111020009.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3><strong>Step 14:</strong> Join Household Feature</h3>
<p>After implementing household creation, the next step is to allow users to <strong>join an existing household using an invite code</strong>.</p>
<hr />
<p><strong>Define Routes</strong></p>
<p><strong>Create:</strong></p>
<pre><code class="language-plaintext">src/modules/households/households.routes.js
</code></pre>
<pre><code class="language-javascript">import { Router } from "express";
import authMiddleware from "../auth/auth.middleware.js";
import * as controller from './households.controller.js';

const router = Router();

router.post('/', authMiddleware, controller.createHouseHold);
router.post('/join', authMiddleware, controller.joinHouseHold);

export default router;
</code></pre>
<hr />
<p><strong>Controller Layer</strong></p>
<p><strong>Create:</strong></p>
<pre><code class="language-plaintext">src/modules/households/households.controller.js
</code></pre>
<pre><code class="language-javascript">import ApiResponse from "../../common/utils/api-response.js";
import * as householdService from './households.service.js'

const joinHouseHold = async (req, res, next) =&gt; {
    try {
        const data = await householdService.joinHouseHold(req);
        ApiResponse.ok(res, "Household Joined Successfully", data);
    } catch (err) {
        next (err);
    }
}

export {
    joinHouseHold
}
</code></pre>
<p><strong>Responsibility of Controller</strong></p>
<ul>
<li><p>Calls service layer</p>
</li>
<li><p>Sends success response</p>
</li>
<li><p>Passes errors to global middleware</p>
</li>
</ul>
<hr />
<p><strong>Business Logic (Service Layer)</strong></p>
<p>Create:</p>
<pre><code class="language-plaintext">src/modules/households/households.service.js
</code></pre>
<pre><code class="language-javascript">const joinHouseHold = async (req) =&gt; {
  const { inviteCode } = req.body;

  const houseHoldExists = await houseHoldModel.findOne({
    inviteCode,
  });

  if (!houseHoldExists) {
    throw ApiError.badRequest("Invalid Invite Code");
  }

  const newMember = req.userId;

  const memberExists = houseHoldExists?.members?.includes(newMember);

  if (memberExists) {
    throw ApiError.badRequest("Member already exists in household");
  }

  houseHoldExists.members.push(newMember);

  await houseHoldExists.save();

  // UPDATE USER
  await userModel.findByIdAndUpdate(newMember, {
    householdId: houseHoldExists._id,
  });

  return {
    householdId: houseHoldExists._id,
    name: houseHoldExists.name,
    members: houseHoldExists.members,
  };
};
</code></pre>
<hr />
<p><strong>Request Flow</strong></p>
<pre><code class="language-plaintext">POST /api/households/join
   ↓
auth.middleware.js (verify token)
   ↓
households.controller.js
   ↓
households.service.js
   ↓
MongoDB
   ↓
Response
</code></pre>
<hr />
<p><strong>Final Result: Testing api/households/join API Using Postman</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/2bf6503e-5eaf-4a26-adcb-50342baf091d.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>Step 15: Get All Household Members</h3>
<p>After implementing join functionality, the next step is to allow users to <strong>view all members of a household</strong>.</p>
<hr />
<p><strong>Update Household Model (Add Reference)</strong></p>
<p>To fetch full user details, we need to define a reference in the schema.</p>
<p>Update:</p>
<pre><code class="language-plaintext">src/modules/households/households.model.js
</code></pre>
<pre><code class="language-javascript">import mongoose from "mongoose";

const houseHoldSchema = new mongoose.Schema({
    name: String,
    inviteCode: String,
    members: [{
        type: mongoose.Types.ObjectId,
        ref: "users" // reference to users collection
    }],
    createdBy: mongoose.Types.ObjectId
}, { timestamps: true });

const houseHoldModel = mongoose.model("households", houseHoldSchema);

export default houseHoldModel;
</code></pre>
<p><strong>Why Add</strong> <code>ref</code><strong>?</strong></p>
<ul>
<li><p>MongoDB stores only <strong>ObjectIds</strong> in <code>members</code></p>
</li>
<li><p><code>ref: "users"</code> tells Mongoose:</p>
<p>“These IDs belong to the users collection”</p>
</li>
<li><p>This enables the use of <strong>populate</strong> (explained below)</p>
</li>
</ul>
<hr />
<p><strong>Define Routes</strong></p>
<p>Update:</p>
<pre><code class="language-plaintext">src/modules/households/households.routes.js
</code></pre>
<pre><code class="language-javascript">import { Router } from "express";
import authMiddleware from "../auth/auth.middleware.js";
import * as controller from './households.controller.js';

const router = Router();

router.post('/', authMiddleware, controller.createHouseHold);
router.post('/join', authMiddleware, controller.joinHouseHold);
router.get('/:id/members', authMiddleware, controller.getAllMembers)

export default router;
</code></pre>
<hr />
<p><strong>Controller Layer</strong></p>
<p>Create / Update:</p>
<pre><code class="language-plaintext">src/modules/households/households.controller.js
</code></pre>
<pre><code class="language-javascript">const getAllMembers = async (req, res, next) =&gt; {
    try {
        const data = await householdService.getAllMembers(req);
        ApiResponse.ok(res, "All Members", data);
    } catch (err) {
        next(err);
    }
};
</code></pre>
<hr />
<p><strong>Service Layer (Business Logic)</strong></p>
<p>Create / Update:</p>
<pre><code class="language-plaintext">src/modules/households/households.service.js
</code></pre>
<pre><code class="language-javascript">const getAllMembers = async (req) =&gt; {
    const houseHoldId = req.params.id;

    const houseHoldExists = await houseHoldModel
        .findById(houseHoldId)
        .populate('members', 'name email');

    if (!houseHoldExists) {
        throw ApiError.notFound("Household not found");
    }

    return houseHoldExists.members;
};
</code></pre>
<hr />
<p><strong>What is</strong> <code>.populate()</code><strong>?</strong></p>
<p>In MongoDB, we store references like this:</p>
<pre><code class="language-javascript">members: [ObjectId("1234"), ObjectId("5678")]
</code></pre>
<p>If we fetch data without populate:</p>
<pre><code class="language-javascript">{
  "members": ["1234", "5678"]
}
</code></pre>
<p>This is not useful for the frontend.</p>
<hr />
<p><strong>Using Populate</strong></p>
<pre><code class="language-javascript">.populate('members', 'name email')
</code></pre>
<p>This tells Mongoose:</p>
<blockquote>
<p>Replace member IDs with actual user data (only name and email)</p>
</blockquote>
<hr />
<p><strong>Output After Populate</strong></p>
<pre><code class="language-javascript">{
  "members": [
    {
      "_id": "64abc...",
      "name": "Shubham",
      "email": "shubham@gmail.com"
    }
  ]
}
</code></pre>
<hr />
<p><strong>Why Use Populate?</strong></p>
<ul>
<li><p>Converts <strong>ObjectIds → real data</strong></p>
</li>
<li><p>Works similar to <strong>JOIN in SQL</strong></p>
</li>
<li><p>Reduces extra API calls from frontend</p>
</li>
</ul>
<hr />
<p><strong>Request Flow</strong></p>
<pre><code class="language-plaintext">GET /api/households/:id/members
   ↓
auth.middleware.js (verify token)
   ↓
households.controller.js
   ↓
households.service.js
   ↓
MongoDB (findById + populate)
   ↓
Response
</code></pre>
<hr />
<p><strong>Final Result: Testing /api/households/:id/members API Using Postman</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/2b5920cb-5112-4818-873a-a76cae16099b.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>Step 16: Leave Household Feature</h3>
<p>After implementing the ability to join a household, the next important feature is to allow users to <strong>leave a household</strong>.</p>
<hr />
<p><strong>Define Routes</strong></p>
<p>Update:</p>
<pre><code class="language-plaintext">src/modules/households/households.routes.js
</code></pre>
<pre><code class="language-javascript">import { Router } from "express";
import authMiddleware from "../auth/auth.middleware.js";
import * as controller from './households.controller.js';

const router = Router();

router.post('/', authMiddleware, controller.createHouseHold);
router.post('/join', authMiddleware, controller.joinHouseHold);
router.get('/:id/members', authMiddleware, controller.getAllMembers)

// Leave household
router.delete('/:id/leave', authMiddleware, controller.leaveHouseHold);

export default router;
</code></pre>
<hr />
<p><strong>Controller Layer</strong></p>
<p>Update:</p>
<pre><code class="language-plaintext">src/modules/households/households.controller.js
</code></pre>
<pre><code class="language-javascript">const leaveHouseHold = async (req, res, next) =&gt; {
    try {
        const data = await householdService.leaveHouseHold(req);
        ApiResponse.ok(res, "Left Household Successfully", data);
    } catch (err) {
        next(err);
    }
};
</code></pre>
<hr />
<p><strong>Service Layer (Business Logic)</strong></p>
<p>Update:</p>
<pre><code class="language-js">src/modules/households/households.service.js
</code></pre>
<pre><code class="language-js">const leaveHouseHold = async (req) =&gt; {
    const houseHoldId = req.params.id;
    const userId = req.userId;

    const houseHold = await houseHoldModel.findById(houseHoldId);

    if (!houseHold) {
        throw ApiError.notFound("Household not found");
    }

    const isMember = houseHold.members.some(
        (member) =&gt; member.toString() === userId
    );

    if (!isMember) {
        throw ApiError.badRequest("User is not a member of this household");
    }

    houseHold.members = houseHold.members.filter(
        (member) =&gt; member.toString() !== userId
    );

    //If creator/admin leaves
    if (houseHold.createdBy.toString() === userId) {
        if (houseHold.members.length === 0) {
            await houseHold.deleteOne();
            return { message: "Household deleted as no members left" };
        }else{
            houseHold.createdBy = houseHold.members[0];
        }
    }

    await houseHold.save();

    return {
        householdId: houseHold._id,
        members: houseHold.members
    };
}
</code></pre>
<hr />
<p><strong>Request Flow</strong></p>
<pre><code class="language-plaintext">DELETE /api/households/:id/leave
   ↓
auth.middleware.js (verify token)
   ↓
households.controller.js
   ↓
households.service.js
   ↓
MongoDB (find → update → save/delete)
   ↓
Response
</code></pre>
<hr />
<p><strong>Final Result: Testing /api/households/:id/leave API Using Postman</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/09452f0d-30f3-4808-8e11-810fa7e92373.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>Step 17: Add Items Feature (Inventory Management)</h3>
<p>Now that households are implemented, the next step is to allow users to <strong>add grocery items inside a household</strong>.</p>
<p>This is the <strong>core functionality</strong> of the application.</p>
<hr />
<p><strong>Requirement</strong></p>
<p>A logged-in user should be able to:</p>
<ul>
<li><p>Add items with:</p>
<ul>
<li><p><strong>name</strong></p>
</li>
<li><p><strong>category</strong></p>
</li>
<li><p><strong>quantity</strong></p>
</li>
<li><p><strong>expiry date</strong></p>
</li>
</ul>
</li>
<li><p>The system should automatically assign a <strong>status</strong>:</p>
<ul>
<li><p><code>fresh</code></p>
</li>
<li><p><code>expiring-soon</code> (≤ 3 days)</p>
</li>
<li><p><code>expired</code></p>
</li>
</ul>
</li>
</ul>
<hr />
<p><strong>Module Structure</strong></p>
<p>Created a new module:</p>
<pre><code class="language-plaintext">src/modules/items/
├── items.controller.js
├── items.middleware.js
├── items.model.js
├── items.routes.js
├── items.service.js
</code></pre>
<hr />
<p><strong>Item Schema</strong></p>
<p>Create:</p>
<pre><code class="language-plaintext">src/modules/items/items.model.js
</code></pre>
<pre><code class="language-javascript">import mongoose from "mongoose";

const itemsSchema = new mongoose.Schema({
    householdId: {
        type: mongoose.Types.ObjectId,
        ref: "households"
    },
    addedBy: {
        type: mongoose.Types.ObjectId,
        ref: "users"
    },
    name: {
        type: String,
        required: true
    },
    category: {
        type: String,
        enum: ['produce', 'dairy', 'meat', 'pantry', 'frozen']
    },
    quantity: Number,
    expiryDate: Date,
    status: {
        type: String,
        enum: ['fresh', 'expiring-soon', 'expired', 'used', 'wasted']
    }
}, { timestamps: true });

const itemsModel = mongoose.model("items", itemsSchema);

export default itemsModel;
</code></pre>
<hr />
<p><strong>Why Enum?</strong></p>
<p>We use <code>enum</code> to <strong>restrict values</strong>.</p>
<p>Example:</p>
<ul>
<li><p>Category must be one of:</p>
<ul>
<li><code>produce</code>, <code>dairy</code>, <code>meat</code>, <code>pantry</code>, <code>frozen</code></li>
</ul>
</li>
</ul>
<p>If any other value is passed → Mongoose throws a validation error.</p>
<hr />
<p><strong>Household Middleware</strong></p>
<p>Before adding an item, we must ensure:</p>
<p>The user <strong>belongs to a household</strong></p>
<p>Create:</p>
<pre><code class="language-plaintext">src/modules/households/households.middleware.js
</code></pre>
<pre><code class="language-javascript">import ApiError from '../../common/utils/api-error.js';
import houseHoldModel from './households.model.js';

const housholdMiddleware = async (req, res, next) =&gt; {
    try {
        const userId = req.userId;

        const householdExists = await houseHoldModel.findOne({
            members: userId
        });

        if (!householdExists) {
            throw ApiError.notFound("HouseHold Not found");
        }

        req.householdId = householdExists._id.toString();

        next();
    } catch (error) {
        next(error);
    }
};

export default housholdMiddleware;
</code></pre>
<hr />
<p><strong>Why This Middleware?</strong></p>
<ul>
<li><p>Ensures user is part of a household</p>
</li>
<li><p>Prevents unauthorized item creation</p>
</li>
<li><p>Attaches <code>householdId</code> to request</p>
</li>
</ul>
<hr />
<p><strong>Routes</strong></p>
<p>Create:</p>
<pre><code class="language-plaintext">src/modules/items/items.routes.js
</code></pre>
<pre><code class="language-javascript">import { Router } from "express";
import authMiddleware from "../auth/auth.middleware.js";
import housholdMiddleware from "../households/households.middleware.js";
import * as controller from './items.controller.js';

const router = Router();

router.post('/', authMiddleware, housholdMiddleware, controller.createItem);

export default router;
</code></pre>
<hr />
<p><strong>Controller Layer</strong></p>
<p>Create:</p>
<pre><code class="language-plaintext">src/modules/items/items.controller.js
</code></pre>
<pre><code class="language-javascript">import ApiResponse from "../../common/utils/api-response.js";
import * as itemService from './items.service.js';

const createItem = async (req, res, next) =&gt; {
    try {
        const data = await itemService.createItem(req);
        ApiResponse.ok(res, "Item Created Successfully", data);
    } catch (err) {
        next(err);
    }
};

export {
    createItem
};
</code></pre>
<hr />
<p><strong>Responsibility of Controller</strong></p>
<ul>
<li><p>Calls service layer</p>
</li>
<li><p>Sends success response</p>
</li>
<li><p>Passes errors to middleware</p>
</li>
</ul>
<hr />
<p><strong>Service Layer (Business Logic)</strong></p>
<p>Create:</p>
<pre><code class="language-plaintext">src/modules/items/items.service.js
</code></pre>
<pre><code class="language-javascript">import ApiError from "../../common/utils/api-error.js";
import itemsModel from "./items.model.js";

const createItem = async (req) =&gt; {
    const { name, category, quantity, expiryDate } = req.body;
    const userId = req.userId;
    const householdId = req.householdId;

    if (!name || !category || !quantity || !expiryDate) {
        throw ApiError.badRequest("All fields are required");
    }

    const today = new Date();
    const expiry = new Date(expiryDate);

    // Normalize time (important for correct comparison)
    //Removes time part → avoids wrong comparisons
    today.setHours(0, 0, 0, 0);
    expiry.setHours(0, 0, 0, 0);

    const diffTime = expiry - today;
    // 1000 → milliseconds in 1 second
    // 60 → seconds in 1 minute
    // 60 → minutes in 1 hour
    // 24 → hours in 1 day
    //1 day = 86,400,000 milliseconds
    const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));

    let status = "fresh";

    if (diffDays &lt; 0) {
        status = "expired";
    } else if (diffDays &lt;= 3) {
        status = "expiring-soon";
    }

    const newItem = await itemsModel.create({
        householdId,
        addedBy: userId,
        name,
        category,
        quantity,
        expiryDate,
        status
    });

    return {
        _id: newItem._id,
        name: newItem.name,
        status: newItem.status
    };
}

export {
    createItem
}
</code></pre>
<hr />
<p><strong>Request Flow</strong></p>
<pre><code class="language-plaintext">POST /api/items/
   ↓
auth.middleware.js
   ↓
household.middleware.js
   ↓
items.controller.js
   ↓
items.service.js
   ↓
MongoDB
   ↓
Response
</code></pre>
<hr />
<p><strong>Final Result: Testing /api/items/ API Using Postman</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/a2c973e9-b3b0-4cbc-913a-84dd538fa42c.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>Step 18: Automated Status Updates (Using Cron Job)</h3>
<p>Now that item creation is implemented, the next improvement is making item status <strong>dynamic instead of static</strong>.</p>
<p>Because expiry status changes over time, we use a background cron job.</p>
<hr />
<p><strong>Why This is Needed?</strong></p>
<p>When an item is created:</p>
<ul>
<li><p>It may be <strong>fresh today</strong></p>
</li>
<li><p>But before 2 days of expiry date it becomes <strong>expiring-soon</strong></p>
</li>
<li><p>After expiry → <strong>expired</strong></p>
</li>
</ul>
<p>So status must update automatically without user action.</p>
<hr />
<p><strong>Solution :</strong></p>
<p>To solve this, we introduce a <strong>background cron job</strong>.</p>
<p>A cron job runs at a fixed interval and updates item status automatically based on current date.</p>
<p>We used:</p>
<pre><code class="language-plaintext">node-cron
</code></pre>
<hr />
<p><strong>Cron Job Setup (Testing Mode)</strong></p>
<p>Initially, I tested the cron job by running it every minute.</p>
<p>Create:</p>
<pre><code class="language-plaintext">src/modules/items/items.cron.js
</code></pre>
<pre><code class="language-javascript">import cron from "node-cron";

cron.schedule("* * * * *", () =&gt; {
  console.log(
    "Cron job running every minute at",
    new Date().toLocaleTimeString()
  );
});
</code></pre>
<hr />
<p><strong>Understanding the Cron Expression</strong></p>
<pre><code class="language-javascript">"* * * * *"
</code></pre>
<p>This means:</p>
<pre><code class="language-plaintext">minute hour day month weekday
</code></pre>
<p>So the above expression runs:</p>
<ul>
<li>Every minute</li>
</ul>
<p>For production, this can later be changed to:</p>
<pre><code class="language-javascript">0 6 * * *
</code></pre>
<p>Which means:</p>
<ul>
<li>Run every day at 6 AM</li>
</ul>
<p>For more details checkout node-cron documentation: <a href="https://www.nodecron.com/getting-started.html">https://www.nodecron.com/getting-started.html</a></p>
<hr />
<p><strong>Implementing Status Update Logic</strong></p>
<p>Now the next step is to update item statuses automatically.</p>
<p>Inside the service layer, we created updateItemStatuses function to implement the logic:</p>
<ul>
<li><p>Fetch all items from database(item collection)</p>
</li>
<li><p>Compare expiry date with current date</p>
</li>
<li><p>Calculate difference in days</p>
</li>
<li><p>Assign correct status</p>
</li>
</ul>
<p><strong>Rules:</strong></p>
<ul>
<li><p>diffDays &lt; 0 → expired</p>
</li>
<li><p>diffDays &lt;= 3 → expiring-soon</p>
</li>
<li><p>else → fresh</p>
</li>
</ul>
<pre><code class="language-plaintext">src/modules/items/items.service.js
</code></pre>
<pre><code class="language-javascript">const updateItemStatuses = async () =&gt; {
  try {
    const items = await itemsModel.find({});

    const today = new Date();
    today.setHours(0, 0, 0, 0);

    const bulkOperations = [];
    const expiringItems = [];

    for (const item of items) {
      const expiry = new Date(item.expiryDate);

      expiry.setHours(0, 0, 0, 0);

      const diffTime = expiry - today;

      const diffDays = Math.ceil(
        diffTime / (1000 * 60 * 60 * 24)
      );

      let newStatus = "fresh";

      if (diffDays &lt; 0) {
        newStatus = "expired";
      } else if (diffDays &lt;= 3) {
        newStatus = "expiring-soon";

        expiringItems.push(item);
      }

      // Update only if status changed
      if (item.status !== newStatus) {
        bulkOperations.push({
          updateOne: {
            filter: { _id: item._id },
            update: {
              $set: {
                status: newStatus,
              },
            },
          },
        });
      }
    }

    // Bulk update optimization
    if (bulkOperations.length &gt; 0) {
      await itemsModel.bulkWrite(bulkOperations);

      console.log(
        `Updated ${bulkOperations.length} items`
      );
    }

    return expiringItems;
  } catch (error) {
    console.error("Cron job failed:", error);
  }
};
</code></pre>
<hr />
<p><strong>Why Use bulkWrite (mongoDB) ?</strong></p>
<p>Instead of updating items one by one:</p>
<pre><code class="language-plaintext">await item.save();
</code></pre>
<p>We use:</p>
<pre><code class="language-plaintext">itemsModel.bulkWrite(bulkOperations);
</code></pre>
<p>Benefits:</p>
<ul>
<li><p>Fewer database calls</p>
</li>
<li><p>Better performance</p>
</li>
<li><p>More scalable approach</p>
</li>
</ul>
<p>Check out mongodb documentation on <strong>bulkWrite</strong>: <a href="https://www.mongodb.com/docs/manual/reference/method/db.collection.bulkWrite/">https://www.mongodb.com/docs/manual/reference/method/db.collection.bulkWrite/</a> )</p>
<hr />
<p><strong>Collecting Expiring Items</strong></p>
<p>While updating statuses, we also track items that are close to expiry.</p>
<p>These items are stored separately:</p>
<pre><code class="language-javascript">expiringItems.push(item);
</code></pre>
<p>This allows us to later notify users about upcoming expirations.</p>
<hr />
<p><strong>Connecting Cron with Service</strong></p>
<p>Now we connect the cron scheduler with the status update logic.</p>
<p>Update:</p>
<pre><code class="language-plaintext">src/modules/items/items.cron.js
</code></pre>
<pre><code class="language-javascript">import cron from "node-cron";
import { updateItemStatuses } from "./items.service.js";

cron.schedule("* * * * *", async () =&gt; {
  console.log("Cron job started...");

  await updateItemStatuses();
});
</code></pre>
<hr />
<p><strong>Final Cron Flow</strong></p>
<pre><code class="language-javascript">Cron Trigger
   ↓
Fetch all items
   ↓
Calculate expiry difference
   ↓
Update statuses
   ↓
Bulk update MongoDB
   ↓
Collect expiring items
</code></pre>
<hr />
<h3>Step 19: Email Notification System for Expiring Items</h3>
<p>Now that item statuses update automatically using a cron job, the next step is to notify household members when items are about to expire.</p>
<hr />
<p><strong>Problem</strong></p>
<p>Even if the database status updates correctly:</p>
<ul>
<li><p>Users may never open the app regularly</p>
</li>
<li><p>Expiring items can still go unnoticed</p>
</li>
<li><p>Food waste can still happen</p>
</li>
</ul>
<p>So we need a notification mechanism.</p>
<hr />
<p><strong>Solution</strong></p>
<p>Whenever the cron job finds items that are:</p>
<pre><code class="language-plaintext">expiring-soon
</code></pre>
<p>The system automatically sends email notifications to all household members.</p>
<p>This ensures everyone in the household is informed in advance.</p>
<hr />
<p><strong>Setting Up Email Service (Nodemailer)</strong></p>
<p>To send emails, I used:</p>
<pre><code class="language-plaintext">nodemailer
</code></pre>
<p>Checkout <strong>nodemailer</strong> documentation: <a href="https://nodemailer.com/">https://nodemailer.com/</a></p>
<hr />
<p><strong>Email Configuration</strong></p>
<p>Create:</p>
<pre><code class="language-plaintext">src/common/config/email.js
</code></pre>
<pre><code class="language-javascript">import nodemailer from "nodemailer";

const transporter = nodemailer.createTransport({
  host: "smtp.gmail.com",

  // SMTP port for secure connection
  port: 465,

  secure: true,

  auth: {
    user: process.env.SMTP_USER,
    pass: process.env.SMTP_PASS,
  },
});

const sendMail = async (to, subject, html) =&gt; {
  await transporter.sendMail({
    from: process.env.SMTP_FROM_EMAIL,
    to,
    subject,
    html,
  });
};

export { sendMail };
</code></pre>
<hr />
<p><strong>Environment Variables</strong></p>
<p>Update .env</p>
<pre><code class="language-plaintext">SMTP_USER=your_email@gmail.com
SMTP_PASS=your_app_password
SMTP_FROM_EMAIL=your_email@gmail.com
</code></pre>
<hr />
<p><strong>Why App Password?</strong></p>
<p>For Gmail SMTP:</p>
<ul>
<li><p>Normal Gmail password does not work</p>
</li>
<li><p>Google requires App Passwords for secure SMTP access</p>
</li>
</ul>
<hr />
<p><strong>What is App Password?</strong></p>
<p>It is a special 16-digit password generated by Google that allows apps like Nodemailer to send emails safely.</p>
<hr />
<p><strong>How to get App password?</strong></p>
<p><strong>1. Enable 2-Step Verification</strong></p>
<p>Go to: <a href="https://myaccount.google.com/security">https://myaccount.google.com/security</a></p>
<p>Turn ON:</p>
<ul>
<li><strong>2-Step Verification</strong></li>
</ul>
<p><strong>2. Generate App Password</strong></p>
<p>After enabling 2FA:</p>
<p>Go to: <a href="https://myaccount.google.com/apppasswords">https://myaccount.google.com/apppasswords</a></p>
<p>You will get something like:</p>
<pre><code class="language-plaintext">abcd efgh ijkl mnop
</code></pre>
<p><strong>3. Use it in Nodemailer</strong></p>
<pre><code class="language-plaintext">auth: {
  user: process.env.SMTP_USER, // your gmail address
  pass: process.env.SMTP_PASS, // app password (NOT normal password)
}
</code></pre>
<hr />
<p><strong>Notification Flow</strong></p>
<p>The notification system works like this:</p>
<pre><code class="language-javascript">Cron Job
   ↓
Update item statuses
   ↓
Collect expiring items
   ↓
Group items by household
   ↓
Fetch household members
   ↓
Send emails
</code></pre>
<hr />
<p><strong>Grouping Expiring Items by Household</strong></p>
<p>Inside the cron job schedular code:</p>
<pre><code class="language-javascript">const grouped = {};

for (const item of expiringItems) {
  const key = item.householdId.toString();

  if (!grouped[key]) {
    grouped[key] = [];
  }

  grouped[key].push(item);
}
</code></pre>
<p><strong>Why Group by Household?</strong></p>
<p>Because:</p>
<ul>
<li><p>Multiple households exist</p>
</li>
<li><p>Each household has different members</p>
</li>
<li><p>Notifications should only go to relevant users</p>
</li>
</ul>
<hr />
<p><strong>Fetching Household Members</strong></p>
<p>Now we fetch all members of a household.</p>
<pre><code class="language-javascript">const household = await houseHoldModel
  .findById(householdId)
  .populate("members", "email");
</code></pre>
<p>Using <code>populate()</code> gives access to member emails directly.</p>
<hr />
<p><strong>Generating Email Content</strong></p>
<p>Next, we generate<br />the email body dynamically.</p>
<pre><code class="language-javascript">const itemsList = grouped[householdId]
  .map(
    (i) =&gt;
      `&lt;li&gt;
        ${i.name} - expires on 
        ${new Date(i.expiryDate).toDateString()}
      &lt;/li&gt;`
  )
  .join("");
</code></pre>
<hr />
<p><strong>Final Email Template</strong></p>
<pre><code class="language-html">const html = `
  &lt;h2&gt;Expiring Items Alert&lt;/h2&gt;

  &lt;p&gt; The following items will expire soon: &lt;/p&gt;

  &lt;ul&gt; ${itemsList} &lt;/ul&gt;
`;
</code></pre>
<hr />
<p><strong>Sending Emails</strong></p>
<p>Finally, emails are sent to all household members.</p>
<pre><code class="language-javascript">for (const email of emails) {
  await sendMail(
    email,
    "Items Expiring Soon",
    html
  );
</code></pre>
<hr />
<p><strong>Final Cron Implementation</strong></p>
<p>Update:</p>
<pre><code class="language-plaintext">src/modules/items/items.cron.js
</code></pre>
<pre><code class="language-javascript">import cron from "node-cron";

import { updateItemStatuses } from "./items.service.js";

import houseHoldModel from "../households/households.model.js";

import { sendMail } from "../../common/config/email.js";

cron.schedule("* * * * *", async () =&gt; {
  try {
    console.log("cronJob started...");

    const expiringItems =
      await updateItemStatuses();

    if (!expiringItems.length) {
      console.log("No expiring items");

      return;
    }

    // Group items by household
    const grouped = {};

    for (const item of expiringItems) {
      const key = item.householdId.toString();

      if (!grouped[key]) {
        grouped[key] = [];
      }

      grouped[key].push(item);
    }

    // Send notifications
    for (const householdId of Object.keys(grouped)) {
      const household = await houseHoldModel
        .findById(householdId)
        .populate("members", "email");

      const emails = household.members.map(
        (u) =&gt; u.email
      );

      const itemsList = grouped[householdId]
        .map(
          (i) =&gt;
            `&lt;li&gt;
              ${i.name} - expires on 
              ${new Date(i.expiryDate).toDateString()}
            &lt;/li&gt;`
        )
        .join("");

      const html = `
        &lt;h2&gt;Expiring Items Alert&lt;/h2&gt;

        &lt;p&gt;
          The following items will expire soon:
        &lt;/p&gt;

        &lt;ul&gt;
          ${itemsList}&lt;/ul&gt;
      `;

      for (const email of emails) {
        await sendMail(
          email,
          "Items Expiring Soon",
          html
        );
      }
    }
  } catch (err) {
    console.error("Cron failed:", err);
  }
});
</code></pre>
<hr />
<h3>Outcome</h3>
<p>With this implementation:</p>
<ul>
<li><p>Household members receive automated expiry alerts</p>
</li>
<li><p>Expiring items are tracked proactively</p>
</li>
<li><p>Users can consume items before they expire</p>
</li>
<li><p>Food wastage is reduced significantly</p>
</li>
</ul>
<hr />
<h3>Step 20: Get All Items &amp; Update Item APIs</h3>
<p>After implementing item creation and automated expiry tracking, the next step was building inventory management APIs for the frontend dashboard.</p>
<p>These APIs allow users to:</p>
<ul>
<li><p>View all household inventory items</p>
</li>
<li><p>Edit existing items directly from the inventory table</p>
</li>
</ul>
<p>This became important after integrating the inventory dashboard using Material React Table.</p>
<p><strong>Why These APIs Were Needed</strong></p>
<p>The frontend inventory system required:</p>
<ul>
<li><p>Fetching all items belonging to the logged-in user’s household</p>
</li>
<li><p>Displaying items inside a data table</p>
</li>
<li><p>Supporting inline editing</p>
</li>
<li><p>Updating items without refreshing the entire page manually</p>
</li>
</ul>
<p>This enabled a much smoother inventory management experience.</p>
<hr />
<p><strong>Get All Items API</strong></p>
<p><strong>Requirements</strong></p>
<p>A logged-in user should be able to:</p>
<ul>
<li><p>Fetch all items from their household</p>
</li>
<li><p>View:</p>
<ul>
<li><p>name</p>
</li>
<li><p>category</p>
</li>
<li><p>quantity</p>
</li>
<li><p>expiry date</p>
</li>
<li><p>status</p>
</li>
</ul>
</li>
</ul>
<p>Only authenticated household members should access these items.</p>
<hr />
<p><strong>Define Routes</strong></p>
<p>Update:</p>
<p><code>src/modules/items/items.routes.js</code></p>
<pre><code class="language-javascript">import { Router } from "express";

import authMiddleware from "../auth/auth.middleware.js";

import housholdMiddleware from "../households/households.middleware.js";

import * as controller from "./items.controller.js";

const router = Router();

router.post(
  "/",
  authMiddleware,
  housholdMiddleware,
  controller.createItem
);

router.get(
  "/",
  authMiddleware,
  housholdMiddleware,
  controller.getItems
);

router.put(
  "/:id",
  authMiddleware,
  housholdMiddleware,
  controller.updateItem
);

export default router;
</code></pre>
<hr />
<p><strong>Controller Layer</strong></p>
<p>Update:</p>
<p><code>src/modules/items/items.controller.js</code></p>
<pre><code class="language-javascript">import ApiResponse from "../../common/utils/api-response.js";

import * as itemService from "./items.service.js";

const getItems = async (req, res, next) =&gt; {
  try {
    const data = await itemService.getItems(req);

    ApiResponse.ok(
      res,
      "Items fetched successfully",
      data
    );
  } catch (error) {
    next(error);
  }
};

const updateItem = async (req, res, next) =&gt; {
  try {
    const data = await itemService.updateItem(req);

    ApiResponse.ok(
      res,
      "Item updated successfully",
      data
    );
  } catch (error) {
    next(error);
  }
};

export {
  getItems,
  updateItem,
};
</code></pre>
<hr />
<p><strong>Get All Items Service</strong></p>
<p>Create / Update:</p>
<p><code>src/modules/items/items.service.js</code></p>
<pre><code class="language-plaintext">const getItems = async (req) =&gt; {
  const householdId = req.householdId;

  const page = Number(req.query.page) || 1;
  const limit = Number(req.query.limit) || 10;
  const search = req.query.search || "";

  const skip = (page - 1) * limit;

  const query = {
    householdId,
  };

  if (search) {
    query.$or = [
      {
        name: {
          $regex: search,
          $options: "i",
        },
      },
      {
        category: {
          $regex: search,
          $options: "i",
        },
      },
    ];
  }

  const items = await itemsModel
    .find(query)
    .populate("addedBy", "name email")
    .sort({ createdAt: -1 })
    .skip(skip)
    .limit(limit);

  const totalItems = await itemsModel.countDocuments({
    householdId,
  });

  return {
    items,
    totalItems,
  };
};
</code></pre>
<hr />
<p><strong>Why Filter Using householdId?</strong></p>
<p>This ensures:</p>
<ul>
<li><p>Users only see items belonging to their household</p>
</li>
<li><p>Data remains isolated between households</p>
</li>
<li><p>Unauthorized inventory access is prevented</p>
</li>
</ul>
<p>Without this filtering, users could potentially access items from other households.</p>
<hr />
<p><strong>Update Item Service</strong></p>
<p>Now users can edit inventory items directly from the dashboard table.</p>
<p>Update:</p>
<p><code>src/modules/items/items.service.js</code></p>
<pre><code class="language-plaintext">const updateItem = async (req) =&gt; {
  const itemId = req.params.id;

  const {
    name,
    category,
    quantity,
    expiryDate,
  } = req.body;

  const item = await itemsModel.findById(itemId);

  if (!item) {
    throw ApiError.notFound("Item not found");
  }

  item.name = name;
  item.category = category;
  item.quantity = quantity;
  item.expiryDate = expiryDate;

  const today = new Date();

  const expiry = new Date(expiryDate);

  today.setHours(0, 0, 0, 0);

  expiry.setHours(0, 0, 0, 0);

  const diffDays = Math.ceil(
    (expiry - today) /
      (1000 * 60 * 60 * 24)
  );

  let status = "fresh";

  if (diffDays &lt; 0) {
    status = "expired";
  } else if (diffDays &lt;= 3) {
    status = "expiring-soon";
  }

  item.status = status;

  await item.save();

  return item;
};
</code></pre>
<hr />
<p><strong>Why Recalculate Status During Update?</strong></p>
<p>Suppose a user edits:</p>
<ul>
<li><p>expiry date</p>
</li>
<li><p>quantity</p>
</li>
<li><p>item details</p>
</li>
</ul>
<p>Then the status may also change.</p>
<p>Example:</p>
<ul>
<li><p>Fresh → Expiring Soon</p>
</li>
<li><p>Expiring Soon → Expired</p>
</li>
</ul>
<p>So status recalculation ensures item state always stays accurate.</p>
<hr />
<h3>Step 21: Delete Item API</h3>
<p>After implementing the Get All Items and Update Item APIs, the next important feature was allowing users to remove inventory items directly from the dashboard.</p>
<p>This became necessary after integrating row actions inside the Material React Table inventory system.</p>
<p>The delete functionality allows users to:</p>
<ul>
<li><p>Remove expired items</p>
</li>
<li><p>Delete mistakenly added inventory</p>
</li>
<li><p>Keep household inventory clean and updated</p>
</li>
</ul>
<hr />
<p><strong>Why Delete API Was Needed</strong></p>
<p>Inside the inventory dashboard, each row already had:</p>
<ul>
<li><p>Edit action</p>
</li>
<li><p>Status display</p>
</li>
<li><p>Inline editing support</p>
</li>
</ul>
<p>The next logical feature was:</p>
<pre><code class="language-plaintext">Delete Item
    ↓
Remove item from database
    ↓
Refresh inventory table
    ↓
Show updated inventory list
</code></pre>
<p>This created a much better inventory management experience for users.</p>
<hr />
<p><strong>Defining Delete Route</strong></p>
<p>Update:</p>
<pre><code class="language-plaintext">src/modules/items/items.routes.js
</code></pre>
<pre><code class="language-plaintext">import { Router } from "express";

import authMiddleware from "../auth/auth.middleware.js";

import housholdMiddleware from "../households/households.middleware.js";

import * as controller from "./items.controller.js";

const router = Router();

router.post(
  "/",
  authMiddleware,
  housholdMiddleware,
  controller.createItem
);

router.get(
  "/",
  authMiddleware,
  housholdMiddleware,
  controller.getItems
);

router.put(
  "/:id",
  authMiddleware,
  housholdMiddleware,
  controller.updateItem
);

router.delete(
  "/:itemId",
  authMiddleware,
  housholdMiddleware,
  controller.deleteItem
);

export default router;
</code></pre>
<hr />
<p><strong>Controller Layer</strong></p>
<p>Update:</p>
<pre><code class="language-plaintext">src/modules/items/items.controller.js
</code></pre>
<pre><code class="language-plaintext">import ApiResponse from "../../common/utils/api-response.js";

import * as itemService from "./items.service.js";

const deleteItem = async (req, res, next) =&gt; {
  try {
    const data = await itemService.deleteItem(req);

    ApiResponse.ok(
      res,
      "Item Deleted Successfully",
      data
    );
  } catch (err) {
    next(err);
  }
};

export {
  getItems,
  updateItem,
  deleteItem,
};
</code></pre>
<hr />
<p><strong>Delete Item Service</strong></p>
<p>Update:</p>
<pre><code class="language-plaintext">src/modules/items/items.service.js
</code></pre>
<pre><code class="language-plaintext">const deleteItem = async (req) =&gt; {
  const { itemId } = req.params;

  const householdId = req.householdId;

  const item = await itemsModel.findOne({
    _id: itemId,
    householdId,
  });

  if (!item) {
    throw ApiError.notFound("Item not found");
  }

  await itemsModel.deleteOne({
    _id: itemId,
    householdId,
  });

  return {
    itemId,
  };
};
</code></pre>
<hr />
]]></content:encoded></item><item><title><![CDATA[Sliding Window]]></title><description><![CDATA[Problem
You are given an array of size N and an integer K.
Your task:Find the maximum sum of any subarray of size exactly K

Understanding the Problem
Let’s take an example:
arr = [2, 1, 5, 1, 3, 2]
k]]></description><link>https://blog.realdev.club/sliding-window</link><guid isPermaLink="true">https://blog.realdev.club/sliding-window</guid><category><![CDATA[sliding window]]></category><category><![CDATA[DSA]]></category><category><![CDATA[data structures]]></category><category><![CDATA[learntocode]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Problem Solving]]></category><dc:creator><![CDATA[Shubham Kumar Singh]]></dc:creator><pubDate>Tue, 28 Apr 2026 12:59:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/59135b1d-22e3-4288-87c5-59ac692ecf06.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3>Problem</h3>
<p>You are given an array of size <code>N</code> and an integer <code>K</code>.</p>
<p>Your task:<br />Find the <strong>maximum sum of any subarray of size exactly K</strong></p>
<hr />
<h3>Understanding the Problem</h3>
<p>Let’s take an example:</p>
<pre><code class="language-plaintext">arr = [2, 1, 5, 1, 3, 2]
k = 3
</code></pre>
<p>Possible subarrays of size 3:</p>
<pre><code class="language-plaintext">[2, 1, 5] → sum = 8
[1, 5, 1] → sum = 7
[5, 1, 3] → sum = 9
[1, 3, 2] → sum = 6
</code></pre>
<p>Answer = <strong>9</strong></p>
<hr />
<h3>Brute Force Thinking</h3>
<p>A straightforward way:</p>
<ul>
<li><p>Pick every subarray of size <code>k</code></p>
</li>
<li><p>Calculate sum again and again</p>
</li>
</ul>
<p>Time Complexity = <strong>O(n * k)</strong></p>
<hr />
<h3>Key Observation</h3>
<p>Look at how the window moves:</p>
<pre><code class="language-plaintext">arr = [2, 1, 5, 1, 3, 2]

[2, 1, 5, 1, 3, 2]
 0  1  2  3  4  5 
 ↑     ↑

[2, 1, 5] → sum = 8  

[2, 1, 5, 1, 3, 2]
 0  1  2  3  4  5 
    ↑     ↑

[1, 5, 1] → sum = 8+1-2 = 7
</code></pre>
<p>Instead of recalculating everything:</p>
<ul>
<li><p>Remove <code>2</code></p>
</li>
<li><p>Add <code>1</code></p>
</li>
</ul>
<hr />
<h3>Sliding Window Idea</h3>
<blockquote>
<p>Don’t recompute the whole sum.<br />Just update it while moving forward.</p>
</blockquote>
<hr />
<h3><strong>Step-by-Step Dry Run</strong></h3>
<pre><code class="language-plaintext">arr = [2, 1, 5, 1, 3, 2]
       0  1  2  3  4  5
        
window (size = 3)
</code></pre>
<p><strong>Step 1</strong></p>
<pre><code class="language-plaintext">[2, 1, 5, 1, 3, 2]
 0  1  2  3  4  5 
 ↑     ↑
</code></pre>
<p>Window size = 3<br />Window elements: <code>[2, 1, 5]</code><br />Sum = <code>8</code></p>
<p><strong>Step 2</strong></p>
<pre><code class="language-plaintext">[2, 1, 5, 1, 3, 2]
 0  1  2  3  4  5 
    ↑     ↑
</code></pre>
<p>New window element : <code>[1, 5, 1]</code></p>
<p>Add <code>arr[3] = 1</code>, Remove <code>arr[0] = 2</code></p>
<p>Sum = <code>8 + 1 - 2 = 7</code></p>
<p><strong>Step 3</strong></p>
<pre><code class="language-plaintext">[2, 1, 5, 1, 3, 2]
 0  1  2  3  4  5 
       ↑     ↑
</code></pre>
<p>New window element: <code>[5, 1, 3]</code></p>
<p>Add <code>arr[4] = 3</code>, Remove <code>arr[1] = 1</code></p>
<p>Sum = <code>7 + 3 - 1 = 9</code></p>
<p><strong>Step 4</strong></p>
<pre><code class="language-plaintext">[2, 1, 5, 1, 3, 2]
 0  1  2  3  4  5 
          ↑     ↑
</code></pre>
<p>New window element: <code>[1, 3, 2]</code></p>
<p>Add <code>arr[5]=2</code>, Remove <code>arr[2]= 5</code></p>
<p>Sum = <code>9 + 2 - 5 = 6</code></p>
<hr />
<h3>Key Insight</h3>
<p>At every step:</p>
<ul>
<li><p>Add the next element</p>
</li>
<li><p>Remove the previous element</p>
</li>
</ul>
<p>Constant work per step → <strong>O(n)</strong></p>
<hr />
<h3>Code</h3>
<pre><code class="language-javascript">sum = 0

// Step 1: Calculate sum of first window
for i from 0 to k-1:
    sum = sum + arr[i]

maximum = sum

// Step 2: Slide the window
for i from k to n-1:
    sum = sum + arr[i]        // add next element
    sum = sum - arr[i - k]    // remove previous element

    maximum = max(maximum, sum)

// Final answer
return maximum
</code></pre>
<hr />
<h3>Complexity</h3>
<ul>
<li><p><strong>Time Complexity:</strong> <code>O(n)</code></p>
</li>
<li><p><strong>Space Complexity:</strong> <code>O(1)</code></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Aggressive Cows Problem using Binary Search]]></title><description><![CDATA[Problem



Core Idea
You are not directly placing cows.
Instead, you are guessing the answer (minimum distance) and checking:
“Is it possible to place all cows with at least this distance?”

Goal
Find]]></description><link>https://blog.realdev.club/aggressive-cows-problem-using-binary-search</link><guid isPermaLink="true">https://blog.realdev.club/aggressive-cows-problem-using-binary-search</guid><category><![CDATA[Binary Search Algorithm]]></category><category><![CDATA[DSA]]></category><category><![CDATA[data structures]]></category><category><![CDATA[aggressive cows problem]]></category><dc:creator><![CDATA[Shubham Kumar Singh]]></dc:creator><pubDate>Tue, 28 Apr 2026 00:21:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/c48ca6d8-c83c-4110-b25b-730c2e772e03.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Problem</h2>
<img src="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/a3ae3566-f774-4693-91f3-c844b0543faf.png" alt="" />

<hr />
<h2>Core Idea</h2>
<p>You are not directly placing cows.</p>
<p>Instead, you are <strong>guessing the answer (minimum distance)</strong> and checking:</p>
<p><em>“Is it possible to place all cows with at least this distance?”</em></p>
<hr />
<h2>Goal</h2>
<p>Find the <strong>maximum possible minimum distance</strong> between any two cows.</p>
<hr />
<h2>Why Binary Search?</h2>
<ul>
<li><p>Minimum distance <code>(L)</code> = <code>0</code></p>
</li>
<li><p>Maximum distance <code>(R)</code> = <code>max(stall) - min(stall)</code></p>
</li>
</ul>
<p>So the answer lies in a <strong>range</strong> → perfect for binary search.</p>
<hr />
<h2>Example</h2>
<pre><code class="language-plaintext">Stalls = [1, 2, 4, 8, 9]
Cows = 3
</code></pre>
<hr />
<h3>Step 1: Sort</h3>
<pre><code class="language-plaintext">[1, 2, 4, 8, 9]
</code></pre>
<h3>Step 2: Apply Binary Search on distance</h3>
<p>Range:</p>
<pre><code class="language-plaintext">l = 0
r = 9 - 1 = 8
</code></pre>
<p>Search space:</p>
<pre><code class="language-plaintext">0 1 2 3 4 5 6 7 8
l               r
</code></pre>
<h3>Step 3: Try Mid Values</h3>
<p><strong>mid =</strong> <code>(L + R)/2</code> <strong>=</strong><code>(0+8)/2 = 4</code></p>
<pre><code class="language-plaintext">0 1 2 3 4 5 6 7 8
l       m       r
</code></pre>
<p>Try placing cows with minimum distance = 4</p>
<pre><code class="language-plaintext">Stalls:  1   2   4   8   9
</code></pre>
<ul>
<li><p>Place 1st cow at <code>1</code></p>
</li>
<li><p><code>2-1 ≥ 4</code> → Distance Smaller than minimum distance 4. So, cannot place cow</p>
</li>
<li><p><code>4-1 ≥ 4</code> → Distance Smaller than minimum distance 4. So, cannot place cow</p>
</li>
<li><p><code>8-1 ≥ 4</code> → place 2nd cow at <code>8</code></p>
</li>
<li><p><code>9-8 ≥ 4</code> → Distance Smaller than minimum distance 4. So, cannot place cow</p>
</li>
</ul>
<p><strong>2 cows placed</strong></p>
<p>Since we cannot place all 3 cows with a minimum distance of 4, it is impossible to place them with any larger distance like 5, 6, 7, or 8.</p>
<p>So:</p>
<pre><code class="language-plaintext">r = mid - 1 = 3
</code></pre>
<hr />
<p><strong>mid =</strong> <code>(L + R)/2</code> <strong>=</strong><code>(0+3)/2 = 2</code></p>
<pre><code class="language-plaintext">0 1 2 3 4 5 6 7 8
l   m r           
</code></pre>
<p>Try placing cows with minimum distance = 2</p>
<pre><code class="language-plaintext">Stalls:  1   2   4   8   9
</code></pre>
<ul>
<li><p>Place 1st cow at <code>1</code></p>
</li>
<li><p><code>2-1 ≥ 2</code> → Distance Smaller than minimum distance 2. So, cannot place cow</p>
</li>
<li><p><code>4-1 ≥ 2</code> place 2nd cow at <code>4</code></p>
</li>
<li><p><code>8-4 ≥ 2</code> → place 2nd cow at <code>8</code></p>
</li>
</ul>
<p><strong>3 cows placed</strong></p>
<p>Since all 3 cows can be placed with a minimum distance of 2, we try to maximize the distance further by checking for a larger value.</p>
<p>So:</p>
<pre><code class="language-plaintext">ans = 2
l = mid + 1 = 3
</code></pre>
<hr />
<p><strong>mid =</strong> <code>(L + R)/2</code> <strong>=</strong> <code>(3 + 3)/2 = 3</code></p>
<pre><code class="language-plaintext">0 1 2 3 4 5 6 7 8
      l
      m
      r                  
</code></pre>
<p>Try placing cows with minimum distance = 3</p>
<pre><code class="language-plaintext">Stalls:  1   2   4   8   9
</code></pre>
<ul>
<li><p>Place 1st cow at <code>1</code></p>
</li>
<li><p><code>2-1 ≥ 3</code> Distance Smaller than minimum distance 3. So cannot place cow</p>
</li>
<li><p><code>4-1 ≥ 3</code>→ place 2nd cow at <code>4</code></p>
</li>
<li><p><code>8-4 ≥ 3</code> → place 2nd cow at <code>8</code></p>
</li>
</ul>
<p><strong>3 cows placed</strong></p>
<p>So:</p>
<pre><code class="language-plaintext">ans = 3
l = mid + 1 = 4
r = 3
</code></pre>
<p><strong>Loop break</strong></p>
<p><strong>Final Answer: 3</strong></p>
<p><strong>So,</strong> The minimum distance between cows in this case is 3, which is the largest among all possible ways.</p>
<hr />
<h3>Code :</h3>
<pre><code class="language-js">#include &lt;bits/stdc++.h&gt;
using namespace std;

#define int long long

bool possible(int arr[], int mid, int n, int cow){
    int cowCount = 1;
    int prev = arr[0];

    for(int i = 1; i &lt; n; i++){
        if(arr[i] - prev &gt;= mid){
            cowCount++;
            prev = arr[i];
        }
    }

    return cowCount &gt;= cow;
}

signed main() {
    int stall, cow;
    cin &gt;&gt; stall &gt;&gt; cow;

    int arr[stall];
    for(int i = 0; i &lt; stall; i++){
        cin &gt;&gt; arr[i];
    }

    // Step 1: Sort stalls
    sort(arr, arr + stall);

    int l = 0;
    int r = arr[stall - 1] - arr[0];
    int ans = -1;

    // Step 2: Binary Search
    while(l &lt;= r){
        int mid = (l + r) / 2;

        if(possible(arr, mid, stall, cow)){
            ans = mid;       // store answer
            l = mid + 1;     // try for bigger distance
        } else {
            r = mid - 1;     // try smaller distance
        }
    }

    cout &lt;&lt; ans &lt;&lt; endl;
}
</code></pre>
<hr />
<h3>Complexity:</h3>
<ul>
<li><p><strong>Time Complexity: O(n log n + n log(max distance))</strong></p>
</li>
<li><p><strong>Space Complexity: O(1)</strong></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Building a Todo App with Express + PostgreSQL 
]]></title><description><![CDATA[What We’re Building
Before diving into the code, let’s understand what we are going to build.
In this project, we’ll create a secure Todo Backend API using Express.js and PostgreSQL (via Neon).
Core F]]></description><link>https://blog.realdev.club/building-a-todo-app-with-express-postgresql</link><guid isPermaLink="true">https://blog.realdev.club/building-a-todo-app-with-express-postgresql</guid><category><![CDATA[backend]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[Express]]></category><category><![CDATA[PostgreSQL]]></category><category><![CDATA[neondatabase]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[SQL]]></category><dc:creator><![CDATA[Shubham Kumar Singh]]></dc:creator><pubDate>Thu, 23 Apr 2026 19:05:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/f40ebf74-653d-47fd-8db8-194360abda2c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3>What We’re Building</h3>
<p>Before diving into the code, let’s understand what we are going to build.</p>
<p>In this project, we’ll create a <strong>secure Todo Backend API</strong> using <strong>Express.js</strong> and PostgreSQL (via Neon).</p>
<h3>Core Features</h3>
<ul>
<li><p>User Signup (with password hashing)</p>
</li>
<li><p>User Signin (with JWT authentication)</p>
</li>
<li><p>Secure APIs using Middleware</p>
</li>
<li><p>Create Todo (user-specific)</p>
</li>
<li><p>Get Todos (only your own todos)</p>
</li>
<li><p>Prevent SQL Injection attacks</p>
</li>
</ul>
<hr />
<h3>Before We Start…</h3>
<p>If you’re completely new to backend development, I’ve already built a similar Todo app using MongoDB:</p>
<p><a href="https://shubhamsinghbundela.hashnode.dev/building-a-todo-app-using-express-js-mongodb">Building a Todo App using Express.js + MongoDB</a></p>
<p>That version uses a <strong>NoSQL database</strong>, while in this blog we’ll use PostgreSQL (SQL).</p>
<p>This will help you understand:</p>
<ul>
<li><p>Difference between SQL vs NoSQL</p>
</li>
<li><p>When to use which database</p>
</li>
</ul>
<hr />
<h3>Step 1: <strong>Initialize Project</strong></h3>
<pre><code class="language-javascript">npm init -y
</code></pre>
<p>It Creates <code>package.json</code></p>
<hr />
<h3>Step 2: Installing Dependencies</h3>
<p>Since I’m building the backend with Express and PostgreSQL, I installed:</p>
<pre><code class="language-javascript">npm install express 
npm install pg
</code></pre>
<ul>
<li><p><strong>express</strong> → For creating the server and APIs</p>
</li>
<li><p><strong>pg</strong> → PostgreSQL client for Node.js</p>
</li>
</ul>
<hr />
<h3><strong>Step 3: Setup Express Server</strong></h3>
<p>First, I created a basic Express server:</p>
<p>Create file <code>index.js</code></p>
<pre><code class="language-javascript">const express = require("express");
const app = express();

app.use(express.json());

app.listen(3000, () =&gt; {
  console.log("Server is running on port 3000");
});
</code></pre>
<hr />
<h3>Step 4: Setting Up PostgreSQL (NeonDB)</h3>
<p><strong>Steps I followed:</strong></p>
<ol>
<li><p>Signed up on Neon</p>
</li>
<li><p>Created a new project</p>
</li>
<li><p>Got the connection string on Dashboard</p>
</li>
</ol>
<hr />
<h3>Step 4: Connecting Express to PostgreSQL</h3>
<p>Now comes the important part — connecting the database to our backend.</p>
<p>I used <code>Pool</code> from the <strong>pg</strong> library:</p>
<p>Let's created a separate file: <code>model.js</code></p>
<pre><code class="language-javascript">const { Pool } = require('pg');

const pool = new Pool({
  connectionString: "postgresql://neondb_owner:YOUR_PASSWORD@YOUR_HOST/neondb?sslmode=require&amp;channel_binding=require"
});

module.exports = {
    pool: pool
}
</code></pre>
<p>This connection string is provided by Neon after creating the project.</p>
<hr />
<h3>Step 5: Designing the Database</h3>
<p>For this app, I created two tables using the SQL Editor in Neon:</p>
<ul>
<li><p><code>users</code> → Stores user information</p>
</li>
<li><p><code>todo</code> → Stores tasks created by users</p>
</li>
</ul>
<p><strong>Users Table</strong></p>
<pre><code class="language-javascript">CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(255) NOT NULL,
    password VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
</code></pre>
<p><strong>Todo Table</strong></p>
<pre><code class="language-javascript">CREATE TABLE todo (
    id SERIAL PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    user_id INTEGER NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
</code></pre>
<p><strong>Understanding the Relationship</strong></p>
<p>This is where things get interesting.</p>
<ul>
<li><p>Each <strong>todo belongs to a user i.e.</strong> You cannot create a todo without a valid user</p>
</li>
<li><p>We use <code>user_id</code> as a <strong>foreign key</strong> i.e. we use<code>user_id</code> in <code>todo</code> refers to <code>id</code> in <code>users</code></p>
</li>
</ul>
<p><strong>What does ON DELETE CASCADE mean?</strong></p>
<ul>
<li>If a user is deleted → all their todos are automatically deleted from todo table</li>
</ul>
<hr />
<h3>Why SQL is Strictly Typed</h3>
<p>One important thing to notice while designing the database is that PostgreSQL (used via Neon) is a <strong>strictly typed database</strong>.</p>
<p>This means we must define the type of each column in advance.</p>
<p>For example:</p>
<ul>
<li><p><code>username</code> → <code>VARCHAR(255)</code> (must be text)</p>
</li>
<li><p><code>user_id</code> → <code>INTEGER</code> (must be a number)</p>
</li>
<li><p><code>created_at</code> → <code>TIMESTAMP</code> (must be a valid date-time)</p>
</li>
</ul>
<p>Because of this:</p>
<p>You <strong>cannot insert random data types</strong> into the database.</p>
<hr />
<h3><strong>Step 6: Signup Route</strong></h3>
<p>Before writing code, let’s understand the goal.</p>
<p>What we want to <strong>achieve:</strong></p>
<ul>
<li><p>Allow a new user to <strong>register</strong></p>
</li>
<li><p>Store user details in database</p>
</li>
<li><p>Prevent duplicate users</p>
</li>
<li><p>Secure the password</p>
</li>
</ul>
<hr />
<p><strong>Why Password Hashing is Important</strong></p>
<p>Instead of storing the actual password, we store a <strong>hashed version</strong> of it.</p>
<p>What is hashing?</p>
<ul>
<li><p>Converts a password into a random-looking string</p>
</li>
<li><p>It is a <strong>one-way function</strong> (cannot be reversed easily)</p>
</li>
</ul>
<p>So even if your database is leaked, attackers <strong>cannot see real passwords</strong>.</p>
<hr />
<p>Before using hashing, install the library:</p>
<pre><code class="language-javascript">npm install bcrypt
</code></pre>
<p>Then import it in your project:</p>
<pre><code class="language-javascript">const bcrypt = require("bcrypt");
</code></pre>
<hr />
<p><strong>Signup API Implementation</strong></p>
<pre><code class="language-javascript">app.post("/signup", async (req, res) =&gt; {
  const username = req.body.username;
  const password = req.body.password;

  const hashedPassword = await bcrypt.hash(password, 10);

  const userExist = await pool.query(
    "SELECT * FROM users WHERE username = $1",
    [username],
  );

  if (userExist.rows[0]) {
    return res.status(403).json({
      message: "User with this username already exists",
    });
  }

  const response = await pool.query(
    "INSERT INTO users (username, password) VALUES (\(1, \)2) RETURNING id, username, password",
    [username, hashedPassword],
  );

  res.status(200).json({
    userId: response.rows[0].id,
    message: "Signup Done",
  });
});
</code></pre>
<hr />
<p><strong>Why We Use</strong> <code>$1</code> <strong>Instead of Direct Values in SQL ?</strong></p>
<p>You might be tempted to write a query like this:</p>
<pre><code class="language-plaintext">const query = `SELECT * FROM users WHERE username = '${username}'`;
</code></pre>
<p>This may work—but it is <strong>dangerous</strong> and result to SQL Injection.</p>
<hr />
<p><strong>What is SQL Injection?</strong></p>
<p>SQL Injection happens when a user sends <strong>malicious input</strong> that changes your query.</p>
<hr />
<p><strong>Final Result: Testing /signup API Using Postman</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/91a8a2b2-b62e-4dff-b653-df6e55dbfcb9.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>Step 7: Signin + JWT Authentication</h3>
<p>After signup, the next step is allowing users to <strong>log in</strong> and get a token for authentication.</p>
<p><strong>What we want to achieve</strong></p>
<ul>
<li><p>Verify user credentials (username + password)</p>
</li>
<li><p>Compare hashed password securely</p>
</li>
<li><p>Generate a token after successful login</p>
</li>
<li><p>Send that token to the client</p>
</li>
</ul>
<hr />
<p><strong>Install JWT Library</strong></p>
<p>Before using tokens, install:</p>
<pre><code class="language-csharp">npm install jsonwebtoken
</code></pre>
<p>Import it:</p>
<pre><code class="language-javascript">const jwt = require("jsonwebtoken");
</code></pre>
<hr />
<p><strong>Signin API Implementation</strong></p>
<pre><code class="language-javascript">app.post("/signin", async (req, res) =&gt; {
  const username = req.body.username;
  const password = req.body.password;

  const userExist = await pool.query(
    "SELECT * FROM users WHERE username = $1",
    [username],
  );

  if (!userExist.rows[0]) {
    return res.status(404).json({
      message: "User not found",
    });
  }

  const correctPassword = await bcrypt.compare(
    password,
    userExist.rows[0].password,
  );

  if (correctPassword) {
    const token = jwt.sign(
      {
        userId: userExist.rows[0].id,
      },
      "shubham123",
    );

    return res.status(200).json({
      token,
    });
  } else {
    return res.status(403).json({
      message: "Password is invalid",
    });
  }
});
</code></pre>
<hr />
<p><strong>Final Result: Testing /signin API Using Postman</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/994f9dd4-bee4-4950-8a0b-292b5271c4f2.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3><strong>Step 8:</strong> Authentication Middleware (Protecting Routes)</h3>
<p>Now that we have JWT-based login, we need a way to <strong>protect routes</strong> so only logged-in users can access them.</p>
<p>For that, we create a middleware.</p>
<hr />
<p>Let's created a separate file: <code>middleware.js</code></p>
<p><strong>Auth Middleware Implementation</strong></p>
<pre><code class="language-javascript">const jwt = require("jsonwebtoken");
const { pool } = require("./models.js");

async function authMiddleware(req, res, next) {
  try {
    const token = req.headers.token;

    if (!token) {
      return res.status(401).json({
        message: "Token missing",
      });
    }

    const decode = jwt.verify(token, "shubham123");

    const userExist = await pool.query(
      "SELECT * FROM users WHERE id = $1",
      [decode.userId],
    );

    if (!userExist.rows[0]) {
      return res.status(404).json({
        message: "User not found",
      });
    }

    req.userId = decode.userId;
    next();
  } catch (err) {
    return res.status(403).json({
      message: "Invalid or expired token",
    });
  }
}

module.exports = {
  authMiddleware,
};
</code></pre>
<hr />
<h3>Step 9: <strong>Create Todo</strong></h3>
<p>Before writing the code, let’s understand</p>
<p><strong>What are we trying to achieve?</strong></p>
<p>We want:<br />1. Only <strong>logged-in users</strong> should be able to create a todo i.e. Each todo should be linked to the <strong>specific user who created it</strong></p>
<p>When user tries to create a todo, first <strong>authentication happens</strong> using <code>authMiddleware</code>.</p>
<ul>
<li><p>It verifies the token</p>
</li>
<li><p>Extracts <code>userId</code></p>
</li>
<li><p>Attaches it to <code>req.userId</code></p>
</li>
</ul>
<p>Then we use this <code>userId</code> to link the todo with the logged-in user.</p>
<p>So each todo belongs to a specific user, and later we can fetch user-specific todos.</p>
<pre><code class="language-javascript">app.post("/todo", authMiddleware, async (req, res) =&gt; {
  const title = req.body.title;

  const newTodo = await pool.query(
    "INSERT INTO todo (title,user_id) VALUES (\(1, \)2) RETURNING id", [title, req.userId]);

  res.status(200).json({
    id: newTodo.rows[0].id,
    message: "todo get created",
  });
});
</code></pre>
<hr />
<p><strong>Final Result: Testing POST /todo API Using Postman</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/5f6b1a1a-4492-4ff0-96dd-fab5653b0de1.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3><strong>Step 10: Get All Todos</strong></h3>
<pre><code class="language-js">app.get("/todo", authMiddleware, async (req, res) =&gt; {
  
  const allTodos = await pool.query("SELECT * FROM todo WHERE user_id = $1", [req.userId]);

  res.status(200).json({
    todos: allTodos.rows,
  });
});
</code></pre>
<hr />
<p><strong>Final Result: Testing GET /todo API Using Postman</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/62444f9d-7437-494b-9fca-150a84a80f8b.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3><strong>Complete Source Code</strong></h3>
<p>I’ve uploaded the complete project on GitHub. You can check it here:</p>
<p><strong>GitHub Repo:</strong><br /><a href="https://github.com/shubhamsinghbundela/Todo-Application-SQL">https://github.com/shubhamsinghbundela/Todo-Application-SQL</a></p>
]]></content:encoded></item><item><title><![CDATA[Refactoring a Trello Backend into a Scalable REST API Architecture]]></title><description><![CDATA[Till now, the backend was working, but everything was in a single file / unstructured format.
Now the goal is to:

Follow clean folder structure

Make code scalable & maintainable



Before diving int]]></description><link>https://blog.realdev.club/refactoring-a-trello-backend-into-a-scalable-rest-api-architecture</link><guid isPermaLink="true">https://blog.realdev.club/refactoring-a-trello-backend-into-a-scalable-rest-api-architecture</guid><category><![CDATA[REST API]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[Express]]></category><category><![CDATA[MongoDB]]></category><category><![CDATA[backend]]></category><category><![CDATA[clean code]]></category><category><![CDATA[Clean Architecture]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Shubham Kumar Singh]]></dc:creator><pubDate>Tue, 21 Apr 2026 23:10:31 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/898d4247-bb58-4a51-b582-574edb178040.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Till now, the backend was working, but everything was in a <strong>single file / unstructured format</strong>.</p>
<p>Now the goal is to:</p>
<ul>
<li><p>Follow <strong>clean folder structure</strong></p>
</li>
<li><p>Make code <strong>scalable &amp; maintainable</strong></p>
</li>
</ul>
<hr />
<p>Before diving into this structured version, I recommend reading my initial implementation where everything was built in a single file: <strong>Building a Trello - Backend from Scratch</strong> → <a href="https://shubhamsinghbundela.hashnode.dev/building-a-trello-backend-from-scratch">https://shubhamsinghbundela.hashnode.dev/building-a-trello-backend-from-scratch</a></p>
<hr />
<h3>Step 1: Initialize Project</h3>
<pre><code class="language-javascript">npm init -y
</code></pre>
<p>It Creates <code>package.json</code></p>
<hr />
<h3>Step 2: Install Dependencies</h3>
<pre><code class="language-plaintext">npm install express
npm install --save-dev nodemon
npm install dotenv // This package is used to load environment variables from the .env file.
</code></pre>
<hr />
<h3>Step 3: Setup App Entry (src/app.js)</h3>
<p>Create <code>src/app.js</code></p>
<pre><code class="language-javascript">import express from "express";

const app = express();

app.use(express.json());

export default app;
</code></pre>
<p>This file is responsible for:</p>
<ul>
<li><p>Initializing express app</p>
</li>
<li><p>Adding global middlewares</p>
</li>
</ul>
<hr />
<h3>Step 4: Environment Variables</h3>
<p>Create <code>.env</code> file:</p>
<pre><code class="language-javascript">PORT=3000
NODE_ENV=development
MONGODB_URI=your_mongodb_connection_string
</code></pre>
<hr />
<h3>Step 5: Server Entry Point (server.js)</h3>
<p>create <code>server.js</code></p>
<pre><code class="language-javascript">import "dotenv/config"; //This automatically loads .env variables into process.env
import app from "./src/app.js";

const PORT = process.env.PORT || 3000;

const start = async () =&gt; {
    app.listen(PORT, () =&gt; {
        console.log(`Server is running at \({PORT} in \){process.env.NODE_ENV} mode`);
    });
};

start().catch((err) =&gt; {
    console.error("Failed to start server", err);
    process.exit(1);
});
</code></pre>
<p>This file is responsible for:</p>
<ul>
<li><p>Starting the server</p>
</li>
<li><p>Handling startup errors</p>
</li>
</ul>
<hr />
<h3>Step 6: Scripts Setup</h3>
<p>Update <code>package.json</code>:</p>
<pre><code class="language-javascript">"scripts": {
  "start": "node server.js",
  "dev": "nodemon server.js"
}
</code></pre>
<p>Use:</p>
<pre><code class="language-plaintext">npm run dev
</code></pre>
<hr />
<h3>Step 7: Setup Database Connection</h3>
<p>Create:</p>
<pre><code class="language-plaintext">src/common/config/db.js
</code></pre>
<pre><code class="language-js">import mongoose from "mongoose";
import dns from "dns";

dns.setServers(["1.1.1.1", "8.8.8.8"]);

const connectDB = async () =&gt; {
    const conn = await mongoose.connect(process.env.MONGODB_URI);

    console.log(`MongoDB connected: ${conn.connection.host}`);
};

export default connectDB;
</code></pre>
<p>Initially, I tried connecting to <strong>MongoDB (cloud)</strong> but faced a DNS issue</p>
<p><strong>Problem:</strong></p>
<p>MongoDB connection was failing due to DNS resolution.</p>
<p><strong>Fix:</strong></p>
<p>After watching a YouTube video: , I added this:</p>
<pre><code class="language-javascript">const dns = require("dns");
dns.setServers(["1.1.1.1", "8.8.8.8"]);
</code></pre>
<p>This fixed the issue and I was able to connect to cloud MongoDB.</p>
<hr />
<h3>Step 8: Standard API Response Structure</h3>
<p><strong>Create:</strong></p>
<pre><code class="language-plaintext">src/common/utils/api-response.js
</code></pre>
<pre><code class="language-javascript">class ApiResponse {
    static ok(res, message, data = null) {
        return res.status(200).json({
            success: true,
            message,
            data
        });
    }
}

export default ApiResponse;
</code></pre>
<p><strong>Why This is Important</strong></p>
<p>Instead of writing:</p>
<pre><code class="language-javascript">res.status(200).json({ message: "Success" });
</code></pre>
<p>To avoid repeating response logic across multiple APIs, I implemented a reusable utility using the DRY (Don't Repeat Yourself) principle:</p>
<pre><code class="language-javascript">ApiResponse.ok(res, "Success", data);
</code></pre>
<hr />
<h3>Step 9: Standard API Error Structure</h3>
<p>While building APIs, handling errors properly is <strong>as important as handling success responses</strong>.</p>
<p>1**. Create Custom Error Class**</p>
<p>Create:</p>
<pre><code class="language-plaintext">src/common/utils/api-error.js
</code></pre>
<pre><code class="language-javascript">class ApiError extends Error {
    // Inherit from built-in JavaScript Error
    constructor(statusCode, message) {
        super(message);

        this.statusCode = statusCode;

        // Capture clean stack trace
        Error.captureStackTrace(this, this.constructor);
    }

    static badRequest(message = "Bad Request") {
        return new ApiError(400, message);
    }

    static forbidden(message = "Forbidden") {
        return new ApiError(403, message);
    }

    static notFound(message = "Not Found") {
        return new ApiError(404, message);
    }
}

export default ApiError;
</code></pre>
<p><strong>Why Extend</strong> <code>Error</code><strong>?</strong></p>
<p>JavaScript provides built-in errors like:</p>
<ul>
<li><p><code>Error</code></p>
</li>
<li><p><code>TypeError</code></p>
</li>
<li><p><code>ReferenceError</code></p>
</li>
</ul>
<p>But they <strong>don’t include HTTP status codes</strong>, which are required in APIs.</p>
<p><strong>2. Global Error Middleware</strong></p>
<p>Create:</p>
<pre><code class="language-plaintext">src/common/middleware/error.middleware.js
</code></pre>
<pre><code class="language-javascript">import ApiError from "../utils/api-error.js";

const errorHandler = (err, req, res, next) =&gt; {

    // Handle known (custom) errors
    if (err instanceof ApiError) {
        return res.status(err.statusCode).json({
            success: false,
            message: err.message
        });
    }

    // Handle unknown errors
    return res.status(500).json({
        success: false,
        message: "Internal Server Error"
    });
};

export default errorHandler;
</code></pre>
<p><strong>3. Register Middleware in App</strong></p>
<p>Update <code>src/app.js</code>:</p>
<pre><code class="language-javascript">import express from "express";
import authRoute from "./modules/auth/auth.routes.js";
import errorHandler from "./common/middleware/error.middleware.js";

const app = express();
app.use(express.json());

app.use(errorHandler);

export default app;
</code></pre>
<p>This must be the <strong>last middleware</strong> in the app.  </p>
<p><strong>Final Error Flow</strong></p>
<pre><code class="language-plaintext">Request → Route → Controller → Service → throws ApiError → Passes to errorHandler middleware → JSON response 
</code></pre>
<hr />
<h3>Step 10: Authentication Module (Modular Structure)</h3>
<p>Now that we have:</p>
<ul>
<li><p>Clean folder structure</p>
</li>
<li><p>Database setup</p>
</li>
<li><p>Standard response &amp; error handling</p>
</li>
</ul>
<p>Next step is to build a <strong>modular authentication system</strong></p>
<hr />
<p><strong>Module Structure</strong></p>
<p>Inside <code>src/modules/auth/</code>, I created:</p>
<pre><code class="language-plaintext">auth/
├── auth.controller.js
├── auth.middleware.js
├── auth.model.js
├── auth.routes.js
├── auth.service.js
</code></pre>
<hr />
<p><strong>Why This Structure?</strong></p>
<p>Each layer has a clear responsibility:</p>
<ul>
<li><p><strong>Model</strong> → Database schema</p>
</li>
<li><p><strong>Routes</strong> → Define API endpoints</p>
</li>
<li><p><strong>Controller</strong> → Handle request/response</p>
</li>
<li><p><strong>Service</strong> → Business logic</p>
</li>
<li><p><strong>Middleware</strong> → Authentication / validation</p>
</li>
</ul>
<p>This makes code <strong>clean, maintainable, and scalable</strong></p>
<hr />
<p><strong>Create User Schema (</strong><code>auth.model.js</code><strong>)</strong></p>
<pre><code class="language-javascript">import mongoose from "mongoose";

const userSchema = new mongoose.Schema({
    username: String,
    password: String
}, { timestamps: true });

const userModel = mongoose.model("users", userSchema);

export default userModel;
</code></pre>
<p><code>timestamps</code> automatically adds:</p>
<ul>
<li><p><code>createdAt</code></p>
</li>
<li><p><code>updatedAt</code></p>
</li>
</ul>
<hr />
<p><strong>Register Auth Routes in App.js</strong></p>
<p>Update <code>src/app.js</code>:</p>
<pre><code class="language-javascript">import express from "express";
import authRoute from "./modules/auth/auth.routes.js";
import errorMiddleware from "./common/middleware/error.middleware.js";

const app = express();

app.use(express.json());

// Register routes
app.use("/api/auth", authRoute);

// Global error handler
app.use(errorMiddleware);

export default app;
</code></pre>
<p>All auth APIs will now be available under:</p>
<pre><code class="language-plaintext">/api/auth/*
</code></pre>
<hr />
<p><strong>Define Routes (</strong><code>auth.routes.js</code><strong>)</strong></p>
<pre><code class="language-javascript">import { Router } from "express";
import * as controller from "./auth.controller.js";

const router = Router();

router.post("/signup", controller.signup);

export default router;
</code></pre>
<hr />
<p><strong>Controller Layer (</strong><code>auth.controller.js</code><strong>)</strong></p>
<pre><code class="language-javascript">import ApiResponse from "../../common/utils/api-response.js";
import * as authService from "./auth.service.js";

const signup = async (req, res, next) =&gt; {
  try {
    const user = await authService.signup(req.body);

    ApiResponse.ok(res, "User get Created", user);
  } catch (error) {
    next(error); // pass error to global error middleware
  }
};

export { signup };
</code></pre>
<p><strong>Responsibility of Controller</strong></p>
<ul>
<li><p>Calls service layer</p>
</li>
<li><p>Sends response using <code>ApiResponse</code></p>
</li>
<li><p>Keeps logic minimal</p>
</li>
</ul>
<hr />
<p><strong>Business Logic (</strong><code>auth.service.js</code><strong>)</strong></p>
<pre><code class="language-javascript">import userModel from "./auth.model.js";
import ApiError from "../../common/utils/api-error.js";

const signup = async ({ username, password }) =&gt; {

    // Step 1: Check if user already exists
    const userExists = await userModel.findOne({ username });

    if (userExists) {
        throw ApiError.forbidden("User Already Exists");
    }

    // Step 2: Create new user
    const newUser = await userModel.create({
        username,
        password
    });

    return newUser;
};

export {
    signup
};
</code></pre>
<hr />
<p><strong>Final Result: Testing /api/auth/signup API Using Postman</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/918612d0-fcee-4c57-8864-3a4cf67f5726.png" alt="" style="display:block;margin:0 auto" />

<hr />
<p><strong>Request Flow</strong></p>
<pre><code class="language-plaintext">POST /api/auth/signup
  ↓
auth.routes.js
  ↓
auth.controller.js
  ↓
auth.service.js
  ↓
MongoDB
</code></pre>
<hr />
<h3>Step 11: Implement Signin</h3>
<p>Now that the signup API is ready, the next step is to allow users to <strong>login (signin)</strong> and generate a token for authentication.</p>
<hr />
<p><strong>Define Routes</strong></p>
<p>Inside <code>auth.route.js</code>, we define the signin route:</p>
<pre><code class="language-javascript">import { Router } from "express";
import * as controller from "./auth.controller.js";

const router = Router();

router.post("/signup", controller.signup);
router.post("/signin", controller.signin);

export default router;
</code></pre>
<p>This creates an endpoint:</p>
<pre><code class="language-plaintext">POST /api/auth/signin
</code></pre>
<hr />
<p><strong>Controller Layer</strong></p>
<p>Now we handle the request in the controller.</p>
<pre><code class="language-javascript">const signin = async (req, res, next) =&gt; {
  try {
    const token = await authService.signin(req.body);

    ApiResponse.ok(res, "Signin successfully", token);
  } catch (error) {
    next(error);
  }
};
</code></pre>
<h4>Responsibility of Controller:</h4>
<ul>
<li><p>Receive request data (<code>req.body</code>)</p>
</li>
<li><p>Call service layer</p>
</li>
<li><p>Send response using <code>ApiResponse</code></p>
</li>
<li><p>Pass errors to global error handler</p>
</li>
</ul>
<hr />
<p><strong>Service Layer (Business Logic)</strong></p>
<p>Now the actual authentication logic lives in <code>auth.service.js</code>:</p>
<pre><code class="language-javascript">const signin = async ({ username, password }) =&gt; {
  const userExists = await userModel.findOne({
    username: username,
    password: password,
  });

  if (!userExists) {
    throw ApiError.notFound("User not found");
  }

  const token = jwt.sign(
    {
      userId: userExists.id,
    },
    "shubham123"
  );

  return token;
};
</code></pre>
<hr />
<p><strong>Final Result: Testing /api/auth/signin API Using Postman</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/ee753d49-ad84-4e26-ab05-23448d2e26b3.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>Step 12: Create organisation inside Organisation Module</h3>
<p>Now that authentication is complete, the next step is to allow users to create organisations — similar to how Trello works.</p>
<hr />
<p><strong>Requirements</strong></p>
<p>After login, a user should be able to:</p>
<ul>
<li><p>Create an organisation</p>
</li>
<li><p>Automatically become the <strong>admin</strong></p>
</li>
<li><p>Ensure organisation names are <strong>unique</strong></p>
</li>
<li><p>Only <strong>authenticated users</strong> can create organisations</p>
</li>
</ul>
<hr />
<p><strong>Module Structure</strong></p>
<p>Inside <code>src/modules/</code>, create a new folder:</p>
<pre><code class="language-plaintext">src/modules/org/
├── org.controller.js
├── org.middleware.js
├── org.model.js
├── org.routes.js
├── org.service.js
</code></pre>
<hr />
<p><strong>Create Organisation Schema</strong></p>
<p>Create:</p>
<pre><code class="language-plaintext">src/modules/org/org.model.js
</code></pre>
<pre><code class="language-javascript">import mongoose from "mongoose";

const orgSchema = new mongoose.Schema({
    orgName: "String",
    description: "String",
    admin: mongoose.Types.ObjectId,
    member: [mongoose.Types.ObjectId]
}, { timestamps: true });

const orgModel = mongoose.model("organisations", orgSchema);

export default orgModel;
</code></pre>
<hr />
<p><strong>Authentication Middleware</strong></p>
<p>To ensure only logged-in users can create organisations:</p>
<p>Inside:</p>
<pre><code class="language-plaintext">src/modules/auth/auth.middleware.js
</code></pre>
<pre><code class="language-javascript">import jwt from "jsonwebtoken";

const authMiddleware = (req, res, next) =&gt; {
    const token = req.headers.token;

    const decode = jwt.verify(token, "shubham123");

    req.userId = decode.userId;

    next();
};

export default authMiddleware;
</code></pre>
<hr />
<p><strong>Register Organisation Routes</strong></p>
<p>Update <code>src/app.js</code>:</p>
<pre><code class="language-javascript">import orgRoute from "./modules/org/org.routes.js";

app.use("/api/org", orgRoute);
</code></pre>
<p>Now all organisation APIs will be available under:</p>
<pre><code class="language-plaintext">/api/org/* 
</code></pre>
<hr />
<p><strong>Define Routes</strong></p>
<p><strong>Create:</strong></p>
<pre><code class="language-plaintext">src/modules/org/org.routes.js
</code></pre>
<pre><code class="language-javascript">import { Router } from "express";
import * as controller from "./org.controller.js";
import authMiddleware from "../auth/auth.middleware.js";

const router = Router();

router.post(
  "/create-organisation",
  authMiddleware,
  controller.createOrganisation
);

export default router;
</code></pre>
<hr />
<p><strong>Controller Layer</strong></p>
<p><strong>Create:</strong></p>
<pre><code class="language-plaintext">src/modules/org/org.controller.js
</code></pre>
<pre><code class="language-javascript">import ApiResponse from "../../common/utils/api-response.js";
import * as orgService from "./org.service.js";

const createOrganisation = async (req, res, next) =&gt; {
  try {
    const org = await orgService.createOrganisation(req);

    ApiResponse.ok(
      res,
      "Org get created",
      org
    );

  } catch (error) {
    next(error);
  }
};

export { createOrganisation };
</code></pre>
<p><strong>Responsibility of Controller</strong></p>
<ul>
<li><p>Calls service layer</p>
</li>
<li><p>Sends success response</p>
</li>
<li><p>Passes errors to global middleware</p>
</li>
</ul>
<hr />
<p><strong>Business Logic (Service Layer)</strong></p>
<p>Create:</p>
<pre><code class="language-plaintext">src/modules/org/org.service.js
</code></pre>
<pre><code class="language-javascript">import ApiError from "../../common/utils/api-error.js";
import orgModel from "./org.model.js";

const createOrganisation = async (req) =&gt; {

    // Step 1: Check if organisation already exists
    const orgExist = await orgModel.findOne({
        orgName: req.body.orgName
    });

    if (orgExist) {
        throw ApiError.forbidden("Organisation already exists");
    }

    // Step 2: Create organisation
    const newOrg = await orgModel.create({
        orgName: req.body.orgName,
        description: req.body.description,
        admin: req.userId,
        member: []
    });

    return newOrg;
};

export { createOrganisation };
</code></pre>
<hr />
<p><strong>Request Flow</strong></p>
<pre><code class="language-plaintext">POST /api/org/create-organisation
   ↓
auth.middleware.js (verify token)
   ↓
org.controller.js
   ↓
org.service.js
   ↓
MongoDB
   ↓
Response
</code></pre>
<hr />
<p><strong>Final Result: Testing api/org/create-organisation API Using Postman</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/71a65463-46d8-4f7d-b3b2-99ed54975c40.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>Step 13: Add Members to Organisation</h3>
<p>Now that organisations are created, the next step is to allow admins to add members — similar to how teams work in Trello.</p>
<hr />
<p><strong>Requirement</strong></p>
<ul>
<li><p>Admin can add users to their organisation</p>
</li>
<li><p>Users are added using their <strong>username</strong></p>
</li>
<li><p>Only <strong>existing users</strong> can be added</p>
</li>
<li><p>Prevent adding the <strong>same user twice</strong></p>
</li>
</ul>
<hr />
<p><strong>Update Routes</strong></p>
<p>Update:</p>
<pre><code class="language-plaintext">src/modules/org/org.routes.js
</code></pre>
<pre><code class="language-javascript">import { Router } from "express";
import * as controller from "./org.controller.js";
import authMiddleware from "../auth/auth.middleware.js";

const router = Router();

router.post("/create-organisation", authMiddleware, controller.createOrganisation);

router.post("/add-member", authMiddleware, controller.addMember);

export default router;
</code></pre>
<hr />
<p><strong>Controller Layer</strong></p>
<p>Update:</p>
<pre><code class="language-plaintext">src/modules/org/org.controller.js
</code></pre>
<pre><code class="language-javascript">const addMember = async (req, res, next) =&gt; {
  try {
    const data = await orgService.addMember(req);

    ApiResponse.ok(
      res,
      "Member get added",
      data
    );

  } catch (error) {
    next(error);
  }
};

export {
  addMember
};
</code></pre>
<hr />
<p><strong>Responsibility</strong></p>
<ul>
<li><p>Calls service layer</p>
</li>
<li><p>Sends response</p>
</li>
<li><p>Passes errors to middleware</p>
</li>
</ul>
<hr />
<p><strong>Business Logic (Service Layer)</strong></p>
<p>Update:</p>
<pre><code class="language-plaintext">src/modules/org/org.service.js
</code></pre>
<pre><code class="language-javascript">import ApiError from "../../common/utils/api-error.js";
import orgModel from "./org.model.js";
import userModel from "../auth/auth.model.js";

const addMember = async (req) =&gt; {

    const newMember = req.body.member;

    // Step 1: Check user exists
    const newMemberUser = await userModel.findOne({
        username: newMember
    });

    if (!newMemberUser) {
        throw ApiError.notFound("User not exist");
    }

    // Step 2: Find organisation (admin only)
    const orgDetails = await orgModel.findOne({
        admin: req.userId
    });

    if (!orgDetails) {
        throw ApiError.notFound("Organisation not exists");
    }

    // Step 3: Prevent duplicate members
    const memberExists = orgDetails.member.includes(newMemberUser._id);

    if (memberExists) {
        throw ApiError.badRequest("Member already exists in organisation");
    }

    // Step 4: Add member
    orgDetails.member.push(newMemberUser._id);

    await orgDetails.save();

    return orgDetails;
};

export {
    addMember
};
</code></pre>
<hr />
<p><strong>Request Flow</strong></p>
<pre><code class="language-plaintext">POST /api/org/add-member
  ↓
auth.middleware.js (verify token)
  ↓
org.controller.js
  ↓
org.service.js
  ↓
MongoDB
  ↓
Response
</code></pre>
<hr />
<p><strong>Final Result: Testing api/org/add-member API Using Postman</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/392ff088-cc47-46aa-95c6-7867390c4bb4.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>Step 14: Remove Member from Organisation</h3>
<p>After adding members, the next important feature is allowing admins to remove members — similar to how team management works in Trello.</p>
<hr />
<p><strong>Requirement</strong></p>
<p>Admins should be able to:</p>
<ul>
<li><p>Remove users from the organisation</p>
</li>
<li><p>Only remove <strong>existing members</strong></p>
</li>
<li><p>Only <strong>admin</strong> can perform this action</p>
</li>
</ul>
<hr />
<p><strong>Update Routes</strong></p>
<p>Update:</p>
<pre><code class="language-plaintext">src/modules/org/org.routes.js
</code></pre>
<pre><code class="language-javascript">import { Router } from "express";
import * as controller from "./org.controller.js";
import authMiddleware from "../auth/auth.middleware.js";

const router = Router();

router.post("/create-organisation", authMiddleware, controller.createOrganisation);

router.post("/add-member", authMiddleware, controller.addMember);

router.delete("/delete-member", authMiddleware, controller.deleteMember);

export default router;
</code></pre>
<hr />
<p><strong>Controller Layer</strong></p>
<p>Update:</p>
<pre><code class="language-plaintext">src/modules/org/org.controller.js
</code></pre>
<pre><code class="language-javascript">const deleteMember = async (req, res, next) =&gt; {
  try {
    const data = await orgService.deleteMember(req);

    ApiResponse.ok(
      res,
      "Member get deleted",
      data
    );

  } catch (error) {
    next(error);
  }
};

export {
  deleteMember
};
</code></pre>
<hr />
<p><strong>Business Logic (Service Layer)</strong></p>
<p>Update:</p>
<pre><code class="language-plaintext">src/modules/org/org.service.js
</code></pre>
<pre><code class="language-javascript">import ApiError from "../../common/utils/api-error.js";
import orgModel from "./org.model.js";
import userModel from "../auth/auth.model.js";

const deleteMember = async (req) =&gt; {

    const deleteUser = req.body.username;

    // Step 1: Check user exists
    const deleteUserData = await userModel.findOne({
        username: deleteUser
    });

    if (!deleteUserData) {
        throw ApiError.notFound("User not exist");
    }

    // Step 2: Find organisation (admin only)
    const orgDetails = await orgModel.findOne({
        admin: req.userId
    });

    if (!orgDetails) {
        throw ApiError.notFound("Organisation not exists");
    }

    // Step 3: Check if user is actually a member
    const isMember = orgDetails.member.some(
        id =&gt; id.toString() === deleteUserData._id.toString()
    );

    if (!isMember) {
        throw ApiError.badRequest("User is not a member of this organisation");
    }

    // Step 4: Remove member
    orgDetails.member = orgDetails.member.filter(
        id =&gt; id.toString() !== deleteUserData._id.toString()
    );

    await orgDetails.save();

    return orgDetails;
};

export {
    deleteMember
};
</code></pre>
<hr />
<p><strong>Request Flow</strong></p>
<pre><code class="language-plaintext">DELETE /api/org/delete-member
   ↓
auth.middleware.js (verify token)
  ↓
org.controller.js
  ↓
org.service.js
  ↓
MongoDB
  ↓
Response
</code></pre>
<hr />
<p><strong>Final Result: Testing api/org/delete-member API Using Postman</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/97de3e07-5f7d-4b7a-883a-d4652c02cf40.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>Step 15: Create Board</h3>
<p>Now comes the most important feature — boards — just like in Trello.</p>
<hr />
<p><strong>Requirement</strong></p>
<ul>
<li><p>Each organisation can have multiple boards</p>
</li>
<li><p>Only <strong>authorized users (admin)</strong> can create boards</p>
</li>
<li><p>Each board belongs to a <strong>specific organisation</strong></p>
</li>
</ul>
<hr />
<p><strong>Module Structure</strong></p>
<p>Inside <code>src/modules/</code>, create:</p>
<pre><code class="language-plaintext">board/
├── board.controller.js
├── board.middleware.js
├── board.model.js
├── board.routes.js
├── board.service.js
</code></pre>
<hr />
<p><strong>Create Board Schema</strong></p>
<p>Create:</p>
<pre><code class="language-plaintext">src/modules/board/board.model.js
</code></pre>
<pre><code class="language-javascript">import mongoose from "mongoose";

const boardSchema = mongoose.Schema({
    boardName: "String",
    organisationId: mongoose.Types.ObjectId
});

const boardModel = mongoose.model("boards", boardSchema);

export default boardModel;
</code></pre>
<hr />
<p><strong>Define Routes</strong></p>
<p>Create:</p>
<pre><code class="language-plaintext">src/modules/board/board.routes.js
</code></pre>
<pre><code class="language-javascript">import { Router } from "express";
import authMiddleware from "../auth/auth.middleware.js";
import * as controller from "./board.controller.js";

const router = Router();

router.post("/create-board", authMiddleware, controller.createBoard);

export default router;
</code></pre>
<hr />
<p><strong>Register Route in App</strong></p>
<p>Update <code>src/app.js</code>:</p>
<pre><code class="language-javascript">import boardRoute from "./modules/board/board.routes.js";

app.use("/api/board", boardRoute);
</code></pre>
<hr />
<p><strong>Controller Layer</strong></p>
<p>Create:</p>
<pre><code class="language-plaintext">src/modules/board/board.controller.js
</code></pre>
<pre><code class="language-javascript">import ApiResponse from "../../common/utils/api-response.js";
import * as boardService from "./board.service.js";

const createBoard = async (req, res, next) =&gt; {
  try {
    const data = await boardService.createBoard(req);

    ApiResponse.ok(
      res,
      "Board get created",
      data
    );

  } catch (error) {
    next(error);
  }
};

export {
  createBoard
};
</code></pre>
<hr />
<p><strong>Business Logic (Service Layer)</strong></p>
<p>Create:</p>
<pre><code class="language-plaintext">src/modules/board/board.service.js
</code></pre>
<pre><code class="language-javascript">import ApiError from "../../common/utils/api-error.js";
import orgModel from "../org/org.model.js";
import boardModel from "./board.model.js";

const createBoard = async (req) =&gt; {

    const boardName = req.body.boardName;

    // Step 1: Check organisation (admin only)
    const orgDetails = await orgModel.findOne({
        admin: req.userId
    });

    if (!orgDetails) {
        throw ApiError.notFound("Organisation not exists");
    }

    // Step 2: Create board
    const newBoard = await boardModel.create({
        boardName,
        organisationId: orgDetails._id
    });

    return newBoard;
};

export {
    createBoard
};
</code></pre>
<hr />
<p><strong>Request Flow</strong></p>
<pre><code class="language-plaintext">POST /api/board/create-board
  ↓
auth.middleware.js (verify token)
  ↓
board.controller.js
  ↓
board.service.js
  ↓
MongoDB
  ↓
Response
</code></pre>
<hr />
<p><strong>Final Result: Testing api/board/create-board API Using Postman</strong>  </p>
<img src="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/dc4e749d-d296-4988-9346-48322b62bb01.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>Step 16: Task Management (Create Task)</h3>
<p>Now comes the most important part of any Trello-like application — <strong>Tasks</strong>.</p>
<p>This is where users actually track their work.</p>
<hr />
<p><strong>Requirement</strong></p>
<p>We want users to:</p>
<ul>
<li><p>Create tasks inside a board</p>
</li>
<li><p>Update task status (<strong>Todo → In Progress → Done</strong>) <em>(next step)</em></p>
</li>
<li><p>Delete tasks <em>(next step)</em></p>
</li>
<li><p>Ensure tasks are tied to the logged-in user</p>
</li>
</ul>
<p>This forms the <strong>core task lifecycle system</strong></p>
<hr />
<p><strong>Module Structure</strong></p>
<p>Inside <code>src/modules/</code>, create:</p>
<pre><code class="language-plaintext">task/
├── task.controller.js
├── task.middleware.js
├── task.model.js
├── task.routes.js
├── task.service.js
</code></pre>
<hr />
<p><strong>Task Schema</strong></p>
<p>Create:</p>
<pre><code class="language-plaintext">src/modules/task/task.model.js
</code></pre>
<pre><code class="language-javascript">import mongoose from "mongoose";

const taskSchema = mongoose.Schema({
    description: "String",
    status: "String",
    boardId: mongoose.Types.ObjectId,
    userId: mongoose.Types.ObjectId,
});

const taskModel = mongoose.model("tasks", taskSchema);

export default taskModel;
</code></pre>
<hr />
<p><strong>Define Routes</strong></p>
<p>Create:</p>
<pre><code class="language-plaintext">src/modules/task/task.routes.js
</code></pre>
<pre><code class="language-javascript">import { Router } from "express";
import authMiddleware from "../auth/auth.middleware.js";
import * as controller from "./task.controller.js";

const router = Router();

router.post("/create-task", authMiddleware, controller.createTask);

export default router;
</code></pre>
<hr />
<p><strong>Register Route in App</strong></p>
<p>Update <code>src/app.js</code>:</p>
<pre><code class="language-javascript">import taskRouter from "./modules/task/task.routes.js";

app.use("/api/task", taskRouter);
</code></pre>
<hr />
<p><strong>Controller Layer</strong></p>
<p>Create:</p>
<pre><code class="language-plaintext">src/modules/task/task.controller.js
</code></pre>
<pre><code class="language-javascript">import ApiResponse from "../../common/utils/api-response.js";
import * as taskService from "./task.service.js";

const createTask = async (req, res, next) =&gt; {
  try {
    const data = await taskService.createTask(req);

    ApiResponse.ok(
      res,
      "Task created successfully",
      data
    );

  } catch (error) {
    next(error);
  }
};

export {
  createTask
};
</code></pre>
<hr />
<p><strong>Business Logic (Service Layer)</strong></p>
<p>Create:</p>
<pre><code class="language-plaintext">src/modules/task/task.service.js
</code></pre>
<pre><code class="language-javascript">import ApiError from "../../common/utils/api-error.js";
import boardModel from "../board/board.model.js";
import taskModel from "./task.model.js";

const createTask = async (req) =&gt; {

    const { description, status, boardId } = req.body;

    // Step 1: Validate input
    if (!description || !status || !boardId) {
        throw ApiError.badRequest("All fields are required");
    }

    // Step 2: Validate status
    const allowedStatus = ["Todo", "In Progress", "Done"];

    if (!allowedStatus.includes(status)) {
        throw ApiError.badRequest("Invalid status");
    }

    // Step 3: Check board exists
    const board = await boardModel.findById(boardId);

    if (!board) {
        throw ApiError.notFound("Board not found");
    }

    // Step 4: Prevent duplicate task in same board
    const taskExists = await taskModel.findOne({
        userId: req.userId,
        description,
        boardId
    });

    if (taskExists) {
        throw ApiError.forbidden("Task already exists in this board");
    }

    // Step 5: Create task
    const newTask = await taskModel.create({
        userId: req.userId,
        description,
        status,
        boardId
    });

    return newTask;
};

export {
    createTask
};
</code></pre>
<hr />
<p><strong>Request Flow</strong></p>
<pre><code class="language-plaintext">POST /api/task/create-task
        ↓
auth.middleware.js (verify token)
        ↓
task.controller.js
        ↓
task.service.js
        ↓
MongoDB
        ↓
Response
</code></pre>
<hr />
<p><strong>Final Result: Testing api/task/create-task API Using Postman</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/d22beb16-0eac-4a8f-9f84-63eb52bacb1f.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>Step 17: Update Task Status</h3>
<p>Now that tasks can be created, the next step is to update their status — this is what enables the classic workflow of moving tasks across stages (Todo → In Progress → Done), just like in Trello.</p>
<hr />
<p><strong>Requirement</strong></p>
<p>When updating a task:</p>
<ul>
<li><p>Identify the task using <code>taskId</code></p>
</li>
<li><p>Validate the new <code>status</code></p>
</li>
<li><p>Ensure the task belongs to the logged-in user</p>
</li>
<li><p>Update the status</p>
</li>
</ul>
<hr />
<p><strong>Define Route</strong></p>
<p>Update:</p>
<pre><code class="language-plaintext">src/modules/task/task.routes.js
</code></pre>
<pre><code class="language-javascript">import { Router } from "express";
import authMiddleware from "../auth/auth.middleware.js";
import * as controller from "./task.controller.js";

const router = Router();

router.post("/create-task", authMiddleware, controller.createTask);

router.put("/update-task", authMiddleware, controller.updateTask);

export default router;
</code></pre>
<hr />
<p><strong>Controller Layer</strong></p>
<p>Update:</p>
<pre><code class="language-plaintext">src/modules/task/task.controller.js
</code></pre>
<pre><code class="language-javascript">const updateTask = async (req, res, next) =&gt; {
  try {
    const data = await taskService.updateTask(req);

    ApiResponse.ok(
      res,
      "Task updated successfully",
      data
    );

  } catch (error) {
    next(error);
  }
};

export {
  updateTask
};
</code></pre>
<hr />
<p><strong>Business Logic (Service Layer)</strong></p>
<p>Update:</p>
<pre><code class="language-plaintext">src/modules/task/task.service.js
</code></pre>
<pre><code class="language-javascript">import ApiError from "../../common/utils/api-error.js";
import taskModel from "./task.model.js";

const updateTask = async (req) =&gt; {

    const { taskId, status } = req.body;

    // Step 1: Validate status
    const allowedStatus = ["Todo", "In Progress", "Done"];

    if (!allowedStatus.includes(status)) {
        throw ApiError.badRequest("Invalid status");
    }

    // Step 2: Find task (only owner can update)
    const task = await taskModel.findOne({
        _id: taskId,
        userId: req.userId
    });

    if (!task) {
        throw ApiError.notFound("Task not found");
    }

    // Step 3: Update status
    task.status = status;

    await task.save();

    return task;
};

export {
    updateTask
};
</code></pre>
<hr />
<p><strong>Request Flow</strong></p>
<pre><code class="language-plaintext">PUT /api/task/update-task
        ↓
auth.middleware.js (verify token)
        ↓
task.controller.js
        ↓
task.service.js
        ↓
MongoDB
        ↓
Response
</code></pre>
<hr />
<p><strong>Final Result: Testing api/task/update-task API Using Postman</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/dab48cd0-ef37-42cf-833d-2736e4d74881.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>Step 18: Delete Task</h3>
<p>Now that tasks can be created and updated, the final step in the task lifecycle is deleting tasks — just like removing completed work in Trello.</p>
<hr />
<p><strong>Requirement</strong></p>
<p>When deleting a task:</p>
<ul>
<li><p>Identify the task using <code>taskId</code></p>
</li>
<li><p>Ensure the task belongs to the logged-in user</p>
</li>
<li><p>If it exists → delete it from the database</p>
</li>
</ul>
<hr />
<p><strong>Define Route</strong></p>
<p>Update:</p>
<pre><code class="language-plaintext">src/modules/task/task.routes.js
</code></pre>
<pre><code class="language-javascript">import { Router } from "express";
import authMiddleware from "../auth/auth.middleware.js";
import * as controller from "./task.controller.js";

const router = Router();

router.post("/create-task", authMiddleware, controller.createTask);
router.put("/update-task", authMiddleware, controller.updateTask);
router.delete("/delete-task", authMiddleware, controller.deleteTask);

export default router;
</code></pre>
<hr />
<p><strong>Controller Layer</strong></p>
<p>Update:</p>
<pre><code class="language-plaintext">src/modules/task/task.controller.js
</code></pre>
<pre><code class="language-javascript">const deleteTask = async (req, res, next) =&gt; {
  try {
    const data = await taskService.deleteTask(req);

    ApiResponse.ok(
      res,
      "Task deleted successfully",
      data
    );

  } catch (error) {
    next(error);
  }
};

export {
  deleteTask
};
</code></pre>
<hr />
<p><strong>Business Logic (Service Layer)</strong></p>
<p>Update:</p>
<pre><code class="language-plaintext">src/modules/task/task.service.js
</code></pre>
<pre><code class="language-javascript">import ApiError from "../../common/utils/api-error.js";
import taskModel from "./task.model.js";

const deleteTask = async (req) =&gt; {

    const { taskId } = req.body;

    // Step 1: Find task (only owner can delete)
    const task = await taskModel.findOne({
        _id: taskId,
        userId: req.userId
    });

    if (!task) {
        throw ApiError.notFound("Task not found");
    }

    // Step 2: Delete task
    await taskModel.deleteOne({ _id: taskId });

    return task;
};

export {
    deleteTask
};
</code></pre>
<hr />
<p><strong>Request Flow</strong></p>
<pre><code class="language-plaintext">DELETE /api/task/delete-task
        ↓
auth.middleware.js (verify token)
        ↓
task.controller.js
        ↓
task.service.js
        ↓
MongoDB
        ↓
Response
</code></pre>
<hr />
<p><strong>What Changed from Previous Approach?</strong></p>
<p>Before:</p>
<ul>
<li><p>All logic in one file</p>
</li>
<li><p>Hard to scale</p>
</li>
</ul>
<p>Now:</p>
<ul>
<li><p>Clean modular structure</p>
</li>
<li><p>Separation of concerns</p>
</li>
<li><p>Reusable logic</p>
</li>
<li><p>Easy to maintain</p>
</li>
</ul>
<hr />
<h3><strong>Complete Source Code</strong></h3>
<p>I’ve uploaded the complete project on GitHub. You can check it here:</p>
<p><strong>GitHub Repo:</strong><br /><a href="https://github.com/shubhamsinghbundela/Rest-API-Trello/">https://github.com/shubhamsinghbundela/Rest-API-Trello</a></p>
]]></content:encoded></item><item><title><![CDATA[Understanding Promise.any() (with Custom Implementation)]]></title><description><![CDATA[What is Promise.any()?
Promise.any() is used to run multiple promises in parallel and return the first successfully resolved promise.

Behavior:

If any one promise resolves, it immediately returns a ]]></description><link>https://blog.realdev.club/understanding-promise-any-with-custom-implementation</link><guid isPermaLink="true">https://blog.realdev.club/understanding-promise-any-with-custom-implementation</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[promises]]></category><category><![CDATA[asynchronous]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Frontend Development]]></category><dc:creator><![CDATA[Shubham Kumar Singh]]></dc:creator><pubDate>Thu, 16 Apr 2026 19:21:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/25543c5c-43a6-45c7-87b7-f00feb414bef.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3>What is Promise.any()?</h3>
<p><code>Promise.any()</code> is used to run multiple promises in parallel and <strong>return the first successfully resolved promise</strong>.</p>
<hr />
<h3>Behavior:</h3>
<ul>
<li><p>If <strong>any one promise resolves</strong>, it immediately returns a resolved promise with that value.</p>
</li>
<li><p>If <strong>all promises reject</strong>, it returns a rejected promise with an <code>AggregateError</code> containing all errors.</p>
</li>
</ul>
<hr />
<h3>Example</h3>
<pre><code class="language-javascript">const p1 = Promise.reject("Error 1");
const p2 = Promise.resolve(20);
const p3 = Promise.resolve(30);

Promise.any([p1, p2, p3])
  .then(result =&gt; {
    console.log(result); // 20
  })
  .catch(err =&gt; {
    console.error(err);
  });
</code></pre>
<hr />
<h3>Rejection Case</h3>
<pre><code class="language-javascript">const p1 = Promise.reject("Error 1");
const p2 = Promise.reject("Error 2");

Promise.any([p1, p2])
  .then(result =&gt; {
    console.log(result);
  })
  .catch(err =&gt; {
    console.error(err); 
    // AggregateError: All promises were rejected
    // err.errors =&gt; ["Error 1", "Error 2"]
  });
</code></pre>
<hr />
<h3>Key Concept</h3>
<p><code>Promise.any()</code> follows <strong>success-first behavior</strong> — one success is enough.</p>
<hr />
<h3>Custom Implementation of Promise.any()</h3>
<p>Let’s now understand how <code>Promise.any()</code> works internally by implementing it ourselves.</p>
<h3>Code :</h3>
<pre><code class="language-javascript">function promiseAny(promises) {
    return new Promise((resolve, reject) =&gt; {
        let errors = [];
        let rejectedCount = 0;

        if (promises.length === 0) {
            reject(new Error("Empty iterable"));
        }

        promises.forEach((element, index) =&gt; {
            Promise.resolve(element)
                .then((data) =&gt; {
                    resolve(data); // first success wins
                })
                .catch((err) =&gt; {
                    errors[index] = err;
                    rejectedCount++;

                    if (rejectedCount === promises.length) {
                        reject(new AggregateError(errors, "All promises were rejected"));
                    }
                });
        });
    });
}
</code></pre>
<hr />
<h3>🔗Before You Go Further</h3>
<p>If you haven’t read about <code>Promise.all()</code>, check it out here:</p>
<p><strong>Understanding Promise.all (with Custom Implementation)</strong><br /><a href="https://shubhamsinghbundela.hashnode.dev/understanding-promise-all-with-custom-implementation">https://shubhamsinghbundela.hashnode.dev/understanding-promise-all-with-custom-implementation</a></p>
]]></content:encoded></item><item><title><![CDATA[Building a Trello - Backend from Scratch]]></title><description><![CDATA[Core Features

Authentication System (Sign Up / Sign In)Every user starts here:

Users can sign up with credentials

Users can log in securely

Authentication is handled via tokens (JWT)


This ensure]]></description><link>https://blog.realdev.club/building-a-trello-backend-from-scratch</link><guid isPermaLink="true">https://blog.realdev.club/building-a-trello-backend-from-scratch</guid><category><![CDATA[Node.js]]></category><category><![CDATA[Express]]></category><category><![CDATA[backend]]></category><category><![CDATA[MongoDB]]></category><category><![CDATA[JWT]]></category><category><![CDATA[backend developments]]></category><dc:creator><![CDATA[Shubham Kumar Singh]]></dc:creator><pubDate>Thu, 16 Apr 2026 09:35:03 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/287d3d3b-cb4f-4c2e-9e90-85ba056b7d82.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3>Core Features</h3>
<ol>
<li><p><strong>Authentication System (Sign Up / Sign In)</strong><br />Every user starts here:</p>
<ul>
<li><p>Users can <strong>sign up</strong> with credentials</p>
</li>
<li><p>Users can <strong>log in securely</strong></p>
</li>
<li><p>Authentication is handled via tokens (JWT)</p>
</li>
</ul>
<p>This ensures every action is tied to an authenticated user.</p>
</li>
<li><p><strong>First-Time Onboarding (Organization Creation)</strong></p>
<p>After logging in for the first time:</p>
<ul>
<li><p>User can <strong>create an organization</strong></p>
</li>
<li><p>Organization includes:</p>
<ul>
<li><p>Name of organisation</p>
</li>
<li><p>Description</p>
</li>
</ul>
</li>
</ul>
<p>This makes the user the <strong>admin of that organization</strong></p>
</li>
<li><p><strong>Multi-User Organization System</strong></p>
<p>Admins can:</p>
<ul>
<li><p>add users to the organization based on username</p>
</li>
<li><p>Manage members</p>
</li>
<li><p>Users can:<br />Be part of <strong>multiple organizations</strong></p>
</li>
</ul>
</li>
<li><p><strong>Dashboard View</strong><br />Once logged in:</p>
<ul>
<li><p>The user first sees a <strong>list of all organizations</strong> they are part of.</p>
</li>
<li><p>Inside the selected organization, the user can see <strong>all boards related to that specific organization</strong></p>
</li>
</ul>
<p>If a user is part of 2 organizations → they will see select organisation</p>
</li>
<li><p><strong>Board-Level Task Management</strong><br />When user click any board inside a board, users can:</p>
<ul>
<li><p>Create tasks</p>
</li>
<li><p>Move tasks across stages:</p>
<ul>
<li><p><strong>Todo</strong></p>
</li>
<li><p><strong>In Progress</strong></p>
</li>
<li><p><strong>Done</strong></p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>Task Lifecycle</strong></p>
<p>Each task follows a simple lifecycle:</p>
<ol>
<li><p>Created in <strong>Todo</strong></p>
</li>
<li><p>Moved to <strong>In Progress</strong></p>
</li>
<li><p>Completed in <strong>Done</strong></p>
</li>
<li><p>Option to <strong>delete/remove</strong> task</p>
</li>
</ol>
</li>
</ol>
<hr />
<h3><strong>Step 1: Setup Express Server</strong></h3>
<p>First, I created a basic Express server:</p>
<pre><code class="language-javascript">const express = require("express");
const app = express();

app.use(express.json());

app.listen(3000, () =&gt; {
  console.log("Server is running on port 3000");
});
</code></pre>
<hr />
<h3><strong>Step 2: Setup MongoDB</strong></h3>
<ol>
<li><p>Go to <a href="https://cloud.mongodb.com/"><strong>https://cloud.mongodb.com/</strong></a></p>
</li>
<li><p>Create account &amp; login</p>
</li>
<li><p>Create a <strong>Cluster</strong></p>
</li>
<li><p>Go to <strong>Database Access</strong></p>
<ul>
<li>Create username &amp; password</li>
</ul>
</li>
<li><p>Go to <strong>Network Access</strong></p>
<ul>
<li>Add IP: <code>0.0.0.0/0</code> (for development)</li>
</ul>
</li>
<li><p>Click <strong>Connect</strong></p>
</li>
<li><p>Copy connection string</p>
</li>
</ol>
<p>Example:</p>
<pre><code class="language-plaintext">mongodb+srv://username:password@cluster.mongodb.net/dbname
</code></pre>
<hr />
<h3><strong>MongoDB Connection Code</strong></h3>
<p>Before connecting MongoDB, first we need to install <strong>mongoose</strong>.</p>
<p><strong>Step 1: Install Mongoose</strong></p>
<p>Run this command in your project:</p>
<pre><code class="language-javascript">npm install mongoose
</code></pre>
<hr />
<p><strong>Step 2: Connect to MongoDB</strong></p>
<p>Now write the connection code:</p>
<pre><code class="language-javascript">const mongoose = require("mongoose");

async function connectDB() {
    try {
        await mongoose.connect("mongodb+srv://&lt;username&gt;:&lt;password&gt;@todo.dtrpx.mongodb.net/todo");
        console.log("MongoDB connected");
    } catch (err) {
        console.error("Connection error:", err);
    }
}

connectDB();
</code></pre>
<p>Initially, I tried connecting to <strong>MongoDB (cloud)</strong> but faced a DNS issue</p>
<p><strong>Problem:</strong></p>
<p>MongoDB connection was failing due to DNS resolution.</p>
<p><strong>Fix:</strong></p>
<p>After watching a YouTube video: , I added this:</p>
<pre><code class="language-javascript">const dns = require("dns");
dns.setServers(["1.1.1.1", "8.8.8.8"]);
</code></pre>
<p>This fixed the issue and I was able to connect to cloud MongoDB.</p>
<hr />
<h3><strong>Step 3: Create Schema using Mongoose</strong></h3>
<p>Before writing any backend or frontend logic, I first designed how my data should look.</p>
<p>This step is very important.</p>
<p><strong>Learning:</strong><br />Always define your <strong>schema first</strong> before building APIs or UI.</p>
<p>Because:</p>
<ul>
<li><p>It gives clarity on what data you need</p>
</li>
<li><p>Helps structure your database properly</p>
</li>
<li><p>Makes backend logic easier to write</p>
</li>
</ul>
<pre><code class="language-javascript">const userSchema = new mongoose.Schema({
    username: 'String',
    password: "String"
})

const orgSchema = new mongoose.Schema({
    orgName: "String",
    description: "String",
    admin: mongoose.Types.ObjectId, 
    member: [mongoose.Types.ObjectId]
})

const boardSchema = new mongoose.Schema({
    boardName: "String",
    organisationId: mongoose.Types.ObjectId
})
const taskSchema = new mongoose.Schema({
    description: String,
    status: String, // Todo | In Progress | Done
    userId: mongoose.Types.ObjectId,
    boardId: mongoose.Types.ObjectId
})

const userModel = mongoose.model("users", userSchema);
const orgModel = mongoose.model("organisations", orgSchema);
const boardModel = mongoose.model("boards", boardSchema);
const taskModel = mongoose.model("tasks", taskSchema);

module.exports = {
    userModel,
    orgModel,
    boardModel
}
</code></pre>
<hr />
<h3><strong>Step 4: Signup Route</strong></h3>
<p>Before writing code, let’s understand the goal.</p>
<p>What we want to achieve:</p>
<ul>
<li><p>Allow a new user to <strong>register</strong></p>
</li>
<li><p>Store user details in database</p>
</li>
<li><p>Prevent duplicate users</p>
</li>
</ul>
<pre><code class="language-javascript">app.post('/signup', async (req,res)=&gt;{
    const username = req.body.username;
    const password = req.body.password;

    const userExists = await userModel.findOne({
        username: username
    })

    if(userExists){
        return res.status(403).json({
            message: "User already exists"
        })
    }

    const newUser = await userModel.create({
        username: username,
        password: password
    })

    res.status(200).json({
        id: newUser._id,
        message: "user get created"
    })
})
</code></pre>
<hr />
<h3><strong>Step 5: Signin + JWT Token</strong></h3>
<p>Before writing code, let’s understand the goal.</p>
<p>What we want to achieve:</p>
<ul>
<li><p>Verify user credentials</p>
</li>
<li><p>If valid → allow login</p>
</li>
<li><p>Generate a <strong>JWT token</strong> for authentication</p>
</li>
</ul>
<pre><code class="language-javascript">app.post("/signin", async (req, res) =&gt; {
  const { userName, password } = req.body;

  const userExist = await userModel.findOne({ userName });

  if (!userExist) {
    return res.status(404).json({
      message: "User not found",
    });
  }

  const token = jwt.sign(
    { userId: userExist.id },
    "shubham123"
  );

  res.json({ token });
});
</code></pre>
<hr />
<h3><strong>Step 6: Auth Middleware</strong></h3>
<p>This ensures only logged-in users can access routes.</p>
<pre><code class="language-javascript">function authMiddleware(req, res, next) {
    const token = req.headers.token;

    if (!token) {
        return res.status(401).json({
            message: "Token not provided"
        });
    }

    try {
        const decode = jwt.verify(token, "shubham123");
        req.userId = decode.userId;
        next();
    } catch (err) {
        return res.status(401).json({
            message: "Invalid or expired token"
        });
    }
}
</code></pre>
<hr />
<h3><strong>Step 7: Create Organisation</strong></h3>
<p><strong>Requirement</strong></p>
<p>Now that authentication is done, we need to allow users to:</p>
<ul>
<li><p>Create an organization after login</p>
</li>
<li><p>Automatically assign the creator as <strong>admin</strong></p>
</li>
<li><p>Ensure organization names are unique</p>
</li>
</ul>
<p>Only <strong>authenticated users</strong> should be able to create organizations.</p>
<p><strong>Implementation</strong></p>
<pre><code class="language-js">app.post("/create-organisation", authMiddleware, async (req,res)=&gt;{
    const orgName = req.body.orgName;
    const description = req.body.description;

    const orgExist = await orgModel.findOne({
        orgName: orgName
    })

    if(orgExist){
        return res.status(403).json({
            message: "Organisation already exists"
        })
    }

    const newOrg = await orgModel.create({
        orgName: orgName,
        description: description,
        admin: req.userId,
        member: []
    })
    
    res.status(200).json({
        orgId: newOrg.id
    })
})
</code></pre>
<hr />
<h3>Step 8: Add Member to Organization</h3>
<p><strong>Requirement</strong></p>
<p>Now that organizations are created, the next step is:</p>
<ul>
<li><p>Allow <strong>admin to add users</strong> to their organization</p>
</li>
<li><p>Users are added using their <strong>username</strong></p>
</li>
<li><p>Only <strong>existing users</strong> can be added</p>
</li>
<li><p>Prevent adding the same user twice</p>
</li>
</ul>
<p><strong>Implementation</strong></p>
<pre><code class="language-javascript">app.post('/add-member-to-organisation', authMiddleware, async (req,res)=&gt;{
    const newMember = req.body.member;

    const newMemberUser = await userModel.findOne({
        username: newMember
    })

    if(!newMemberUser){
        return res.status(404).json({
            message: "User does not exist"
        })
    }

    const orgDetails = await orgModel.findOne({
        admin: req.userId
    })

    if(!orgDetails){
        return res.status(404).json({
            message: "Organization not found"
        })
    }

    const memberExists = orgDetails.member.includes(newMemberUser._id);

    if(memberExists){
        return res.status(400).json({
            message: "Member already exists in organization"
        })
    }

    orgDetails.member.push(newMemberUser._id);

    await orgDetails.save()

    res.status(200).json({
        message: "Member added successfully"
    })
})
</code></pre>
<hr />
<h3>Step 9: Delete Member from Organization</h3>
<p><strong>Requirement</strong></p>
<p>Admins should also be able to:</p>
<ul>
<li>Remove users from the organization</li>
</ul>
<p><strong>Implementation</strong></p>
<pre><code class="language-javascript">app.delete('/delete-member-from-organisation', authMiddleware, async (req, res)=&gt;{
    const deleteUser = req.body.username;

    const deleteUserData = await userModel.findOne({
        username: deleteUser
    })

    if(!deleteUserData){
        return res.status(404).json({
            message: "User does not exist"
        })
    }

    const orgDetails = await orgModel.findOne({
        admin: req.userId
    })

    if(!orgDetails){
        return res.status(404).json({
            message: "Organization not found"
        })
    }

    orgDetails.member = orgDetails.member.filter(
        e =&gt; e.toString() !== deleteUserData._id.toString()
    );

    await orgDetails.save()

    res.status(200).json({
        message: "Member removed successfully"
    })
})
</code></pre>
<hr />
<h3>Step 10: Create Board</h3>
<p><strong>Requirement</strong></p>
<p>Now comes the core feature of Trello:</p>
<ul>
<li><p>Each organization should have <strong>boards</strong></p>
</li>
<li><p>Only authorized users (admin) can create boards</p>
</li>
<li><p>Each board belongs to a specific organization</p>
</li>
</ul>
<p><strong>Implementation</strong></p>
<pre><code class="language-js">app.post("/create-board", authMiddleware, async (req,res)=&gt;{
    const boardName = req.body.boardName;

    const orgDetails = await orgModel.findOne({
        admin: req.userId
    });

    if(!orgDetails){
        return res.status(404).json({
            message: "Organization not found"
        })
    }

    const newBoard = await boardModel.create({
        boardName,
        organisationId: orgDetails._id
    })

    res.status(200).json({
        boardId: newBoard._id,
        message: "Board created successfully"
    })
})
</code></pre>
<hr />
<h3>Step 11: Task Management (Create, Update, Delete)</h3>
<p>Now comes the most important part of any Trello-like application — <strong>Tasks</strong>.</p>
<p>This is where users actually track their work.</p>
<hr />
<p><strong>Requirement:</strong></p>
<p>We want to allow users to:</p>
<ul>
<li><p>Create tasks inside a board</p>
</li>
<li><p>Update task status (Todo → In Progress → Done)</p>
</li>
<li><p>Delete tasks when completed or no longer needed</p>
</li>
<li><p>Ensure tasks are tied to the <strong>logged-in user</strong></p>
</li>
</ul>
<p>This forms the core <strong>task lifecycle system</strong></p>
<hr />
<p><strong>Create Task</strong></p>
<p><strong>Logic :</strong></p>
<p>When a user creates a task:</p>
<ol>
<li><p>Take <code>description</code>, <code>status</code>, and <code>boardId</code></p>
</li>
<li><p>Validate:</p>
<ul>
<li><p>Board exists</p>
</li>
<li><p>Status is valid</p>
</li>
</ul>
</li>
<li><p>Check duplicate <strong>inside same board</strong></p>
</li>
<li><p>Create task linked to board</p>
</li>
</ol>
<hr />
<p><strong>Implementation</strong></p>
<pre><code class="language-javascript">app.post("/create-task", authMiddleware, async (req, res) =&gt; {
    const { description, status, boardId } = req.body;
 
    if (!description || !status || !boardId) {
        return res.status(400).json({
            message: "All fields are required"
        });
    }

    const allowedStatus = ["Todo", "In Progress", "Done"];
    if (!allowedStatus.includes(status)) {
        return res.status(400).json({
            message: "Invalid status"
        });
    }
 
    const board = await boardModel.findById(boardId);
    if (!board) {
        return res.status(404).json({
            message: "Board not found"
        });
    }

    const taskExists = await taskModel.findOne({
        userId: req.userId,
        description,
        boardId
    });

    if (taskExists) {
        return res.status(403).json({
            message: "Task already exists in this board"
        });
    }

    const newTask = await taskModel.create({
        userId: req.userId,
        description,
        status,
        boardId
    });

    res.status(200).json({
        message: "Task created successfully",
        taskId: newTask._id
    });
});
</code></pre>
<hr />
<p><strong>Update Task</strong></p>
<p><strong>Logic :</strong></p>
<p>When updating a task:</p>
<ol>
<li>Identify task using:</li>
</ol>
<ul>
<li>taskId</li>
</ul>
<ol>
<li><p>If task exists:</p>
<ul>
<li><p>Update status</p>
</li>
<li><p>Save changes</p>
</li>
</ul>
</li>
</ol>
<p>This enables moving tasks across stages</p>
<hr />
<p><strong>Implementation</strong></p>
<pre><code class="language-javascript">app.put("/update-task", authMiddleware, async (req, res) =&gt; {
    const { taskId, status } = req.body;

    const allowedStatus = ["Todo", "In Progress", "Done"];
    if (!allowedStatus.includes(status)) {
        return res.status(400).json({
            message: "Invalid status"
        });
    }

    const task = await taskModel.findOne({
        _id: taskId,
        userId: req.userId
    });

    if (!task) {
        return res.status(404).json({
            message: "Task not found"
        });
    }

    task.status = status;
    await task.save();

    res.status(200).json({
        message: "Task updated successfully"
    });
});
</code></pre>
<hr />
<p><strong>Delete Task</strong></p>
<p><strong>Logic :</strong></p>
<p>When deleting a task:</p>
<ol>
<li><p>Find task using:</p>
<ul>
<li>taskId</li>
</ul>
</li>
<li><p>If exists → delete it from database</p>
</li>
</ol>
<hr />
<p><strong>Implementation</strong></p>
<pre><code class="language-javascript">app.delete("/delete-task", authMiddleware, async (req, res) =&gt; {
    const { taskId } = req.body;

    const task = await taskModel.findOne({
        _id: taskId,
        userId: req.userId
    });

    if (!task) {
        return res.status(404).json({
            message: "Task not found"
        });
    }

    await taskModel.deleteOne({ _id: taskId });

    res.status(200).json({
        message: "Task deleted successfully"
    });
});
</code></pre>
<hr />
<h3>Final Thoughts</h3>
<p>If you're learning backend development, I highly recommend building something like this — it teaches you far more than just theory.</p>
<hr />
<h3><strong>Complete Source Code</strong></h3>
<p>I’ve uploaded the complete project on GitHub. You can check it here:</p>
<p><strong>GitHub Repo:</strong><br /><a href="https://github.com/shubhamsinghbundela/Trello">https://github.com/shubhamsinghbundela/Trello</a></p>
]]></content:encoded></item><item><title><![CDATA[Understanding Promise.all (with Custom Implementation)]]></title><description><![CDATA[What is Promise.all()?
Promise.all() is used to run multiple promises in parallel and wait until all of them complete.
Behavior:

If all promises resolve, it returns a resolved promise with an array o]]></description><link>https://blog.realdev.club/understanding-promise-all-with-custom-implementation</link><guid isPermaLink="true">https://blog.realdev.club/understanding-promise-all-with-custom-implementation</guid><category><![CDATA[promise.all]]></category><category><![CDATA[asynchronous]]></category><category><![CDATA[asynchronous JavaScript]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[promises]]></category><dc:creator><![CDATA[Shubham Kumar Singh]]></dc:creator><pubDate>Tue, 14 Apr 2026 22:23:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/692a0d34-1707-4e2d-95d4-949502669c14.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3>What is <code>Promise.all()</code>?</h3>
<p><code>Promise.all()</code> is used to run multiple promises in parallel and wait until all of them complete.</p>
<p><strong>Behavior:</strong></p>
<ul>
<li><p>If <strong>all promises resolve</strong>, it returns a resolved promise with an array of results.</p>
</li>
<li><p>If <strong>any one promise rejects</strong>, it immediately returns a rejected promise with that error.</p>
</li>
<li><p>It <strong>fails fast</strong> — meaning it doesn’t wait for other promises once one fails.</p>
</li>
</ul>
<p><strong>Example</strong></p>
<pre><code class="language-javascript">const p1 = Promise.resolve(10);
const p2 = Promise.resolve(20);
const p3 = Promise.resolve(30);

Promise.all([p1, p2, p3])
  .then(result =&gt; {
    console.log(result); // [10, 20, 30]
  })
  .catch(err =&gt; {
    console.error(err);
  });
</code></pre>
<hr />
<p><strong>Rejection Case</strong></p>
<pre><code class="language-javascript">const p1 = Promise.resolve(10);
const p2 = Promise.reject("Error in p2");
const p3 = Promise.resolve(30);

Promise.all([p1, p2, p3])
  .then(result =&gt; {
    console.log(result);
  })
  .catch(err =&gt; {
    console.error(err); // "Error in p2"
  });
</code></pre>
<hr />
<p><strong>Key Concept</strong></p>
<blockquote>
<p><code>Promise.all()</code> follows <strong>fail-fast behavior</strong> — one failure stops everything.</p>
</blockquote>
<hr />
<h3>Custom Implementation of <code>Promise.all()</code></h3>
<p>Let’s now understand how <code>Promise.all()</code> works internally by implementing it ourselves.</p>
<hr />
<h3><strong>Code</strong></h3>
<pre><code class="language-javascript">function promiseAll(promises) {
    return new Promise((resolve, reject) =&gt; {
        let arr = [];
        let completed = 0;

        if (promises.length === 0) {
            resolve([]);
        }

        promises.forEach((element, index) =&gt; {
            Promise.resolve(element)
                .then((data) =&gt; {
                    arr[index] = data;
                    completed++;

                    if (completed === promises.length) {
                        resolve(arr);
                    }
                })
                .catch(reject);
        });
    });
}
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Binary Seach ]]></title><description><![CDATA[Imagine you’re searching for a word in a dictionary.You don’t start from page 1, right?You open somewhere in the middle.

If your word comes before → go left

If it comes after → go right


This is ex]]></description><link>https://blog.realdev.club/binary-seach</link><guid isPermaLink="true">https://blog.realdev.club/binary-seach</guid><category><![CDATA[Binary Search Algorithm]]></category><category><![CDATA[data structures]]></category><category><![CDATA[algorithms]]></category><category><![CDATA[DSA]]></category><category><![CDATA[Problem Solving]]></category><category><![CDATA[Software Engineering]]></category><dc:creator><![CDATA[Shubham Kumar Singh]]></dc:creator><pubDate>Mon, 13 Apr 2026 13:26:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/db411983-98f0-40f5-9ef2-48ee10399ce6.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Imagine you’re searching for a word in a dictionary.<br />You don’t start from page 1, right?<br />You open somewhere in the middle.</p>
<ul>
<li><p>If your word comes before → go left</p>
</li>
<li><p>If it comes after → go right</p>
</li>
</ul>
<p>This is exactly how <strong>Binary Search</strong> works.</p>
<hr />
<h3>Core Idea of Binary Search</h3>
<p><strong>Instead of checking every element (like linear search), we:</strong></p>
<ul>
<li><p>Pick the <strong>middle element</strong></p>
</li>
<li><p>Compare with target</p>
</li>
<li><p>Eliminate half of the search space</p>
</li>
</ul>
<hr />
<h3>Basic Binary Search Formula</h3>
<pre><code class="language-js">mid = (Left + right) / 2
</code></pre>
<p><strong>Decision Making</strong></p>
<p>Once we calculate <code>mid</code>, we compare:</p>
<ul>
<li><p>If <code>arr[mid] == target</code><br />→ Target found</p>
</li>
<li><p>If <code>arr[mid] &gt; target</code><br />→ Target lies on the <strong>left side</strong><br />→ Move:</p>
<pre><code class="language-plaintext">right = mid - 1;
</code></pre>
</li>
<li><p>If <code>arr[mid] &lt; target</code>  </p>
<p>→ Target lies on the <strong>right side</strong>  </p>
<p>→ Move:</p>
<pre><code class="language-plaintext">left = mid + 1;
</code></pre>
</li>
</ul>
<hr />
<h3>Problem Statement:</h3>
<p>Given a <strong>sorted array</strong> of size <code>n</code> and an integer <code>target</code>, determine whether the target exists in the array.</p>
<p>If the target is present, return its <strong>index (0-based)</strong>.<br />If the target is not present, return <strong>-1</strong>.</p>
<hr />
<h3>S<strong>tep-by-Step Dry Run</strong></h3>
<p>Let’s say you have an array:</p>
<pre><code class="language-plaintext">[3, 5, 2, 1, 4]
 0  1  2  3  4
</code></pre>
<p>First rule:</p>
<blockquote>
<p>Binary Search only works on a <strong>sorted array</strong></p>
</blockquote>
<p>So we sort it:</p>
<pre><code class="language-plaintext">[1, 2, 3, 4, 5]
 0  1  2  3  4
</code></pre>
<p><strong>Now Search Target = 4</strong></p>
<p><strong>Step 1:</strong></p>
<pre><code class="language-plaintext">[1, 2, 3, 4, 5]
 0  1  2  3  4
 l     m     r
           
</code></pre>
<ul>
<li><p>mid = 2 → value = 3</p>
</li>
<li><p>4 &gt; 3 → ignore left half</p>
</li>
<li><p>Move right: l = 3</p>
</li>
</ul>
<p><strong>Step 2:</strong></p>
<pre><code class="language-plaintext">[1, 2, 3, 4, 5]
 0  1  2  3  4
          m  r
          l
</code></pre>
<ul>
<li><p>mid = 3 → value = 4</p>
</li>
<li><p>Found!</p>
</li>
</ul>
<hr />
<h3>Code</h3>
<pre><code class="language-javascript">int l = 0, r = n - 1;

while (l &lt;= r) {
    int mid = (l + r) / 2;

    if (arr[mid] == target) {
        return mid; // found
    } else if (arr[mid] &lt; target) {
        l = mid + 1;
    } else {
        r = mid - 1;
    }
}

return -1; // not found
</code></pre>
<hr />
<h3>Complexity</h3>
<ul>
<li><p><strong>Time Complexity:</strong> <code>O(log n)</code></p>
</li>
<li><p><strong>Space Complexity:</strong> <code>O(1)</code></p>
</li>
</ul>
<hr />
<h3>Where This Pattern is Used</h3>
<p>This is where things get interesting 👇</p>
<p>The same binary search logic helps solve multiple problems:</p>
<ol>
<li><p><strong>First &amp; Last Occurrence</strong></p>
</li>
<li><p><strong>Frequency of an Element</strong></p>
</li>
<li><p><strong>Count of Elements Greater Than X</strong></p>
</li>
<li><p><strong>Count of Elements Between X and Y</strong></p>
</li>
</ol>
<blockquote>
<p>Once you understand pointer movement, all these problems become easy.</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[How Everything is an Object in JavaScript (Prototype Explained)]]></title><description><![CDATA[If you’ve been learning JavaScript, you’ve probably heard:

“Everything in JavaScript is an object”


1. The Real Core Concept: Prototype Chain
Let’s start with a simple example:


const user1 = {
   ]]></description><link>https://blog.realdev.club/how-everything-is-an-object-in-javascript-prototype-explained</link><guid isPermaLink="true">https://blog.realdev.club/how-everything-is-an-object-in-javascript-prototype-explained</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[prototype]]></category><category><![CDATA[Object Oriented Programming]]></category><category><![CDATA[Objects]]></category><dc:creator><![CDATA[Shubham Kumar Singh]]></dc:creator><pubDate>Fri, 10 Apr 2026 22:00:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/dc34ef45-a750-43be-b827-779760408d3f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you’ve been learning JavaScript, you’ve probably heard:</p>
<blockquote>
<p>“Everything in JavaScript is an object”</p>
</blockquote>
<hr />
<h3>1. The Real Core Concept: Prototype Chain</h3>
<p>Let’s start with a simple example:</p>
<img src="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/0733481b-1aa1-4cb1-8248-f5fe4d0c6956.png" alt="" />

<pre><code class="language-javascript">const user1 = {
    name: 'Shubham',
}

const user2 = {
    __proto__: user1
}

console.log(user2) // {}
console.log(user2.name) // 'Shubham'
</code></pre>
<p><strong>What’s happening behind the scenes?</strong></p>
<pre><code class="language-js">user2
  ↓ (__proto__)
user1
  ↓ (__proto__)
Object.prototype
  ↓
null
</code></pre>
<p><strong>Flow when accessing</strong> <code>user2.name</code></p>
<pre><code class="language-plaintext">Step 1: Check user2 → not found
Step 2: Go to user1 → found ("shubham")
Step 3: Stop
</code></pre>
<p>This is called <strong>Prototype Chain</strong></p>
<hr />
<h3>Key Insight</h3>
<blockquote>
<p>Objects in JavaScript don’t inherit by copying—they inherit by linking i.e. objects are linked using the prototype (<code>__proto__</code>). When a property is accessed on an object, JavaScript first looks for it in the object itself. If not found, it automatically looks up the prototype chain until it finds the property or reaches <code>null</code>.</p>
</blockquote>
<hr />
<h3>2. Arrays Are Also Objects</h3>
<pre><code class="language-javascript">let arr = [1, 2, 3];
arr.push(4);
</code></pre>
<p>Looks like a special data structure, right?</p>
<p>But internally</p>
<pre><code class="language-js">arr (Array instance)
  ↓ (__proto__)
Array.prototype
  ↓ (__proto__)
Object.prototype
  ↓
null
</code></pre>
<p><strong>What this means:</strong></p>
<ul>
<li><p><code>arr.push()</code> → comes from <code>Array.prototype</code></p>
</li>
<li><p><code>arr.toString()</code> → comes from <code>Object.prototype</code></p>
</li>
</ul>
<p>So arrays is also an <strong>objects.</strong></p>
<hr />
<h3>3. Strings: The Interesting Case</h3>
<pre><code class="language-javascript">let name = "shubham";
console.log(name.toUpperCase());
</code></pre>
<p><strong>But string is primitive… right?</strong></p>
<p>YES  </p>
<p>But JS does <strong>auto-boxing</strong></p>
<p><strong>What actually happens behind the scenes:</strong></p>
<pre><code class="language-javascript">"shubham" (primitive)
   ↓ (auto-boxing)
new String("shubham")
   ↓
String.prototype
   ↓
Object.prototype
   ↓
  null
</code></pre>
<pre><code class="language-plaintext">Step 1: "shubham".toUpperCase()
Step 2: JS converts → String object
Step 3: Finds method in String.prototype
</code></pre>
<hr />
<h3>Key Insight</h3>
<blockquote>
<p>Primitives are not objects, but JavaScript makes them behave like objects when needed.</p>
</blockquote>
<hr />
<h3>4. Function Example</h3>
<pre><code class="language-javascript">function test() {}

test.myName = "shubham"; //Only objects can store key-value pairs → so test is an object

console.log(test.myName); // shubham 

console.log(test.call);   // function
console.log(test.apply);  // function
console.log(test.bind);   // function

console.log(test.__proto__ === Function.prototype); // true
console.log(Function.prototype.__proto__ === Object.prototype); // true
</code></pre>
<pre><code class="language-js">test (function object)
  ↓ (__proto__)
Function.prototype
  ↓ (__proto__)
Object.prototype
  ↓
null
</code></pre>
<hr />
<h3>Final Conclusion</h3>
<ul>
<li><p>Objects use <strong>prototype chaining</strong></p>
</li>
<li><p>Arrays &amp; functions are objects</p>
</li>
<li><p>Primitives can behave like objects via <strong>auto-boxing</strong></p>
</li>
</ul>
<blockquote>
<p>JavaScript is not class-based, it is <strong>prototype-based</strong> because objects inherit properties and methods from other objects through the prototype chain.</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Promise.resolve vs new Promise: When Should You Use What?]]></title><description><![CDATA[Most developers get stuck comparing Promise.resolve and new Promise, trying to understand the difference.

Why This Confusion Happens
In simple cases, both approaches behave the same:
Promise.resolve(]]></description><link>https://blog.realdev.club/promise-resolve-vs-new-promise-when-should-you-use-what</link><guid isPermaLink="true">https://blog.realdev.club/promise-resolve-vs-new-promise-when-should-you-use-what</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[promises]]></category><category><![CDATA[asynchronous]]></category><dc:creator><![CDATA[Shubham Kumar Singh]]></dc:creator><pubDate>Fri, 10 Apr 2026 21:00:19 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/b52ca627-ae2f-4eaa-ad9f-e0917e1cb2e9.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Most developers get stuck comparing <code>Promise.resolve</code> and <code>new Promise</code>, trying to understand the <em>difference</em>.</p>
<hr />
<h3>Why This Confusion Happens</h3>
<p>In simple cases, both approaches behave the same:</p>
<pre><code class="language-javascript">Promise.resolve("Hello");

new Promise((resolve) =&gt; resolve("Hello"));
</code></pre>
<ul>
<li><p>Both return a resolved Promise</p>
</li>
<li><p>Both can be used with <code>.then()</code> or <code>await</code></p>
</li>
</ul>
<hr />
<h3>Rule of Thumb</h3>
<ul>
<li><p>Use <code>Promise.resolve</code> when you already have the result</p>
</li>
<li><p>Use <code>new Promise</code> when you need to create async behavior</p>
</li>
</ul>
<p>That’s it. Now let’s expand with real examples.</p>
<hr />
<h3>When to Use <code>Promise.resolve</code></h3>
<p><strong>1. When You Already Have a Value</strong></p>
<pre><code class="language-javascript">function getData() {
  return Promise.resolve(42);
}
</code></pre>
<ul>
<li><p>You’re not doing anything async</p>
</li>
<li><p>Just wrapping a value in a Promise</p>
</li>
</ul>
<hr />
<h3>When to Use <code>new Promise</code></h3>
<p><strong>1. When You Need Async Control (Most Important)</strong></p>
<pre><code class="language-javascript">function delay(ms) {
  return new Promise((resolve) =&gt; {
    setTimeout(resolve, ms);
  });
}
</code></pre>
<p>You decide <em>when</em> to resolve</p>
<hr />
<h3>Quick Comparison (Use Case Based)</h3>
<table>
<thead>
<tr>
<th>Situation</th>
<th>What to Use</th>
</tr>
</thead>
<tbody><tr>
<td>Already have value</td>
<td><code>Promise.resolve</code></td>
</tr>
<tr>
<td>Just wrapping result</td>
<td><code>Promise.resolve</code></td>
</tr>
<tr>
<td>Need delay / timer</td>
<td><code>new Promise</code></td>
</tr>
<tr>
<td>API / callback conversion</td>
<td><code>new Promise</code></td>
</tr>
<tr>
<td>Need manual resolve/reject</td>
<td><code>new Promise</code></td>
</tr>
</tbody></table>
]]></content:encoded></item><item><title><![CDATA[Cyclic Sort]]></title><description><![CDATA[Problem Idea
You are given an array of size n containing numbers from 1 to n.
Your task:Place every number at its correct index

Core Idea (Think Like This)
“If the current number is not at its correc]]></description><link>https://blog.realdev.club/cyclic-sort</link><guid isPermaLink="true">https://blog.realdev.club/cyclic-sort</guid><category><![CDATA[DSA]]></category><category><![CDATA[cyclic-sort]]></category><category><![CDATA[datastructure]]></category><category><![CDATA[patterns]]></category><category><![CDATA[algorithms]]></category><category><![CDATA[software development]]></category><dc:creator><![CDATA[Shubham Kumar Singh]]></dc:creator><pubDate>Thu, 09 Apr 2026 10:51:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/8d2854e7-e14a-44a9-80c9-333e143afa8d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3>Problem Idea</h3>
<p>You are given an array of size <code>n</code> containing numbers from <code>1 to n</code>.</p>
<p>Your task:<br />Place every number at its <strong>correct index</strong></p>
<hr />
<h3>Core Idea (Think Like This)</h3>
<p>“If the current number is not at its correct position, swap it.”</p>
<p>“Keep doing this until every element is placed correctly.”</p>
<hr />
<h3>Step-by-Step Dry Run</h3>
<p><strong>Input:</strong></p>
<pre><code class="language-plaintext">[3, 5, 2, 1, 4]
</code></pre>
<hr />
<h3><strong>Step 0: First Think</strong></h3>
<pre><code class="language-plaintext">Value → Index
1 → 0
2 → 1
3 → 2
4 → 3
5 → 4
</code></pre>
<p>Formula:</p>
<pre><code class="language-plaintext">correct index = value - 1
</code></pre>
<hr />
<p><strong>Start from index 0</strong></p>
<pre><code class="language-plaintext">[3, 5, 2, 1, 4]
 0  1  2  3  4
 ↑
 i
</code></pre>
<p><strong>Current value is 3</strong></p>
<pre><code class="language-plaintext">correct index of 3 = 2
</code></pre>
<p><strong>First Swap</strong></p>
<pre><code class="language-plaintext">swap(arr[0], arr[2])
</code></pre>
<pre><code class="language-plaintext">[2, 5, 3, 1, 4]
 0  1  2  3  4
 ↑
 i 
</code></pre>
<p><strong>Again Check</strong></p>
<pre><code class="language-plaintext">arr[i] = 2
correct index of 2 = 1
</code></pre>
<p><strong>Second Swap</strong></p>
<pre><code class="language-plaintext">swap(arr[0], arr[1])
</code></pre>
<pre><code class="language-plaintext">[5, 2, 3, 1, 4]
 0  1  2  3  4
 ↑
 i
</code></pre>
<p><strong>Again Check</strong></p>
<pre><code class="language-plaintext">arr[i] = 5
correct index of 5 = 4
</code></pre>
<p><strong>Third Swap</strong></p>
<pre><code class="language-plaintext">swap(arr[0], arr[4])
</code></pre>
<pre><code class="language-plaintext">[4, 2, 3, 1, 5]
 0  1  2  3  4
 ↑
 i
</code></pre>
<p><strong>Again Check</strong></p>
<pre><code class="language-plaintext">arr[i] = 4
correct index of 4 = 3
</code></pre>
<p><strong>Third Swap</strong></p>
<pre><code class="language-plaintext">swap(arr[0], arr[3])
</code></pre>
<pre><code class="language-plaintext">[1, 2, 3, 4, 5]
 0  1  2  3  4
 ↑
 i 
</code></pre>
<p><strong>Now Check</strong></p>
<pre><code class="language-plaintext">arr[i] = 1
correct index of 1 = 0
</code></pre>
<p><strong>Correct position</strong></p>
<p><strong>Now move forward:</strong></p>
<pre><code class="language-plaintext">i++
</code></pre>
<p><strong>Continue</strong></p>
<pre><code class="language-plaintext">[1, 2, 3, 4, 5]
    ↑
    i = 1
</code></pre>
<p><code>2</code> is correct → move</p>
<p><code>3</code> is correct → move</p>
<p><code>4</code> is correct → move</p>
<p><code>5</code> is correct → move</p>
<hr />
<h3>Final Answer</h3>
<pre><code class="language-plaintext">[1, 2, 3, 4, 5]
</code></pre>
<hr />
<h3>What Just Happened?</h3>
<p>At each step:</p>
<pre><code class="language-plaintext">Check → Is number at correct index?
</code></pre>
<ul>
<li><p>No → swap</p>
</li>
<li><p>Yes → move ahead</p>
</li>
</ul>
<hr />
<h3>Most Important Rule</h3>
<pre><code class="language-plaintext">After swap → DO NOT move i
</code></pre>
<p>Why?</p>
<ul>
<li><p>Because new element came to index <code>i</code></p>
</li>
<li><p>It might also be wrong</p>
</li>
</ul>
<hr />
<h3>Code</h3>
<pre><code class="language-javascript">int i = 0;
while(i &lt; n){
    int correct = arr[i] - 1;
    if(arr[i] != arr[correct]){
        swap(arr[i], arr[correct]);
    } else {
        i++;
    }
}
</code></pre>
<hr />
<h3>Complexity</h3>
<ul>
<li><p>Time: <strong>O(n)</strong></p>
</li>
<li><p>Space: <strong>O(1)</strong></p>
</li>
</ul>
<hr />
<h3>Where This Pattern is Used</h3>
<p>This is where things get interesting 👇</p>
<p>Same logic helps solve multiple problems:</p>
<hr />
<h3>1. Find All Duplicates in Array</h3>
<pre><code class="language-javascript">int i = 0;
while (i &lt; n) {
    int correct = arr[i] - 1;
    if (arr[i] != arr[correct]) {
        swap(arr[i], arr[correct]);
    } else {
        i++;
    }
}
vector&lt;int&gt; nums;
for (int i = 0; i &lt; n; i++) {
   if(arr[i] != i + 1){
       nums.push_back(arr[i]);
   }
}
</code></pre>
<hr />
<h3>2. Find All Missing Numbers</h3>
<pre><code class="language-javascript">int i = 0;
while (i &lt; n) {
    int correct = arr[i] - 1;
    if (arr[i] != arr[correct]) {
        swap(arr[i], arr[correct]);
    } else {
        i++;
    }
}
vector&lt;int&gt; nums;
for (int i = 0; i &lt; n; i++) {
   if(arr[i] != i + 1){
       nums.push_back(i + 1);
   }
}
</code></pre>
<hr />
<h3>3. Missing Number</h3>
<pre><code class="language-javascript">int i = 0;
int n = nums.size();

while (i &lt; n) {
    if (nums[i] &lt; n &amp;&amp; nums[i] != nums[nums[i]]) {
        swap(nums[i], nums[nums[i]]);
    } else {
        i++;
    }
}

for (int i = 0; i &lt; n; i++) {
    if (nums[i] != i) {
        return i;
    }
}
return n;
</code></pre>
<hr />
<h3>4. First Missing Positive</h3>
<pre><code class="language-javascript">int i = 0;
while (i &lt; n) {
    if (arr[i] &gt; 0 &amp;&amp; arr[i] &lt;= n) {
        int correct = arr[i] - 1;

        if (arr[i] != arr[correct]) {
            swap(arr[i], arr[correct]);
            continue;
        }
    }
    i++;
}

for (int i = 0; i &lt; n; i++) {
    if (arr[i] != i + 1) {
        return i + 1;
    }
}
return n + 1;
</code></pre>
<hr />
<h3>Final Learning</h3>
<p>At first, it looks like just swapping…</p>
<p>But actually:</p>
<p>It’s about <strong>placing elements at their correct index</strong></p>
<p>Once you understand this:</p>
<p>You unlock 5–6 important interview problems</p>
]]></content:encoded></item><item><title><![CDATA[Building a Todo App using Express.js + MongoDB]]></title><description><![CDATA[Last time, I wrote about Understanding HTTP Requests, Responses, and Methods using Node.js.
This time, I took it one step further…
I built a Todo Application using Express.js and MongoDB

What I Built]]></description><link>https://blog.realdev.club/building-a-todo-app-using-express-js-mongodb</link><guid isPermaLink="true">https://blog.realdev.club/building-a-todo-app-using-express-js-mongodb</guid><category><![CDATA[Node.js]]></category><category><![CDATA[Express]]></category><category><![CDATA[MongoDB]]></category><category><![CDATA[mongoose]]></category><category><![CDATA[JWT]]></category><category><![CDATA[backend]]></category><category><![CDATA[REST API]]></category><category><![CDATA[todoapp]]></category><category><![CDATA[full stack]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[api]]></category><category><![CDATA[authentication]]></category><category><![CDATA[crud]]></category><category><![CDATA[MongoDB Atlas]]></category><dc:creator><![CDATA[Shubham Kumar Singh]]></dc:creator><pubDate>Wed, 08 Apr 2026 13:26:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/1225fa54-8bec-4817-83ed-74b982c84c5e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Last time, I wrote about <a href="https://shubhamsinghbundela.hashnode.dev/understanding-http-requests-responses-and-http-methods-using-node-js"><strong>Understanding HTTP Requests, Responses, and Methods using Node.js</strong></a>.</p>
<p>This time, I took it one step further…</p>
<p>I built a <strong>Todo Application using Express.js and MongoDB</strong></p>
<hr />
<h3><strong>What I Built</strong></h3>
<ul>
<li><p>User Signup &amp; Signin</p>
</li>
<li><p>Create Todo</p>
</li>
<li><p>Get Todos of logged-in user</p>
</li>
<li><p>Protected routes using middleware</p>
</li>
<li><p>MongoDB for database</p>
</li>
</ul>
<hr />
<h3>Step 1: Setup Express Server</h3>
<p>First, I created a basic Express server:</p>
<pre><code class="language-javascript">const express = require("express");
const app = express();

app.use(express.json());

app.listen(3000, () =&gt; {
  console.log("Server is running on port 3000");
});
</code></pre>
<hr />
<h3><strong>Step 2: Setup MongoDB</strong></h3>
<ol>
<li><p>Go to <a href="https://cloud.mongodb.com/"><strong>https://cloud.mongodb.com/</strong></a></p>
</li>
<li><p>Create account &amp; login</p>
</li>
<li><p>Create a <strong>Cluster</strong></p>
</li>
<li><p>Go to <strong>Database Access</strong></p>
<ul>
<li>Create username &amp; password</li>
</ul>
</li>
<li><p>Go to <strong>Network Access</strong></p>
<ul>
<li>Add IP: <code>0.0.0.0/0</code> (for development)</li>
</ul>
</li>
<li><p>Click <strong>Connect</strong></p>
</li>
<li><p>Copy connection string</p>
</li>
</ol>
<p>Example:</p>
<pre><code class="language-plaintext">mongodb+srv://username:password@cluster.mongodb.net/dbname
</code></pre>
<hr />
<h3>MongoDB Connection Code</h3>
<p>Before connecting MongoDB, first we need to install <strong>mongoose</strong>.</p>
<p><strong>Step 1: Install Mongoose</strong></p>
<p>Run this command in your project:</p>
<pre><code class="language-javascript">npm install mongoose
</code></pre>
<hr />
<p><strong>Step 2: Connect to MongoDB</strong></p>
<p>Now write the connection code:</p>
<pre><code class="language-javascript">const mongoose = require("mongoose");

async function connectDB() {
    try {
        await mongoose.connect("mongodb+srv://&lt;username&gt;:&lt;password&gt;@todo.dtrpx.mongodb.net/todo");
        console.log("MongoDB connected");
    } catch (err) {
        console.error("Connection error:", err);
    }
}

connectDB();
</code></pre>
<p>Initially, I tried connecting to <strong>MongoDB (cloud)</strong> but faced a DNS issue</p>
<p><strong>Problem:</strong></p>
<p>MongoDB connection was failing due to DNS resolution.</p>
<p><strong>Fix:</strong></p>
<p>After watching a YouTube video: , I added this:</p>
<pre><code class="language-javascript">const dns = require("dns");
dns.setServers(["1.1.1.1", "8.8.8.8"]);
</code></pre>
<p>This fixed the issue and I was able to connect to cloud MongoDB.</p>
<hr />
<h3>Step 3: Create Schema using Mongoose</h3>
<p>Before writing any backend or frontend logic, I first designed how my data should look.</p>
<p>This step is very important.</p>
<p><strong>Learning:</strong><br />Always define your <strong>schema first</strong> before building APIs or UI.</p>
<p>Because:</p>
<ul>
<li><p>It gives clarity on what data you need</p>
</li>
<li><p>Helps structure your database properly</p>
</li>
<li><p>Makes backend logic easier to write</p>
</li>
</ul>
<pre><code class="language-javascript">const userSchema = new mongoose.Schema({
    userName: String,
    password: String,
    firstName: String,
    lastName: String
})

const todoSchema = new mongoose.Schema({
    description: String,
    userId: mongoose.Types.ObjectId
})

const userModel = mongoose.model("users", userSchema);
const todoModel = mongoose.model("todos", todoSchema);

module.exports = {
    userModel: userModel,
    todoModel: todoModel
}
</code></pre>
<hr />
<h3>Step 4: Signup Route</h3>
<p>Before writing code, let’s understand the goal.</p>
<p>What we want to achieve:</p>
<ul>
<li><p>Allow a new user to <strong>register</strong></p>
</li>
<li><p>Store user details in database</p>
</li>
<li><p>Prevent duplicate users</p>
</li>
</ul>
<pre><code class="language-javascript">app.post("/signup", async (req, res) =&gt; {
  const { userName, password, firstName, lastName } = req.body;

  // checking user exist or not
  const existingUser = await userModel.findOne({
    userName,
    password,
  });

  if (existingUser) {
    return res.status(403).json({
      message: "User already exists",
    });
  }

  // added new user in userModel collection
  const newUser = await userModel.create({
    userName,
    password,
    firstName,
    lastName,
  });

  res.json({
    id: newUser._id,
  });
});
</code></pre>
<hr />
<h3>Step 5: Signin + JWT Token</h3>
<p>Before writing code, let’s understand the goal.</p>
<p>What we want to achieve:</p>
<ul>
<li><p>Verify user credentials</p>
</li>
<li><p>If valid → allow login</p>
</li>
<li><p>Generate a <strong>JWT token</strong> for authentication</p>
</li>
</ul>
<pre><code class="language-javascript">app.post("/signin", async (req, res) =&gt; {
  const { userName, password } = req.body;

  const userExist = await userModel.findOne({ userName });

  if (!userExist) {
    return res.status(404).json({
      message: "User not found",
    });
  }

  const token = jwt.sign(
    { userId: userExist.id },
    "shubham123"
  );

  res.json({ token });
});
</code></pre>
<hr />
<h3>Step 6: Auth Middleware</h3>
<p>This ensures only logged-in users can access routes.</p>
<pre><code class="language-javascript">function authMiddleware(req, res, next) {
    const token = req.headers.token;

    const decode = jwt.verify(token, "shubham123");

    req.userId = decode.userId;

    next();
}
</code></pre>
<hr />
<h3>Step 7: Create Todo</h3>
<p>Before writing the code, let’s understand</p>
<p><strong>What are we trying to achieve?</strong></p>
<p>We want:<br />1. Only <strong>logged-in users</strong> should be able to create a todo i.e. Each todo should be linked to the <strong>specific user who created it</strong></p>
<p>When user tries to create a todo, first <strong>authentication happens</strong> using <code>authMiddleware</code>.</p>
<ul>
<li><p>It verifies the token</p>
</li>
<li><p>Extracts <code>userId</code></p>
</li>
<li><p>Attaches it to <code>req.userId</code></p>
</li>
</ul>
<p>Then we use this <code>userId</code> to link the todo with the logged-in user.</p>
<p>So each todo belongs to a specific user, and later we can fetch user-specific todos.</p>
<pre><code class="language-javascript">app.post("/todo", authMiddleware, async (req, res) =&gt; {
  const newTodo = await todoModel.create({
    description: req.body.description,
    userId: req.userId
  });

  res.json({
    id: newTodo._id
  });
});
</code></pre>
<hr />
<h3>Step 8: Get All Todos</h3>
<pre><code class="language-javascript">app.get("/todos", authMiddleware, async (req, res) =&gt; {
  const allTodos = await todoModel.find({
    userId: req.userId
  });

  res.json({ allTodos });
});
</code></pre>
<hr />
<h3>Testing using Postman</h3>
<p>I used <strong>Postman</strong> to test APIs.</p>
<p><strong>Signup</strong></p>
<ul>
<li><p>Method: <code>POST</code></p>
</li>
<li><p>URL: <a href="http://localhost:3000/signup"><code>http://localhost:3000/signup</code></a></p>
</li>
<li><p>Body (JSON):</p>
</li>
</ul>
<pre><code class="language-javascript">{
  "userName": "shubham",
  "password": "1234",
  "firstName": "Shubham",
  "lastName": "Singh"
}
</code></pre>
<hr />
<p><strong>Signin</strong></p>
<ul>
<li><p>Method: <code>POST</code></p>
</li>
<li><p>URL: <code>/signin</code></p>
</li>
</ul>
<p>Copy the <strong>token</strong> from response</p>
<hr />
<p><strong>Create Todo</strong></p>
<ul>
<li><p>Method: <code>POST</code></p>
</li>
<li><p>URL: <code>/todo</code></p>
</li>
<li><p>Headers:</p>
</li>
</ul>
<pre><code class="language-javascript">token: YOUR_TOKEN
</code></pre>
<ul>
<li>Body:</li>
</ul>
<pre><code class="language-javascript">{
  "description": "Learn Express"
}
</code></pre>
<hr />
<p><strong>Get Todos</strong></p>
<ul>
<li><p>Method: <code>GET</code></p>
</li>
<li><p>URL: <code>/todos</code></p>
</li>
<li><p>Headers:</p>
</li>
</ul>
<pre><code class="language-javascript">token: YOUR_TOKEN
</code></pre>
<hr />
<h2>Complete Source Code</h2>
<p>👉 I’ve uploaded the complete project on GitHub. You can check it here:</p>
<p><strong>GitHub Repo:</strong><br /><a href="https://github.com/shubhamsinghbundela/Todo-application-using-express/tree/main">https://github.com/shubhamsinghbundela/Todo-application-using-express/tree/main</a></p>
]]></content:encoded></item><item><title><![CDATA[Sort 0, 1, 2 (Dutch National Flag Algorithm)]]></title><description><![CDATA[When I first solved the 0s and 1s segregation problem, it felt simple.  
If you haven’t seen it, you can check it here:🔗 https://shubhamsinghbundela.hashnode.dev/segregate-0s-and-1s-in-place-sorting
]]></description><link>https://blog.realdev.club/sort-0-1-2-dutch-national-flag-algorithm</link><guid isPermaLink="true">https://blog.realdev.club/sort-0-1-2-dutch-national-flag-algorithm</guid><category><![CDATA[array]]></category><category><![CDATA[sorting]]></category><category><![CDATA[sorting algorithms]]></category><category><![CDATA[Dutch National Flag Algorithm]]></category><category><![CDATA[inplace sort]]></category><category><![CDATA[coding]]></category><category><![CDATA[coding challenge]]></category><category><![CDATA[interview]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[learning]]></category><category><![CDATA[problem solving skills]]></category><category><![CDATA[DSA]]></category><dc:creator><![CDATA[Shubham Kumar Singh]]></dc:creator><pubDate>Tue, 07 Apr 2026 13:06:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/486523cc-fc81-426a-a542-6f8f23e1d109.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When I first solved the <strong>0s and 1s segregation problem</strong>, it felt simple.  </p>
<p>If you haven’t seen it, you can check it here:<br />🔗 <a href="https://shubhamsinghbundela.hashnode.dev/segregate-0s-and-1s-in-place-sorting">https://shubhamsinghbundela.hashnode.dev/segregate-0s-and-1s-in-place-sorting</a></p>
<p>But then comes this problem — <strong>Sort an array of 0s, 1s, and 2s in one pass</strong></p>
<hr />
<h3>Problem Statement</h3>
<p>You are given an array containing only:</p>
<pre><code class="language-plaintext">0 
1 
2
</code></pre>
<p>Your task:</p>
<ul>
<li><p>Sort the array <strong>in-place</strong></p>
</li>
<li><p>Order should be: <code>0s → 1s → 2s</code></p>
</li>
</ul>
<hr />
<p><strong>Example</strong></p>
<pre><code class="language-plaintext">Input:  [2, 0, 2, 1, 1, 0]
Output: [0, 0, 1, 1, 2, 2]
</code></pre>
<hr />
<h3><strong>Approach 1: Counting (Two Pass)</strong></h3>
<p><strong>Idea</strong></p>
<p>This is the most straightforward way:</p>
<ol>
<li><p>Count number of <code>0</code>s, <code>1</code>s, and <code>2</code>s</p>
</li>
<li><p>Rewrite the array</p>
</li>
</ol>
<hr />
<p><strong>Dry Run</strong></p>
<pre><code class="language-plaintext">Input: [2, 0, 2, 1, 1, 0]

Count:
0 → 2 times
1 → 2 times
2 → 2 times
</code></pre>
<p>Now rewrite:</p>
<pre><code class="language-plaintext">[0, 0, 1, 1, 2, 2]
</code></pre>
<hr />
<p><strong>Complexity</strong></p>
<ul>
<li><p>Time: <strong>O(n)</strong> (2 passes)</p>
</li>
<li><p>Space: <strong>O(1)</strong></p>
</li>
</ul>
<hr />
<h3>Approach 2: Dutch National Flag (One Pass)</h3>
<p>Now comes the <strong>real interview approach</strong></p>
<hr />
<p><strong>Idea</strong></p>
<ul>
<li><p>Use two pointers:</p>
<ul>
<li><p><code>l</code> → start</p>
</li>
<li><p><code>i</code> → start</p>
</li>
<li><p><code>r</code> → end</p>
</li>
</ul>
</li>
</ul>
<p><strong>Rule:</strong></p>
<ul>
<li><p>If <code>arr[i] == 0</code> → swap <code>arr[i]</code> with arr[<code>l</code>] , then move <code>i++</code>, <code>l++</code></p>
</li>
<li><p>if <code>arr[i] == 1</code> -&gt; move <code>i++</code></p>
</li>
<li><p>If <code>arr[i] == 2</code> → swap <code>arr[i]</code> with <code>arr[r]</code>, then <code>r--</code></p>
</li>
</ul>
<hr />
<p><strong>Step-by-Step Dry Run</strong></p>
<p><strong>Input:</strong></p>
<pre><code class="language-plaintext">[2, 0, 2, 1, 1, 0]
</code></pre>
<hr />
<p><strong>Initial</strong></p>
<pre><code class="language-plaintext">[2, 0, 2, 1, 1, 0]
 l
 i
                r
</code></pre>
<hr />
<p><strong>Step 1 :</strong></p>
<pre><code class="language-plaintext">arr[i] = 2 
swap with arr[r]
</code></pre>
<p>Only <code>r--</code>, Don’t move <code>i</code></p>
<pre><code class="language-plaintext">[0, 0, 2, 1, 1, 2]
 l
 i
             r
</code></pre>
<hr />
<p><strong>Step 2 :</strong></p>
<pre><code class="language-plaintext">arr[i] = 0 
→ swap with arr[l]
</code></pre>
<p><strong>Move both:</strong></p>
<pre><code class="language-plaintext">l++, i++
</code></pre>
<pre><code class="language-plaintext">[0, 0, 2, 1, 1, 2]
    l
    i
             r
</code></pre>
<hr />
<p><strong>Step 3 :</strong></p>
<pre><code class="language-plaintext">arr[i] = 0 
→ swap with arr[l]
</code></pre>
<p><strong>Move:</strong></p>
<pre><code class="language-plaintext">l++, i++
</code></pre>
<pre><code class="language-plaintext">[0, 0, 2, 1, 1, 2]
       l
       i
             r
</code></pre>
<hr />
<p><strong>Step 4 :</strong></p>
<pre><code class="language-plaintext">arr[i] = 2 
→ swap with arr[r]
</code></pre>
<p><strong>Only:</strong></p>
<pre><code class="language-plaintext">r--
</code></pre>
<pre><code class="language-plaintext">[0, 0, 1, 1, 2, 2]
       l
       i
          r
</code></pre>
<hr />
<p><strong>Step 5 :</strong></p>
<pre><code class="language-plaintext">arr[i] = 1
</code></pre>
<p>Just:</p>
<pre><code class="language-plaintext">i++
</code></pre>
<pre><code class="language-plaintext">[0, 0, 1, 1, 2, 2]
       l
          i
          r
</code></pre>
<hr />
<p><strong>Step 6 :</strong></p>
<pre><code class="language-plaintext">arr[i] = 1 
→ move forward
</code></pre>
<pre><code class="language-plaintext">i++
</code></pre>
<pre><code class="language-plaintext">[0, 0, 1, 1, 2, 2]
       l
             i
          r
</code></pre>
<hr />
<p><strong>Done</strong></p>
<pre><code class="language-plaintext">i &gt; r → stop
</code></pre>
<hr />
<p><strong>Final Output</strong></p>
<pre><code class="language-plaintext">[0, 0, 1, 1, 2, 2]
</code></pre>
<hr />
<h3>Key Rules (Remember This)</h3>
<h3>If <code>arr[i] == 0</code></h3>
<pre><code class="language-plaintext">swap(arr[l], arr[i]);
l++;
i++;
</code></pre>
<hr />
<h3>If <code>arr[i] == 1</code></h3>
<pre><code class="language-plaintext">i++;
</code></pre>
<hr />
<h3>If <code>arr[i] == 2</code></h3>
<pre><code class="language-plaintext">swap(arr[i], arr[r]);
r--;
</code></pre>
<hr />
<p><strong>Code</strong></p>
<pre><code class="language-plaintext">int i = 0;
int l = 0;
int r = n - 1;

while(i &lt;= r){
    if(arr[i] == 0){
        swap(arr[l], arr[i]);
        l++;
        i++;
    } 
    else if(arr[i] == 1){
        i++;
    } 
    else{
        swap(arr[i], arr[r]);
        r--;
    }
}
</code></pre>
<hr />
<p><strong>Complexity</strong></p>
<ul>
<li><p>Time: <strong>O(n)</strong> (single pass)</p>
</li>
<li><p>Space: <strong>O(1)</strong></p>
</li>
</ul>
<hr />
<h3>Final Thoughts</h3>
<p>If you understood this problem:</p>
<p>You now understand:</p>
<ul>
<li><p>Two pointer technique</p>
</li>
<li><p>In-place partitioning</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Segregate 0s and 1s (In-Place Sorting)]]></title><description><![CDATA[When I first saw this problem, it looked very simple…
But it actually teaches an important concept:How to optimize from 2 passes → 1 pass using two pointers

Problem Statement
You are given an array c]]></description><link>https://blog.realdev.club/segregate-0s-and-1s-in-place-sorting</link><guid isPermaLink="true">https://blog.realdev.club/segregate-0s-and-1s-in-place-sorting</guid><category><![CDATA[array]]></category><category><![CDATA[two pointers]]></category><category><![CDATA[coding]]></category><category><![CDATA[CodingInterview]]></category><category><![CDATA[problem solving skills]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[DSA]]></category><category><![CDATA[algorithms]]></category><dc:creator><![CDATA[Shubham Kumar Singh]]></dc:creator><pubDate>Tue, 07 Apr 2026 12:46:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/109eb5f7-0b5c-4f28-91a5-329acbf471ca.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When I first saw this problem, it looked very simple…</p>
<p>But it actually teaches an important concept:<br /><strong>How to optimize from 2 passes → 1 pass using two pointers</strong></p>
<hr />
<h3><strong>Problem Statement</strong></h3>
<p>You are given an array containing only <code>0</code>s and <code>1</code>s.</p>
<p>Your task:</p>
<ul>
<li><p>Move all <code>0</code>s to the left</p>
</li>
<li><p>Move all <code>1</code>s to the right</p>
</li>
<li><p>Do it <strong>in-place</strong></p>
</li>
</ul>
<hr />
<p><strong>Example</strong></p>
<pre><code class="language-javascript">Input:  [0, 0, 1, 1, 0]
Output: [0, 0, 0, 1, 1]
</code></pre>
<hr />
<h3><strong>Approach 1: Two Pass Solution (Counting)</strong></h3>
<p>This is the most intuitive approach.</p>
<p><strong>Idea</strong></p>
<ol>
<li><p>Count number of <code>0</code>s and <code>1</code>s</p>
</li>
<li><p>Rewrite the array</p>
</li>
</ol>
<hr />
<p><strong>Step-by-Step Traversal</strong></p>
<p><strong>Input:</strong></p>
<pre><code class="language-plaintext">[0, 0, 1, 1, 0]
</code></pre>
<p><strong>Step 1: Count values</strong></p>
<pre><code class="language-plaintext">count0 = 3
count1 = 2
</code></pre>
<p><strong>Step 2: Rewrite array</strong></p>
<pre><code class="language-plaintext">Index 0 → 0
Index 1 → 0
Index 2 → 0
Index 3 → 1
Index 4 → 1
</code></pre>
<p><strong>Final Output:</strong></p>
<pre><code class="language-plaintext">[0, 0, 0, 1, 1]
</code></pre>
<hr />
<p><strong>Code</strong></p>
<pre><code class="language-javascript">int count0 = 0;
int count1 = 0;

for (int i = 0; i &lt; n; i++) {
    if(arr[i] == 0) count0++;
    else count1++;
}

for (int i = 0; i &lt; count0; i++) {
    arr[i] = 0;
}
for (int i = count0; i &lt; n; i++) {
    arr[i] = 1;
}
</code></pre>
<hr />
<p><strong>Complexity</strong></p>
<ul>
<li><p>Time: <strong>O(n)</strong> (2 passes)</p>
</li>
<li><p>Space: <strong>O(1)</strong></p>
</li>
</ul>
<hr />
<h3>Approach 2: One Pass (Two Pointer Technique)</h3>
<p>Now comes the <strong>interesting part</strong></p>
<p>We solve it in <strong>just one traversal</strong></p>
<hr />
<p><strong>Idea</strong></p>
<ul>
<li><p>Use two pointers:</p>
<ul>
<li><p><code>l</code> → start</p>
</li>
<li><p><code>r</code> → end</p>
</li>
</ul>
</li>
</ul>
<p><strong>Rule:</strong></p>
<ul>
<li><p>If <code>arr[l] == 0</code> → move <code>l++</code></p>
</li>
<li><p>If <code>arr[l] == 1</code> → swap with <code>arr[r]</code>, then <code>r--</code></p>
</li>
</ul>
<hr />
<p><strong>Dry Run (Step-by-Step)</strong></p>
<p><strong>Input:</strong></p>
<pre><code class="language-plaintext">[0, 0, 1, 1, 0]
</code></pre>
<p><strong>Initial:</strong></p>
<pre><code class="language-plaintext">l = 0, r = 4
</code></pre>
<hr />
<p><strong>Step 1:</strong></p>
<pre><code class="language-plaintext">[0, 0, 1, 1, 0]
 L           R

arr[L] = 0 → OK → move L →
</code></pre>
<hr />
<p><strong>Step 2:</strong></p>
<pre><code class="language-plaintext">[0, 0, 1, 1, 0]
    L        R

arr[L] = 0 → OK → move L →
</code></pre>
<hr />
<p><strong>Step 3 :</strong></p>
<pre><code class="language-plaintext">[0, 0, 1, 1, 0]
       L     R

arr[L] = 1 → swap with arr[R]
</code></pre>
<p>Then:</p>
<pre><code class="language-plaintext">R--
</code></pre>
<p>After swap:</p>
<pre><code class="language-plaintext">[0, 0, 0, 1, 1]
       L  R   
</code></pre>
<hr />
<p><strong>Step 4:</strong></p>
<pre><code class="language-plaintext">[0, 0, 0, 1, 1]
          L  
          R

arr[L] = 0 → move L →
</code></pre>
<hr />
<p><strong>Step 5:</strong></p>
<pre><code class="language-plaintext">[0, 0, 0, 1, 1]
          L  
          R

arr[L] = 1 → swap with arr[R]
</code></pre>
<p>Then:</p>
<pre><code class="language-plaintext">R--
</code></pre>
<p>After swap:</p>
<pre><code class="language-plaintext">[0, 0, 0, 1, 1]
       R  L   
</code></pre>
<hr />
<p><strong>Final:</strong></p>
<pre><code class="language-plaintext">[0, 0, 0, 1, 1]
Done 
</code></pre>
<hr />
<p><strong>Code</strong></p>
<pre><code class="language-javascript">int l = 0;
int r = n - 1;

while (l &lt;= r) {
    if(arr[l] == 0){
        l++;
    } else {
        swap(arr[l], arr[r]);
        r--;
    }
}
</code></pre>
<hr />
<p><strong>Complexity</strong></p>
<ul>
<li><p>Time: <strong>O(n)</strong> (single pass)</p>
</li>
<li><p>Space: <strong>O(1)</strong></p>
</li>
</ul>
<hr />
<p>This problem is a <strong>simplified version of</strong>: Dutch National Flag Problem (0s, 1s, 2s)</p>
<p>If you understand this,<br />you can easily solve more complex partitioning problems.</p>
<hr />
<h3>Conclusion</h3>
<table>
<thead>
<tr>
<th>Approach</th>
<th>Passes</th>
<th>Idea</th>
</tr>
</thead>
<tbody><tr>
<td>Counting</td>
<td>2</td>
<td>Simple &amp; safe</td>
</tr>
<tr>
<td>Two Pointer</td>
<td>1</td>
<td>Optimized &amp; smart</td>
</tr>
</tbody></table>
]]></content:encoded></item><item><title><![CDATA[From Callbacks → Callback Hell → Promises → Async/Await (JavaScript)]]></title><description><![CDATA[When I started learning async JavaScript, I was confused about how things evolved from callbacks to async/await.
So here’s a simple breakdown with examples
1. What is a Callback?
A callback is just a ]]></description><link>https://blog.realdev.club/from-callbacks-callback-hell-promises-async-await-javascript</link><guid isPermaLink="true">https://blog.realdev.club/from-callbacks-callback-hell-promises-async-await-javascript</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Async JavaScript]]></category><category><![CDATA[asynchronous]]></category><category><![CDATA[callbacks]]></category><category><![CDATA[callback hell ]]></category><category><![CDATA[Promises JavaScript]]></category><category><![CDATA[async/await]]></category><category><![CDATA[javascript interview questions]]></category><category><![CDATA[Event Loop JavaScript]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Shubham Kumar Singh]]></dc:creator><pubDate>Wed, 01 Apr 2026 11:25:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/8e937530-339b-4740-b9ab-03d4a2f6b1c4.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When I started learning async JavaScript, I was confused about how things evolved from <strong>callbacks to async/await</strong>.</p>
<p>So here’s a simple breakdown with examples</p>
<h3><strong>1. What is a Callback?</strong></h3>
<p>A callback is just a function passed into another function and executed later.</p>
<p><strong>Example</strong></p>
<pre><code class="language-javascript">function xyz(text, cb) {
  cb(text);
}

xyz("shubham", (data) =&gt; {
  console.log(data);
});
</code></pre>
<p><strong>Output:</strong></p>
<pre><code class="language-plaintext">shubham
</code></pre>
<p><strong>Making it Asynchronous (using setTimeout)</strong></p>
<pre><code class="language-javascript">function xyz(text, cb) {
  setTimeout(() =&gt; {
    cb(text);
  }, 1000);
}

xyz("shubham", (data) =&gt; {
  console.log(data);
});
</code></pre>
<p><strong>Output (after 1 sec):</strong></p>
<pre><code class="language-plaintext">shubham
</code></pre>
<hr />
<h3><strong>2. Callback Hell</strong></h3>
<p>When multiple async tasks depend on each other, callbacks get nested.</p>
<p><strong>Example</strong></p>
<pre><code class="language-javascript">function xyz(text, cb) {
  setTimeout(() =&gt; {
    cb(text);
  }, 1000);
}

xyz("step 1", (data1) =&gt; {
  console.log(data1);

  xyz("step 2", (data2) =&gt; {
    console.log(data2);

    xyz("step 3", (data3) =&gt; {
      console.log(data3);

      xyz("step 4", (data4) =&gt; {
        console.log(data4);
      });

    });

  });
});
</code></pre>
<p><strong>Problems:</strong></p>
<ul>
<li><p>Hard to read</p>
</li>
<li><p>Hard to debug</p>
</li>
</ul>
<h3><strong>Why Promises Came</strong></h3>
<p>To solve callback hell, JavaScript introduced <strong>Promises</strong>.</p>
<hr />
<h3>3. Promises Version (Clean)</h3>
<pre><code class="language-javascript">function xyz(text) {
  return new Promise((resolve) =&gt; {
    setTimeout(() =&gt; {
      resolve(text);
    }, 1000);
  });
}

xyz("step 1")
  .then((data1) =&gt; {
    console.log(data1);
    return xyz("step 2");
  })
  .then((data2) =&gt; {
    console.log(data2);
    return xyz("step 3");
  })
  .then((data3) =&gt; {
    console.log(data3);
    return xyz("step 4");
  })
  .then((data4) =&gt; {
    console.log(data4);
  })
  .catch(console.error);
</code></pre>
<p><strong>Benefits:</strong></p>
<ul>
<li><p>No nesting</p>
</li>
<li><p>Better readability</p>
</li>
<li><p>Proper error handling</p>
</li>
</ul>
<hr />
<h3><strong>4. Async/Await Version (Best &amp; Cleanest)</strong></h3>
<p>Async/Await makes async code look like normal synchronous code.</p>
<pre><code class="language-javascript">function xyz(text) {
  return new Promise((resolve) =&gt; {
    setTimeout(() =&gt; {
      resolve(text);
    }, 1000);
  });
}

async function run() {
  try {
    const data1 = await xyz("step 1");
    console.log(data1);

    const data2 = await xyz("step 2");
    console.log(data2);

    const data3 = await xyz("step 3");
    console.log(data3);

    const data4 = await xyz("step 4");
    console.log(data4);
  } catch (err) {
    console.error(err);
  }
}

run();
</code></pre>
<p><strong>Benefits:</strong></p>
<ul>
<li><p>Looks synchronous</p>
</li>
<li><p>Easy to read</p>
</li>
<li><p>Cleaner error handling</p>
</li>
</ul>
<hr />
<h2>5. await vs .then()</h2>
<p><strong>Equivalent Code</strong></p>
<pre><code class="language-javascript">const data = await xyz("step 1");
console.log(data);
</code></pre>
<p><strong>Same as:</strong></p>
<pre><code class="language-javascript">xyz("step 1").then((data) =&gt; {
  console.log(data);
});
</code></pre>
<blockquote>
<p>await is just syntactic sugar over Promises (.then), making async code look synchronous.</p>
</blockquote>
<hr />
<h3>Final Summary</h3>
<ul>
<li><p>Callback → Nested (Callback Hell)</p>
</li>
<li><p>Promise → Flat chaining</p>
</li>
<li><p>Async/Await → Clean &amp; readable</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Understanding Event Loop via a Scheduler]]></title><description><![CDATA[While solving a Time-Sliced Task Scheduler problem, I realized that my understanding of the event loop, microtasks, and macrotasks was wrong.

await does not automatically yield control to the event l]]></description><link>https://blog.realdev.club/understanding-event-loop-via-a-scheduler</link><guid isPermaLink="true">https://blog.realdev.club/understanding-event-loop-via-a-scheduler</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[asynchronous]]></category><category><![CDATA[async]]></category><category><![CDATA[asynchronous programming]]></category><category><![CDATA[asynchronous JavaScript]]></category><category><![CDATA[concurrency]]></category><category><![CDATA[scheduler]]></category><category><![CDATA[coding]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Developer]]></category><category><![CDATA[ #TechLearning]]></category><dc:creator><![CDATA[Shubham Kumar Singh]]></dc:creator><pubDate>Wed, 01 Apr 2026 11:00:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/7c6c1852-771c-4edf-baf2-264b6f211f3f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>While solving a <strong>Time-Sliced Task Scheduler</strong> problem, I realized that my understanding of the <strong>event loop, microtasks, and macrotasks was wrong</strong>.</p>
<blockquote>
<p><code>await</code> does not automatically yield control to the event loop.</p>
</blockquote>
<hr />
<h3>Problem Statement:</h3>
<ul>
<li><p>We need to build a scheduler that ensures no single task blocks the event loop for too long.</p>
</li>
<li><p>Instead of running tasks completely, each task should execute in small parts and pause in between.</p>
</li>
<li><p>This pause (yield) allows other tasks, including newly added or higher-priority ones, to run.</p>
</li>
<li><p>Since JavaScript cannot stop tasks forcefully, tasks must voluntarily give up control.<br />This concept is called <strong>cooperative multitasking</strong>, commonly used in UI frameworks to keep applications responsive.</p>
</li>
</ul>
<hr />
<h3>Implementation</h3>
<pre><code class="language-javascript">class TimeSlicedScheduler {
  constructor() {
    this.queue = [];
  }

  schedule(task) {
    this.queue.push(task);
  }

  async run() {
    while (this.queue.length) {
      const task = this.queue.shift();

      await task();

      // Yield control
      await new Promise(resolve =&gt; setTimeout(resolve, 0));
    }
  }
}
</code></pre>
<hr />
<h3>Key Test Case</h3>
<pre><code class="language-javascript">const runPromise = scheduler.run();

await new Promise(r =&gt; setTimeout(r, 0));
events.push("event-loop");

await runPromise;
</code></pre>
<hr />
<h3>What Happens WITHOUT Yield</h3>
<p>If we remove the yield:</p>
<pre><code class="language-javascript">await task();

//remove yield
</code></pre>
<p><strong>Execution Flow:</strong></p>
<ol>
<li><p><code>task-1</code> runs</p>
</li>
<li><p>Immediately continues</p>
</li>
<li><p><code>task-2</code> runs</p>
</li>
<li><p>Then event loop runs</p>
</li>
</ol>
<p><strong>Output:</strong></p>
<pre><code class="language-plaintext">task-1
task-2
event-loop ❌
</code></pre>
<hr />
<h3>Why This Happens</h3>
<p>Because <code>await</code> uses <strong>microtasks</strong>, and:</p>
<blockquote>
<p>Microtasks run immediately after the current execution, before moving to the next event loop cycle.</p>
</blockquote>
<p>So execution continues without any pause.</p>
<hr />
<h3>What Changes WITH Yield</h3>
<pre><code class="language-javascript">await task();

// added yield
await new Promise(resolve =&gt; setTimeout(resolve, 0));
</code></pre>
<p>This introduces a <strong>macrotask</strong>, which:</p>
<ul>
<li><p>Pauses execution</p>
</li>
<li><p>Gives control back to the event loop</p>
</li>
<li><p>Allows other pending work to run</p>
</li>
</ul>
<hr />
<h3>Execution Flow WITH Yield</h3>
<ol>
<li><p><code>task-1</code> runs</p>
</li>
<li><p>Scheduler pauses (<code>setTimeout</code>)</p>
</li>
<li><p>Event loop executes <code>"event-loop"</code></p>
</li>
<li><p>Scheduler resumes</p>
</li>
<li><p><code>task-2</code> runs</p>
</li>
</ol>
<p><strong>Output:</strong></p>
<pre><code class="language-plaintext">task-1
event-loop
task-2
</code></pre>
<hr />
<h3>Microtask vs Macrotask (Simple View)</h3>
<table>
<thead>
<tr>
<th>Type</th>
<th>Examples</th>
<th>Behavior</th>
</tr>
</thead>
<tbody><tr>
<td>Microtask</td>
<td><code>await</code>, <code>Promise.then</code></td>
<td>Runs immediately</td>
</tr>
<tr>
<td>Macrotask</td>
<td><code>setTimeout</code></td>
<td>Runs in next cycle</td>
</tr>
</tbody></table>
<hr />
<h3>Key Insight</h3>
<blockquote>
<p>Microtasks don’t give control back to the event loop — macrotasks do.</p>
</blockquote>
<hr />
<h3>Final Takeaway</h3>
<ul>
<li><p><code>await</code> alone is not enough for yielding</p>
</li>
<li><p>You need a macrotask (<code>setTimeout</code>) to pause execution</p>
</li>
<li><p>This pattern is used in real-world systems to avoid blocking</p>
</li>
</ul>
<hr />
<h3>One Line to Remember</h3>
<blockquote>
<p>If you want to truly yield in JavaScript, use a macrotask — not just <code>await</code>.</p>
</blockquote>
<p>This small detail completely changed how I think about async execution in JavaScript.</p>
]]></content:encoded></item><item><title><![CDATA[Prefix Sum Array Technique]]></title><description><![CDATA[Why Prefix Sum Array?
Prefix Sum is a powerful technique used to optimize range sum queries and many subarray problems.
In many problems, we are asked:

“Find the sum of elements between index l and r]]></description><link>https://blog.realdev.club/prefix-sum-array-technique</link><guid isPermaLink="true">https://blog.realdev.club/prefix-sum-array-technique</guid><category><![CDATA[cpp]]></category><category><![CDATA[DSA]]></category><category><![CDATA[prefix-sum]]></category><category><![CDATA[#PrefixSum]]></category><category><![CDATA[prefix sum technique]]></category><category><![CDATA[algorithms]]></category><dc:creator><![CDATA[Shubham Kumar Singh]]></dc:creator><pubDate>Wed, 01 Apr 2026 10:29:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/cfc84fea-5535-43ed-8755-5c6d66eab7a2.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3>Why Prefix Sum Array?</h3>
<p>Prefix Sum is a powerful technique used to optimize <strong>range sum queries</strong> and many <strong>subarray problems</strong>.</p>
<p>In many problems, we are asked:</p>
<blockquote>
<p>“Find the sum of elements between index <code>l</code> and <code>r</code> ”</p>
</blockquote>
<p>Instead of recalculating sums again and again, we <strong>precompute</strong> values so that we can answered instantly.</p>
<hr />
<h3><strong>What is Prefix Sum Array?</strong></h3>
<p>A prefix sum array is a derived array that stores the cumulative sum of elements in a given array. Each element in the prefix sum array represents the sum of all elements up to that index in the original array.</p>
<hr />
<h3>Step-by-Step Prefix Sum Calculation:</h3>
<p><strong>Given Array</strong></p>
<pre><code class="language-plaintext">arr = [2, 4, 1, 3, 5]
</code></pre>
<hr />
<p><strong>Build Prefix Sum Array</strong></p>
<p>We keep adding elements cumulatively.</p>
<table>
<thead>
<tr>
<th>Index (i)</th>
<th>arr[i]</th>
<th>Running Sum</th>
<th>prefix[i]</th>
</tr>
</thead>
<tbody><tr>
<td>0</td>
<td>2</td>
<td>2</td>
<td>2</td>
</tr>
<tr>
<td>1</td>
<td>4</td>
<td>2 + 4 = 6</td>
<td>6</td>
</tr>
<tr>
<td>2</td>
<td>1</td>
<td>6 + 1 = 7</td>
<td>7</td>
</tr>
<tr>
<td>3</td>
<td>3</td>
<td>7 + 3 = 10</td>
<td>10</td>
</tr>
<tr>
<td>4</td>
<td>5</td>
<td>10 + 5 = 15</td>
<td>15</td>
</tr>
</tbody></table>
<hr />
<p><strong>Final Prefix Sum Array</strong></p>
<pre><code class="language-plaintext">prefix = [2, 6, 7, 10, 15]
</code></pre>
<hr />
<h3>Where Do We Use Prefix Sum Array?</h3>
<p>Prefix sum is mainly used in:</p>
<ol>
<li><p><strong>Range Sum Queries</strong></p>
</li>
<li><p><strong>Subarray Problems</strong></p>
</li>
</ol>
<hr />
<h3>1. Range Sum Query (Most Common Use Case)</h3>
<img src="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/23317999-8d96-42fa-adf6-544a29d6d033.png" alt="" />

<h3>Problem Statement</h3>
<p>You are given an array of size <code>n</code>.</p>
<p>You need to answer <code>q</code> queries:</p>
<p>For each query <code>(l, r)</code>, find the sum of elements from index <code>l</code> to <code>r</code>.</p>
<hr />
<h2>Brute Force Approach</h2>
<pre><code class="language-cpp">int q;
cin &gt;&gt; q;

for (int i = 0; i &lt; q; i++) {
    int l, r;
    cin &gt;&gt; l &gt;&gt; r;

    l--; // convert to 0-based index
    r--;

    int ans = 0;
    for (int i = l; i &lt;= r; i++) {
        ans += arr[i];
    }

    cout &lt;&lt; ans &lt;&lt; endl;
}
</code></pre>
<h3>Time Complexity</h3>
<ul>
<li><p>Each query takes <strong>O(n)</strong> in worst case</p>
</li>
<li><p>Total: <strong>O(q × n)</strong></p>
</li>
</ul>
<p>Worst case:</p>
<ul>
<li><p><code>q = 10^5</code></p>
</li>
<li><p><code>n = 10^5</code></p>
</li>
</ul>
<p>So total operations ≈ <strong>10¹⁰</strong> (Too slow → TLE)</p>
<hr />
<h3>Optimized Approach: Prefix Sum</h3>
<p><strong>Step 1: Build Prefix Sum Array</strong></p>
<blockquote>
<p>We create a new array where each index stores:</p>
<p>Sum of all elements from start to that index</p>
</blockquote>
<p><strong>Lets Build Prefix sum Array.</strong></p>
<pre><code class="language-cpp">int sum = 0;
for (int i = 0; i &lt; n; i++) {
    sum += arr[i];
    arr[i] = sum;
}
</code></pre>
<p><strong>Step 2: Answer Queries in O(1)</strong></p>
<pre><code class="language-cpp">for (int i = 0; i &lt; q; i++) {
    int l, r;
    cin &gt;&gt; l &gt;&gt; r;

    l--; // convert to 0-based
    r--;

    if (l == 0) {
        cout &lt;&lt; arr[r] &lt;&lt; endl;
    } else {
        cout &lt;&lt; arr[r] - arr[l - 1] &lt;&lt; endl;
    }
}
</code></pre>
<hr />
<p><strong>Time Complexity</strong></p>
<table>
<thead>
<tr>
<th>Approach</th>
<th>Time Complexity</th>
</tr>
</thead>
<tbody><tr>
<td>Brute Force</td>
<td>O(q × n)</td>
</tr>
<tr>
<td>Prefix Sum</td>
<td>O(n + q)</td>
</tr>
</tbody></table>
<p>Now it easily fits within <strong>1 second limit</strong></p>
<hr />
<h3>2. Sum of All Subarrays</h3>
<img src="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/0688a2a8-be64-4139-9e69-613d4b1d67dd.png" alt="" style="display:block;margin:0 auto" />

<h3>Problem Understanding</h3>
<p>You are given an array:</p>
<ul>
<li><p>For every starting index <code>L</code>, print <strong>all subarrays starting from L</strong></p>
</li>
<li><p>And print the <strong>sum of each subarray</strong></p>
</li>
</ul>
<p><strong>Example</strong></p>
<pre><code class="language-plaintext">arr = [1, 2, 3]
</code></pre>
<p><strong>Subarrays and their sums:</strong></p>
<pre><code class="language-plaintext">L = 0 → [1] = 1  
         [1,2] = 3  
         [1,2,3] = 6  

L = 1 → [2] = 2  
         [2,3] = 5  

L = 2 → [3] = 3  
</code></pre>
<hr />
<h3><strong>Brute Force Approach</strong></h3>
<p>We generate all subarrays and calculate sum every time:</p>
<pre><code class="language-cpp">for (int l = 0; l &lt; n; l++) {
    for (int r = l; r &lt; n; r++) {
        int sum = 0;
        for (int k = l; k &lt;= r; k++) {
            sum += arr[k];
        }
        cout &lt;&lt; sum &lt;&lt; endl;
    }
}
</code></pre>
<p><strong>Time Complexity</strong></p>
<ul>
<li>3 loops → <strong>O(n³)</strong> (very slow)</li>
</ul>
<hr />
<h3><strong>Optimized using Prefix Sum:</strong></h3>
<p><strong>Steps</strong></p>
<p><strong>1. Build Prefix Sum</strong></p>
<pre><code class="language-cpp">int sum = 0;
for (int i = 0; i &lt; n; i++) {
    sum += arr[i];
    arr[i] = sum;
}
</code></pre>
<p><strong>2. Generate Subarrays (Optimized)</strong></p>
<pre><code class="language-cpp">for (int l = 0; l &lt; n; l++) {
    for (int r = l; r &lt; n; r++) {
        if (l == 0) {
            cout &lt;&lt; arr[r] &lt;&lt; endl;
        } else {
            cout &lt;&lt; arr[r] - arr[l - 1] &lt;&lt; endl;
        }
    }
}
</code></pre>
<p><strong>Time Complexity</strong></p>
<ul>
<li><p>Prefix building → <strong>O(n)</strong></p>
</li>
<li><p>Generating subarrays → <strong>O(n²)</strong></p>
</li>
</ul>
<p>Total = <strong>O(n²)</strong></p>
<hr />
<h3>Where Else Prefix Sum is Used?</h3>
<ul>
<li><p>Subarray sum problems</p>
</li>
<li><p>Count of subarrays with given sum</p>
</li>
<li><p>2D matrix sum queries</p>
</li>
<li><p>Sliding window optimizations</p>
</li>
</ul>
<hr />
<h3><strong>Conclusion</strong></h3>
<p>The prefix sum array serves as a powerful tool in optimizing computations involving cumulative sums. By constructing a prefix sum array, we can efficiently answer queries related to range sums, subarray sums etc.</p>
]]></content:encoded></item><item><title><![CDATA[How I Solved Preemptive Priority Task Scheduler in JavaScript]]></title><description><![CDATA[When I first saw this problem, it looked like something straight out of an Operating System.
But implementing it in JavaScript?That’s where things get interesting.

What is Preemptive Scheduling?
Pree]]></description><link>https://blog.realdev.club/how-i-solved-preemptive-priority-task-scheduler-in-javascript</link><guid isPermaLink="true">https://blog.realdev.club/how-i-solved-preemptive-priority-task-scheduler-in-javascript</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[scheduler]]></category><category><![CDATA[priority queue]]></category><category><![CDATA[asynchronous programming]]></category><category><![CDATA[concurrency]]></category><dc:creator><![CDATA[Shubham Kumar Singh]]></dc:creator><pubDate>Tue, 24 Mar 2026 11:33:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/624226a5db84f8c50fa5b247/8febbbf1-e8e1-4f4d-9d42-e8cb3f3356af.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When I first saw this problem, it looked like something straight out of an Operating System.</p>
<p>But implementing it in JavaScript?<br />That’s where things get interesting.</p>
<hr />
<h3>What is Preemptive Scheduling?</h3>
<p>Preemptive scheduling is an OS technique where:</p>
<p>A <strong>high-priority task can interrupt</strong> a currently running low-priority task<br />CPU automatically switches execution</p>
<p>In simple words:</p>
<blockquote>
<p>“Important work gets preference immediately.”</p>
</blockquote>
<hr />
<p><strong>But here’s the catch in JavaScript</strong></p>
<p>JavaScript does <strong>NOT support true preemption</strong></p>
<p><strong>What is “true preemption”?</strong></p>
<ul>
<li><p>A running task can be forcefully stopped anytime</p>
</li>
<li><p>CPU takes control and switches tasks</p>
</li>
</ul>
<p>This happens in Operating Systems</p>
<p><strong>In JavaScript</strong></p>
<ul>
<li><p>If a function starts running…</p>
</li>
<li><p>Nothing can stop it until it finishes</p>
</li>
</ul>
<p>That means:</p>
<blockquote>
<p>We cannot interrupt execution midway</p>
</blockquote>
<hr />
<h3>So how do we solve this?</h3>
<p>Since JavaScript cannot interrupt a running task, we design tasks in such a way that they <strong>pause themselves after some work</strong> and give control back to the scheduler.</p>
<p>This allows the system to check if any higher-priority task has arrived before continuing.</p>
<p>We achieve this using:</p>
<ul>
<li><p><code>setTimeout</code></p>
</li>
<li><p><code>await</code> (async tasks)</p>
</li>
</ul>
<hr />
<h3>Problem Statement</h3>
<p>We need to:</p>
<ul>
<li><p>Execute tasks based on <strong>priority</strong></p>
</li>
<li><p>Higher priority runs first</p>
</li>
<li><p>Allow <strong>new high-priority tasks</strong> to jump ahead</p>
</li>
<li><p>Since no true preemption → tasks must <strong>yield control</strong></p>
</li>
</ul>
<hr />
<h3>My Approach</h3>
<p>I built a <code>Scheduler</code> class with:</p>
<ol>
<li><p><strong>Priority Queue</strong> → to store tasks</p>
</li>
<li><p><strong>Sorting mechanism</strong> → highest priority first</p>
</li>
<li><p><strong>Execution loop</strong> → runs tasks one by one</p>
</li>
<li><p><strong>Yield mechanism</strong> → allows re-evaluation after each task</p>
</li>
</ol>
<hr />
<h3>Implementation</h3>
<pre><code class="language-javascript">class Scheduler {
  constructor() {
    this.queue = [];
    this.timer = null;
    this.running = false;
  }

  schedule(task, priority = 0) {
    this.queue.push({ task, priority });

    // maintain priority order
    this.queue.sort((a, b) =&gt; b.priority - a.priority);

    // start scheduler only if not already running
    if (!this.running) {
      this.running = true;
      // Don’t continue immediately
      // Pause here and come back later
      this.timer = setTimeout(() =&gt; this.run(), 0);
    }
  }

  run(onAllFinished) {
    if (this.queue.length === 0) {
      this.running = false;
      this.timer = null;
      return onAllFinished &amp;&amp; onAllFinished(null);
    }

    const { task } = this.queue.shift();

    task((err) =&gt; {
      if (err) {
        this.running = false;
        this.timer = null;
        return onAllFinished &amp;&amp; onAllFinished(err);
      }

      // schedule next task instead of running immediately
      this.timer = setTimeout(() =&gt; this.run(onAllFinished), 0);
    });
  }
}

const scheduler = new Scheduler();
const results = [];

const createTask = (val) =&gt; (cb) =&gt; {
  results.push(val);
  cb(null);
};

scheduler.schedule(createTask("low"), 0);
scheduler.schedule(createTask("high"), 10);
scheduler.schedule(createTask("medium"), 5);

scheduler.run((err) =&gt; {
  console.log(results);
});
</code></pre>
<h3>What’s Actually Happening Behind the Scenes?</h3>
<p>Let’s understand the key idea:</p>
<p><strong>1. Tasks are sorted by priority</strong></p>
<pre><code class="language-javascript">this.queue.sort((a, b) =&gt; b.priority - a.priority);
</code></pre>
<p>Highest priority always comes first</p>
<p><strong>2. Only ONE task runs at a time</strong></p>
<pre><code class="language-javascript">const { task } = this.queue.shift();
</code></pre>
<p>This keeps execution controlled</p>
<p><strong>3. After every task → we pause</strong></p>
<pre><code class="language-javascript">setTimeout(() =&gt; this.run(), 0);
</code></pre>
<p>This is the most important design decision</p>
<p>Instead of running tasks continuously:</p>
<ul>
<li><p>We <strong>stop after each task</strong></p>
</li>
<li><p>Give control back to JavaScript runtime</p>
</li>
<li><p>Then resume later</p>
</li>
</ul>
<h3><strong>Why this works like preemption</strong></h3>
<p>Let’s say:</p>
<ol>
<li><p>A low-priority task runs</p>
</li>
<li><p>Before next execution, a <strong>high-priority task is added</strong></p>
</li>
</ol>
<p>Because we paused:</p>
<ul>
<li><p>Scheduler gets a chance to <strong>re-check the queue</strong></p>
</li>
<li><p>High-priority task moves to the top</p>
</li>
<li><p>It runs next</p>
</li>
</ul>
<hr />
<h2>Final Thought</h2>
<p>This problem is not about <code>setTimeout</code>.</p>
<p>It’s about:</p>
<ul>
<li><p>Understanding <strong>how JavaScript execution works</strong></p>
</li>
<li><p>Designing systems within its limitations</p>
</li>
</ul>
]]></content:encoded></item></channel></rss>