<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged reactjs</title>
<link>https://coderlegion.com/tag/reactjs</link>
<description>Powered by Question2Answer</description>
<item>
<title>Your Next.js redirect() Silently Does Nothing Because It's Inside a try/catch</title>
<link>https://coderlegion.com/33072/your-next-js-redirect-silently-does-nothing-because-its-inside-a-try-catch</link>
<description>&lt;p&gt;A user fills in your form and clicks &quot;Create post&quot;. The post is saved in the database. And the page shows &quot;Could not create post&quot;.&lt;/p&gt;
&lt;p&gt;They try again. Now there are two posts.&lt;/p&gt;
&lt;p&gt;Nothing is wrong with your database. Your error handling caught something it was never supposed to catch.&lt;/p&gt;
&lt;h3 id=&quot;the-code-that-causes-it&quot;&gt;The Code That Causes It&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// app/actions.ts
'use server';

import { redirect } from 'next/navigation';

export async function createPost(formData: FormData) {
  try {
    const post = await db.post.create({
      data: { title: String(formData.get('title')) },
    });

    redirect(`/posts/${post.id}`);
  } catch (err) {
    return { error: 'Could not create post' };
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It reads perfectly. Save the post, go to its page, and if anything fails, return an error. A tidy, defensive pattern that you have probably written a hundred times in other frameworks.&lt;/p&gt;
&lt;h3 id=&quot;why-it-breaks&quot;&gt;Why It Breaks&lt;/h3&gt;
&lt;p&gt;In Next.js, &lt;code&gt;redirect()&lt;/code&gt; does not return. It &lt;strong&gt;throws a special error&lt;/strong&gt;, and the framework catches that error higher up and turns it into a redirect response.&lt;/p&gt;
&lt;p&gt;That means your &lt;code&gt;catch&lt;/code&gt; block receives it first. Your code swallows the redirect, returns &lt;code&gt;{ error: ... }&lt;/code&gt;, and the framework never sees it.&lt;/p&gt;
&lt;p&gt;The same is true for &lt;code&gt;notFound()&lt;/code&gt;, which also works by throwing. Both are written to stop execution immediately, which is why TypeScript treats them as functions that never return.&lt;/p&gt;
&lt;h3 id=&quot;why-it-is-hard-to-catch&quot;&gt;Why It Is Hard to Catch&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The data is saved.&lt;/strong&gt; The only visible symptom is a wrong error message, so it looks like a UI bug, not a logic bug.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It is inconsistent in your head.&lt;/strong&gt; The same &lt;code&gt;redirect()&lt;/code&gt; works fine outside a &lt;code&gt;try&lt;/code&gt;, so you assume it is fine everywhere.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Your logs are confusing.&lt;/strong&gt; If you log the caught error, you will see a strange error with a &lt;code&gt;NEXT_REDIRECT&lt;/code&gt; digest and think something crashed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It tempts you to retry.&lt;/strong&gt; Users see an error, submit again and create duplicates.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;fix-1-keep-redirect-outside-the-try-block&quot;&gt;Fix 1: Keep redirect() Outside the try Block&lt;/h3&gt;
&lt;p&gt;Only wrap the code that can genuinely fail, and redirect afterwards:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export async function createPost(formData: FormData) {
  let postId: string;

  try {
    const post = await db.post.create({
      data: { title: String(formData.get('title')) },
    });
    postId = post.id;
  } catch (err) {
    return { error: 'Could not create post' };
  }

  redirect(`/posts/${postId}`);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;try&lt;/code&gt; now covers only the database call. If it fails, you return the error. If it succeeds, execution continues to &lt;code&gt;redirect()&lt;/code&gt;, which is outside any &lt;code&gt;catch&lt;/code&gt;, so it can do its job.&lt;/p&gt;
&lt;p&gt;This is the cleanest option, and it is the one I use by default.&lt;/p&gt;
&lt;h3 id=&quot;fix-2-rethrow-if-you-must-catch-everything&quot;&gt;Fix 2: Rethrow If You Must Catch Everything&lt;/h3&gt;
&lt;p&gt;Sometimes the &lt;code&gt;redirect()&lt;/code&gt; sits deep inside a helper, and restructuring is awkward. Then let the framework's own errors pass through your &lt;code&gt;catch&lt;/code&gt;. Newer Next.js versions provide a helper for exactly this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { redirect, unstable_rethrow } from 'next/navigation';

try {
  // ...work that may call redirect() or notFound() somewhere inside
} catch (err) {
  unstable_rethrow(err); // lets redirect/notFound errors continue upward
  return { error: 'Something went wrong' };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The name says it is not final, so check the documentation for the version you use. If it is not available in yours, structure your code as in Fix 1.&lt;/p&gt;
&lt;h3 id=&quot;fix-3-do-not-use-catch-all-to-control-flow&quot;&gt;Fix 3: Do Not Use Catch-All to Control Flow&lt;/h3&gt;
&lt;p&gt;A broader habit helps here. A &lt;code&gt;catch (err)&lt;/code&gt; with no check is a very wide net. Catch what you expect:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;catch (err) {
  if (err instanceof DuplicateTitleError) {
    return { error: 'You already have a post with that title' };
  }
  throw err; // anything else is not yours to handle here
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rethrowing what you do not recognize means the framework's own signals, real crashes and bugs all reach the place that is meant to handle them.&lt;/p&gt;
&lt;h3 id=&quot;where-else-this-bites&quot;&gt;Where Else This Bites&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;notFound()&lt;/code&gt; inside &lt;code&gt;try&lt;/code&gt;.&lt;/strong&gt; A missing record becomes your generic error instead of a 404 page.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Route handlers and Server Components.&lt;/strong&gt; The same rule applies: any &lt;code&gt;try/catch&lt;/code&gt; around a call that can redirect or 404 will swallow it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;.catch()&lt;/code&gt; on a promise.&lt;/strong&gt; &lt;code&gt;somePromise.catch(() =&amp;gt; null)&lt;/code&gt; has the same effect if the promise's code calls &lt;code&gt;redirect()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wrapper functions.&lt;/strong&gt; A helper like &lt;code&gt;withErrorHandling(fn)&lt;/code&gt; that catches everything will break redirects in every function it wraps.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;quick-checklist&quot;&gt;Quick Checklist&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Situation &lt;/th&gt;&lt;th&gt;What to do&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;redirect()&lt;/code&gt; after a database write &lt;/td&gt;&lt;td&gt;Put the write in &lt;code&gt;try&lt;/code&gt;, &lt;code&gt;redirect()&lt;/code&gt; after it&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;notFound()&lt;/code&gt; when a record is missing &lt;/td&gt;&lt;td&gt;Call it outside the &lt;code&gt;try&lt;/code&gt;, or rethrow&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;A shared error-handling wrapper &lt;/td&gt;&lt;td&gt;Make it rethrow framework errors&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Unsure &lt;/td&gt;&lt;td&gt;Catch specific error types, rethrow the rest&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;the-rule&quot;&gt;The Rule&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;redirect()&lt;/code&gt; and &lt;code&gt;notFound()&lt;/code&gt; signal by throwing, so a &lt;code&gt;catch&lt;/code&gt; around them will eat the signal.&lt;/strong&gt; Keep them outside &lt;code&gt;try&lt;/code&gt; blocks, or rethrow what you do not recognize.&lt;/p&gt;
&lt;p&gt;Quick audit: search your project for &lt;code&gt;redirect(&lt;/code&gt; and &lt;code&gt;notFound(&lt;/code&gt;. For each hit, look upward. If it sits inside a &lt;code&gt;try&lt;/code&gt;, you have either a bug now or one waiting for the day the code path is used.&lt;/p&gt;
&lt;p&gt;If you want a Next.js starting point where forms and actions are set up sensibly from day one, you can browse my &lt;a rel=&quot;nofollow&quot; href=&quot;https://pixelanas.com&quot;&gt;templates on pixelanas.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Get the templates:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://pixelanas.gumroad.com&quot;&gt;https://pixelanas.gumroad.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Have you ever shipped a form that showed an error after it had succeeded? Tell me in the comments how long it took you to find it.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Anas, full-stack Next.js developer building SaaS products and premium templates. X: &lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/ASheikh69751&quot;&gt;@ASheikh69751&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/33072/your-next-js-redirect-silently-does-nothing-because-its-inside-a-try-catch</guid>
<pubDate>Fri, 30 Oct 2026 10:08:42 +0000</pubDate>
</item>
<item>
<title>How to Build a Custom Admin Panel Without Using WordPress</title>
<link>https://coderlegion.com/32925/how-to-build-a-custom-admin-panel-without-using-wordpress</link>
<description>&lt;p&gt;Every modern web application needs a reliable way to manage its content, users, settings, and business operations. Whether you are building an e-commerce platform, a SaaS application, a customer relationship management system, or a content-driven website, an admin panel makes it possible to control important features without editing the source code every time a change is required.&lt;/p&gt;
&lt;p&gt;WordPress provides an administration dashboard that works well for many websites. However, it is not always the right solution for applications that require specialized workflows, complex permissions, custom business logic, or complete control over their architecture.&lt;/p&gt;
&lt;p&gt;Building a custom admin panel without WordPress gives developers the freedom to design an interface around the application's actual requirements. Instead of adapting business processes to an existing content management system, you can create a dashboard that fits your data structures, security requirements, and operational needs.&lt;/p&gt;
&lt;p&gt;In this article, we will explore how to build a custom admin panel using modern web technologies, including React, Node.js, Express, and PostgreSQL. We will cover architecture, authentication, authorization, CRUD operations, database design, user management, security, testing, and deployment.&lt;/p&gt;
&lt;p&gt;By the end, you will understand how to approach a custom admin panel as a maintainable software product rather than simply another dashboard interface.&lt;/p&gt;
&lt;h3 id=&quot;1-define-the-requirements-before-writing-code&quot;&gt;1. Define the Requirements Before Writing Code&lt;/h3&gt;
&lt;p&gt;The first step is not choosing a framework. It is understanding what administrators need to accomplish.&lt;/p&gt;
&lt;p&gt;An admin panel might allow users to manage products, publish articles, review customer orders, monitor analytics, configure application settings, or manage employee accounts. These requirements determine the structure of the application.&lt;/p&gt;
&lt;p&gt;Start by identifying the main modules.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dashboard:&lt;/strong&gt; Displays important metrics, recent activities, pending tasks, and operational summaries.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;User management:&lt;/strong&gt; Supports viewing users, updating permitted information, changing account status, and assigning roles.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Content management:&lt;/strong&gt; Allows administrators to create, edit, publish, archive, and delete content.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Product or inventory management:&lt;/strong&gt; Provides product listings, stock information, pricing, and category management.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reports and analytics:&lt;/strong&gt; Presents trends, transactions, activity summaries, and other business indicators.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Settings:&lt;/strong&gt; Manages application preferences, notification rules, and other configuration options.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Audit logs:&lt;/strong&gt; Records important administrative actions so that changes can be investigated later.&lt;/p&gt;
&lt;p&gt;Not every application needs every module. A small internal tool may require only user management and reporting, while a larger e-commerce platform may need a complete operational dashboard.&lt;/p&gt;
&lt;p&gt;Write user stories before development. For example:&lt;/p&gt;
&lt;p&gt;&quot;As an administrator, I want to deactivate a user account so that unauthorized access can be prevented.&quot;&lt;/p&gt;
&lt;p&gt;&quot;As a content editor, I want to save a draft so that I can publish the article later.&quot;&lt;/p&gt;
&lt;p&gt;These statements help define functionality, permissions, and acceptance criteria.&lt;/p&gt;
&lt;h3 id=&quot;2-choose-a-suitable-technology-stack&quot;&gt;2. Choose a Suitable Technology Stack&lt;/h3&gt;
&lt;p&gt;A custom admin panel can be built with many technologies. The best stack depends on the team's experience, the application's complexity, and its expected growth.&lt;/p&gt;
&lt;p&gt;For this example, we will use a JavaScript-based stack.&lt;/p&gt;
&lt;h4 id=&quot;frontend-react&quot;&gt;Frontend: React&lt;/h4&gt;
&lt;p&gt;React is suitable for building interactive dashboards with reusable components. Tables, forms, navigation menus, charts, modals, and notification messages can all be implemented as independent components.&lt;/p&gt;
&lt;p&gt;Useful supporting libraries include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;React Router for navigation.&lt;/li&gt;
&lt;li&gt;TanStack Query for server-state management and caching.&lt;/li&gt;
&lt;li&gt;React Hook Form for form handling.&lt;/li&gt;
&lt;li&gt;Zod for schema validation.&lt;/li&gt;
&lt;li&gt;A charting library for visualizing analytics.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A component-based architecture prevents the frontend from becoming a collection of duplicated interface code.&lt;/p&gt;
&lt;h4 id=&quot;backend-node-js-and-express&quot;&gt;Backend: Node.js and Express&lt;/h4&gt;
&lt;p&gt;Node.js allows developers to build server-side applications using JavaScript. Express provides a straightforward way to define API endpoints, middleware, authentication checks, and request validation.&lt;/p&gt;
&lt;p&gt;The backend should contain business rules and access-control logic. The frontend must never be treated as the final authority on whether a user can perform an administrative action.&lt;/p&gt;
&lt;h4 id=&quot;database-postgresql&quot;&gt;Database: PostgreSQL&lt;/h4&gt;
&lt;p&gt;PostgreSQL is a strong choice for applications that manage structured information such as users, products, orders, and audit records.&lt;/p&gt;
&lt;p&gt;Its relational model supports foreign keys, transactions, constraints, and complex queries. These features help maintain data integrity when several administrative operations affect related records.&lt;/p&gt;
&lt;p&gt;An ORM such as Prisma can simplify database access, migrations, and schema management.&lt;/p&gt;
&lt;h4 id=&quot;styling-and-interface-components&quot;&gt;Styling and interface components&lt;/h4&gt;
&lt;p&gt;You can use CSS Modules, Tailwind CSS, or a component library to build the interface. The important goal is consistency.&lt;/p&gt;
&lt;p&gt;Define reusable styles for spacing, typography, buttons, form controls, alerts, and tables. A consistent design system reduces development time and makes the dashboard easier to maintain.&lt;/p&gt;
&lt;h3 id=&quot;3-design-the-application-architecture&quot;&gt;3. Design the Application Architecture&lt;/h3&gt;
&lt;p&gt;A maintainable admin panel separates presentation, business logic, and data access.&lt;/p&gt;
&lt;p&gt;A typical architecture looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Administrator's Browser
          |
          v
     React Frontend
          |
          v
     HTTPS REST API
          |
          v
 Express Middleware
          |
          v
 Authentication and Authorization
          |
          v
    Business Services
          |
          v
   Database Access Layer
          |
          v
     PostgreSQL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The frontend handles user interaction and presentation. The API receives requests, validates input, checks permissions, and invokes the appropriate service. The service applies business rules and interacts with the database.&lt;/p&gt;
&lt;p&gt;For a small application, a modular monolith is often a practical starting point. It keeps deployment relatively simple while allowing the codebase to be divided into clearly defined modules.&lt;/p&gt;
&lt;p&gt;A possible project structure is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;admin-panel/
├── client/
│   └── src/
│       ├── components/
│       ├── layouts/
│       ├── pages/
│       ├── features/
│       ├── hooks/
│       └── services/
├── server/
│   └── src/
│       ├── routes/
│       ├── controllers/
│       ├── middleware/
│       ├── services/
│       ├── validators/
│       └── database/
├── prisma/
│   └── schema.prisma
├── tests/
└── README.md
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each module should have a clear responsibility. Routes define endpoints, controllers coordinate requests and responses, services implement business rules, and the database layer handles persistence.&lt;/p&gt;
&lt;p&gt;Avoid placing every operation inside a single large server file. That approach may seem convenient initially, but it becomes difficult to test and extend as the application grows.&lt;/p&gt;
&lt;h3 id=&quot;4-design-the-database-schema&quot;&gt;4. Design the Database Schema&lt;/h3&gt;
&lt;p&gt;Database design is one of the most important parts of a custom admin panel. Poorly structured data can create inconsistent records, complicated queries, and difficult migrations.&lt;/p&gt;
&lt;p&gt;Consider a simple application that manages administrators, customers, products, and audit events.&lt;/p&gt;
&lt;p&gt;A user table might include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;id&lt;/code&gt;: A unique identifier.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;name&lt;/code&gt;: The user's display name.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;email&lt;/code&gt;: The user's email address.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;password_hash&lt;/code&gt;: A securely hashed password.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;status&lt;/code&gt;: The account status.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;created_at&lt;/code&gt;: The creation timestamp.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;updated_at&lt;/code&gt;: The latest update timestamp.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Roles and permissions should be modeled separately when the application requires flexible access control.&lt;/p&gt;
&lt;p&gt;For example, a user may have an administrator role, while another user has an editor role. The editor might manage content but have no permission to change billing settings.&lt;/p&gt;
&lt;p&gt;Products could contain a name, description, price, stock quantity, publication status, and timestamps. Audit records could reference the administrator who performed an action, the action type, the affected resource, and the time it occurred.&lt;/p&gt;
&lt;p&gt;Use database constraints to protect data integrity. Email addresses should have appropriate uniqueness constraints, required fields should be non-null where necessary, and relationships should use foreign keys.&lt;/p&gt;
&lt;p&gt;Money should be stored using an appropriate decimal or integer representation rather than floating-point arithmetic. Inventory quantities should also have suitable constraints.&lt;/p&gt;
&lt;p&gt;Finally, use migrations to manage schema changes. Migrations make database evolution repeatable across development, testing, and production environments.&lt;/p&gt;
&lt;h3 id=&quot;5-build-secure-authentication&quot;&gt;5. Build Secure Authentication&lt;/h3&gt;
&lt;p&gt;Authentication determines who is accessing the admin panel. It should be implemented before building sensitive administrative features.&lt;/p&gt;
&lt;p&gt;A typical login process works as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The administrator submits an email address and password.&lt;/li&gt;
&lt;li&gt;The server validates the request.&lt;/li&gt;
&lt;li&gt;The server finds the account and checks its status.&lt;/li&gt;
&lt;li&gt;The submitted password is verified against the stored password hash.&lt;/li&gt;
&lt;li&gt;The server establishes an authenticated session.&lt;/li&gt;
&lt;li&gt;The browser receives an appropriate response and navigates to the dashboard.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Passwords must never be stored in plain text. Use a password-hashing algorithm designed for passwords, such as Argon2id or bcrypt, with appropriate parameters.&lt;/p&gt;
&lt;p&gt;For a browser-based administrative application, secure server-managed sessions are often a good choice. Store session identifiers in cookies configured with &lt;code&gt;HttpOnly&lt;/code&gt;, &lt;code&gt;Secure&lt;/code&gt;, and an appropriate &lt;code&gt;SameSite&lt;/code&gt; policy. Use HTTPS in production.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;HttpOnly&lt;/code&gt; attribute prevents client-side JavaScript from reading the session cookie. The &lt;code&gt;Secure&lt;/code&gt; attribute restricts transmission to HTTPS connections.&lt;/p&gt;
&lt;p&gt;Cookie-based authentication also requires careful protection against cross-site request forgery (CSRF). Use appropriate CSRF defenses and validate request origins where suitable.&lt;/p&gt;
&lt;p&gt;An alternative is token-based authentication, but tokens require careful handling of expiration, refresh, revocation, and storage. Do not place long-lived sensitive tokens in browser storage without understanding the associated risks.&lt;/p&gt;
&lt;p&gt;For higher-risk administrative applications, consider multi-factor authentication, login rate limiting, session expiration, and alerts for suspicious activity.&lt;/p&gt;
&lt;h3 id=&quot;6-implement-role-based-access-control&quot;&gt;6. Implement Role-Based Access Control&lt;/h3&gt;
&lt;p&gt;Logging in should not automatically grant a user every administrative capability.&lt;/p&gt;
&lt;p&gt;Role-based access control, commonly called RBAC, assigns permissions according to responsibilities.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Role &lt;/th&gt;&lt;th&gt;Typical permissions&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Super administrator &lt;/td&gt;&lt;td&gt;Manage system configuration and access&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Administrator &lt;/td&gt;&lt;td&gt;Manage users and operational resources&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Editor &lt;/td&gt;&lt;td&gt;Create and update approved content&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Support agent &lt;/td&gt;&lt;td&gt;View customer information and handle support tasks&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Analyst &lt;/td&gt;&lt;td&gt;View reports without changing records&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The exact permissions should reflect the business requirements.&lt;/p&gt;
&lt;p&gt;A simple permission model might contain permissions such as &lt;code&gt;users.read&lt;/code&gt;, &lt;code&gt;users.update&lt;/code&gt;, &lt;code&gt;products.create&lt;/code&gt;, &lt;code&gt;products.delete&lt;/code&gt;, and &lt;code&gt;reports.read&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The backend should verify permissions on every protected operation.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function requirePermission(permission) {
  return (req, res, next) =&amp;gt; {
    const permissions = req.user?.permissions ?? [];

    if (!permissions.includes(permission)) {
      return res.status(403).json({
        message: &quot;You do not have permission to perform this action.&quot;
      });
    }

    next();
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This middleware is an illustrative example. It assumes that authentication middleware has already populated &lt;code&gt;req.user&lt;/code&gt; and that permissions are obtained from a trusted server-side source.&lt;/p&gt;
&lt;p&gt;An endpoint could use it like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;app.get(
  &quot;/api/users&quot;,
  requirePermission(&quot;users.read&quot;),
  listUsers
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In a real implementation, ensure the user and their permissions are loaded from a trusted session or validated identity context. Never accept the user's role directly from an untrusted request body.&lt;/p&gt;
&lt;p&gt;The frontend can hide buttons that the current user cannot use, but that is only a usability improvement. It is not a security boundary. The API must enforce the same restrictions independently.&lt;/p&gt;
&lt;p&gt;Also consider resource-level authorization. Permission to view customer records does not necessarily mean permission to view every customer's sensitive information.&lt;/p&gt;
&lt;h3 id=&quot;7-create-the-dashboard-layout&quot;&gt;7. Create the Dashboard Layout&lt;/h3&gt;
&lt;p&gt;The dashboard is the central workspace for administrators. Its design should make important information easy to find without overwhelming the user.&lt;/p&gt;
&lt;p&gt;A common layout includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A sidebar for primary navigation.&lt;/li&gt;
&lt;li&gt;A top navigation bar with account controls and notifications.&lt;/li&gt;
&lt;li&gt;A main content area.&lt;/li&gt;
&lt;li&gt;Summary cards for important metrics.&lt;/li&gt;
&lt;li&gt;Tables for recent activity or pending tasks.&lt;/li&gt;
&lt;li&gt;Charts for trends where visualization is useful.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Build the layout as reusable React components.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;function AdminLayout({ children }) {
  return (
    &amp;lt;div className=&quot;admin-layout&quot;&amp;gt;
      &amp;lt;Sidebar /&amp;gt;

      &amp;lt;div className=&quot;admin-main&quot;&amp;gt;
        &amp;lt;Topbar /&amp;gt;

        &amp;lt;main className=&quot;admin-content&quot;&amp;gt;
          {children}
        &amp;lt;/main&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;Sidebar&lt;/code&gt; and &lt;code&gt;Topbar&lt;/code&gt; components can be reused across the application.&lt;/p&gt;
&lt;p&gt;Create separate pages for users, products, reports, settings, and other modules. React Router can connect these pages to their respective URLs.&lt;/p&gt;
&lt;p&gt;Responsive design is important even when administrators primarily use desktop computers. Users may need to check information from tablets or mobile devices.&lt;/p&gt;
&lt;p&gt;On smaller screens, a collapsible sidebar, horizontally scrollable tables, and simplified data cards can make the dashboard usable without compromising essential functionality.&lt;/p&gt;
&lt;p&gt;Accessibility should also be part of the layout. Use semantic HTML, visible keyboard focus, descriptive form labels, sufficient contrast, and appropriate error messages.&lt;/p&gt;
&lt;h3 id=&quot;8-build-crud-operations&quot;&gt;8. Build CRUD Operations&lt;/h3&gt;
&lt;p&gt;CRUD stands for Create, Read, Update, and Delete. These four operations form the foundation of many administrative modules.&lt;/p&gt;
&lt;p&gt;Consider product management.&lt;/p&gt;
&lt;p&gt;An administrator should be able to create a product, view the product list, update its details, and delete or archive it when necessary.&lt;/p&gt;
&lt;p&gt;A REST API might expose these endpoints:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;GET    /api/products
GET    /api/products/:id
POST   /api/products
PATCH  /api/products/:id
DELETE /api/products/:id
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each endpoint requires authentication, appropriate authorization, request validation, and predictable error handling.&lt;/p&gt;
&lt;p&gt;A simplified Express route might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;app.post(
  &quot;/api/products&quot;,
  requirePermission(&quot;products.create&quot;),
  validateProductInput,
  async (req, res, next) =&amp;gt; {
    try {
      const product = await productService.create(req.body);

      res.status(201).json({ data: product });
    } catch (error) {
      next(error);
    }
  }
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This example assumes that &lt;code&gt;validateProductInput&lt;/code&gt; and &lt;code&gt;productService.create&lt;/code&gt; are implemented elsewhere. Production code should also ensure that only permitted fields can be submitted and that the database enforces applicable constraints.&lt;/p&gt;
&lt;p&gt;For updates, use an allowlist of editable fields rather than blindly passing user-controlled objects to the database. This prevents mass-assignment vulnerabilities, where a request changes fields the user should not control.&lt;/p&gt;
&lt;p&gt;Deletion deserves special attention. In many business applications, archiving is safer than permanently deleting a record. Orders, invoices, and financial transactions may need to remain available for audit and reporting purposes.&lt;/p&gt;
&lt;p&gt;On the frontend, use forms with clear validation messages and confirmation prompts for destructive operations. Display success or failure notifications so administrators understand whether their action completed.&lt;/p&gt;
&lt;h3 id=&quot;9-implement-search-filtering-and-pagination&quot;&gt;9. Implement Search, Filtering, and Pagination&lt;/h3&gt;
&lt;p&gt;Administrative tables often grow quickly. Loading thousands of records into the browser creates unnecessary network traffic and can slow down the interface.&lt;/p&gt;
&lt;p&gt;Implement server-side pagination.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;GET /api/products?page=2&amp;amp;limit=25&amp;amp;search=keyboard
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The server should validate the page number and limit, apply the search criteria, and return a consistent response.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;data&quot;: [],
  &quot;pagination&quot;: {
    &quot;page&quot;: 2,
    &quot;limit&quot;: 25,
    &quot;total&quot;: 148,
    &quot;totalPages&quot;: 6
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The example contains an empty data array because it illustrates the response structure rather than actual product records.&lt;/p&gt;
&lt;p&gt;Filtering might support product status, category, creation date, or stock availability. Sorting could support name, price, or update time.&lt;/p&gt;
&lt;p&gt;Never insert raw user-provided strings directly into SQL queries. Use parameterized queries or safe ORM query methods.&lt;/p&gt;
&lt;p&gt;For large datasets, consider indexes on commonly filtered or sorted columns. The correct indexes depend on actual query patterns, so inspect query plans and measure performance before adding many indexes.&lt;/p&gt;
&lt;p&gt;Also consider stable sorting. If multiple records share the same timestamp, include a unique identifier as a secondary sort key to avoid inconsistent results between pages.&lt;/p&gt;
&lt;h3 id=&quot;10-manage-forms-and-validation&quot;&gt;10. Manage Forms and Validation&lt;/h3&gt;
&lt;p&gt;Administrative forms frequently handle sensitive or business-critical data. Validation should happen at both the frontend and backend.&lt;/p&gt;
&lt;p&gt;Frontend validation improves usability by identifying missing fields or incorrect formats before submission. Backend validation protects the application because requests can be sent without using the frontend at all.&lt;/p&gt;
&lt;p&gt;For example, a product form may require:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A non-empty product name.&lt;/li&gt;
&lt;li&gt;A valid non-negative price.&lt;/li&gt;
&lt;li&gt;A valid inventory quantity.&lt;/li&gt;
&lt;li&gt;A recognized product status.&lt;/li&gt;
&lt;li&gt;A valid category identifier.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A schema-validation library can express these rules in a reusable format.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import { z } from &quot;zod&quot;;

const productSchema = z.object({
  name: z.string().trim().min(1).max(150),
  price: z.number().finite().nonnegative(),
  stock: z.number().int().nonnegative(),
  status: z.enum([&quot;draft&quot;, &quot;active&quot;, &quot;archived&quot;])
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This schema illustrates the expected data shape. Your API must also handle JSON parsing, type conversion where appropriate, and validation failures safely.&lt;/p&gt;
&lt;p&gt;Do not rely on client-side checks for security. An attacker can bypass them and send requests directly to the API.&lt;/p&gt;
&lt;p&gt;For complex workflows, validate business rules as well as individual fields. For example, a product cannot necessarily be archived while it is part of an active fulfillment process.&lt;/p&gt;
&lt;p&gt;Good form design also includes clear field labels, sensible defaults, loading states, and protection against accidental duplicate submissions.&lt;/p&gt;
&lt;h3 id=&quot;11-add-file-uploads-carefully&quot;&gt;11. Add File Uploads Carefully&lt;/h3&gt;
&lt;p&gt;Many admin panels require image uploads, document attachments, profile pictures, or downloadable reports.&lt;/p&gt;
&lt;p&gt;File uploads introduce security and operational concerns beyond ordinary form submissions.&lt;/p&gt;
&lt;p&gt;A safe upload workflow should:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Authenticate the user and check upload permissions.&lt;/li&gt;
&lt;li&gt;Limit file size and permitted file types.&lt;/li&gt;
&lt;li&gt;Validate file content rather than trusting the filename or declared MIME type alone.&lt;/li&gt;
&lt;li&gt;Generate a safe server-side filename.&lt;/li&gt;
&lt;li&gt;Store files outside executable application directories or in an appropriate object-storage service.&lt;/li&gt;
&lt;li&gt;Restrict access to private files.&lt;/li&gt;
&lt;li&gt;Scan files for malware when the application's risk profile requires it.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For public product images, object storage with a CDN may be appropriate. For sensitive documents, private storage and short-lived authorized download links are often more suitable.&lt;/p&gt;
&lt;p&gt;Do not assume that changing a filename extension makes a file safe. Also avoid exposing uploaded files through unrestricted public URLs when they contain private information.&lt;/p&gt;
&lt;h3 id=&quot;12-build-audit-logs-and-activity-tracking&quot;&gt;12. Build Audit Logs and Activity Tracking&lt;/h3&gt;
&lt;p&gt;Administrative actions can affect customers, financial information, and business operations. Recording important changes helps teams investigate mistakes and detect suspicious behavior.&lt;/p&gt;
&lt;p&gt;An audit log might record:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The administrator's identity.&lt;/li&gt;
&lt;li&gt;The action performed.&lt;/li&gt;
&lt;li&gt;The affected resource.&lt;/li&gt;
&lt;li&gt;The timestamp.&lt;/li&gt;
&lt;li&gt;The outcome of the operation.&lt;/li&gt;
&lt;li&gt;Relevant request or correlation identifiers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, if an administrator changes a customer's account status, the audit record might identify the actor, the affected account, the action, and the time.&lt;/p&gt;
&lt;p&gt;Avoid recording passwords, session cookies, authentication tokens, or unnecessary personal information.&lt;/p&gt;
&lt;p&gt;Audit logs should be protected against unauthorized modification and deletion. For high-risk systems, consider storing them in a separate access-controlled system or forwarding them to centralized logging infrastructure.&lt;/p&gt;
&lt;p&gt;Where appropriate, record before-and-after values for important changes, while carefully excluding sensitive fields.&lt;/p&gt;
&lt;p&gt;For critical operations, the business change and its audit event should be written consistently. Database transactions or a transactional outbox pattern can help avoid situations where a change succeeds but its corresponding event is lost.&lt;/p&gt;
&lt;h3 id=&quot;13-protect-the-application-against-common-attacks&quot;&gt;13. Protect the Application Against Common Attacks&lt;/h3&gt;
&lt;p&gt;Security should be built into every layer rather than added at the end.&lt;/p&gt;
&lt;h4 id=&quot;sql-injection&quot;&gt;SQL injection&lt;/h4&gt;
&lt;p&gt;Use parameterized queries and safe database access methods. Never concatenate untrusted input into executable SQL.&lt;/p&gt;
&lt;h4 id=&quot;cross-site-scripting-xss-&quot;&gt;Cross-site scripting (XSS)&lt;/h4&gt;
&lt;p&gt;Render user-generated content safely. Avoid injecting raw HTML unless it has been sanitized with a well-maintained library and there is a clear need.&lt;/p&gt;
&lt;h4 id=&quot;cross-site-request-forgery&quot;&gt;Cross-site request forgery&lt;/h4&gt;
&lt;p&gt;For cookie-based authentication, implement suitable CSRF protection for state-changing requests. Use secure cookie settings and validate origins where appropriate.&lt;/p&gt;
&lt;h4 id=&quot;broken-access-control&quot;&gt;Broken access control&lt;/h4&gt;
&lt;p&gt;Check permissions on the server for every protected action. Test direct API requests, not only the visibility of buttons in the interface.&lt;/p&gt;
&lt;h4 id=&quot;brute-force-attacks&quot;&gt;Brute-force attacks&lt;/h4&gt;
&lt;p&gt;Apply rate limits to login attempts and other sensitive endpoints. Consider multi-factor authentication and monitoring for suspicious patterns.&lt;/p&gt;
&lt;h4 id=&quot;session-theft&quot;&gt;Session theft&lt;/h4&gt;
&lt;p&gt;Use HTTPS, secure cookies, sensible session expiration, session rotation where appropriate, and revocation mechanisms for logout and compromised accounts.&lt;/p&gt;
&lt;h4 id=&quot;sensitive-data-exposure&quot;&gt;Sensitive data exposure&lt;/h4&gt;
&lt;p&gt;Return only the fields necessary for the requested operation. Avoid sending password hashes, internal secrets, or unnecessary customer information to the browser.&lt;/p&gt;
&lt;h4 id=&quot;security-misconfiguration&quot;&gt;Security misconfiguration&lt;/h4&gt;
&lt;p&gt;Keep dependencies updated, protect environment variables, configure security headers, disable unnecessary production debugging, and restrict database network access.&lt;/p&gt;
&lt;p&gt;Security requirements should be documented and tested. A successful login test alone does not prove that the admin panel is secure.&lt;/p&gt;
&lt;h3 id=&quot;14-improve-performance-and-user-experience&quot;&gt;14. Improve Performance and User Experience&lt;/h3&gt;
&lt;p&gt;A useful admin panel should feel responsive even when it manages substantial amounts of data.&lt;/p&gt;
&lt;p&gt;Start by measuring the application's performance. Identify slow database queries, oversized frontend bundles, repeated API requests, and components that render unnecessarily.&lt;/p&gt;
&lt;p&gt;Several techniques can improve responsiveness.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Server-side pagination:&lt;/strong&gt; Fetch only the records needed for the current view.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Database indexing:&lt;/strong&gt; Support frequent filtering and sorting operations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Caching:&lt;/strong&gt; Cache appropriate read-heavy data, while defining clear invalidation rules for updates.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lazy loading:&lt;/strong&gt; Load larger page modules only when users navigate to them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Debounced search:&lt;/strong&gt; Avoid sending a new search request for every keystroke.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Optimistic updates:&lt;/strong&gt; Update the interface immediately for safe operations, then reconcile the result with the server.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Background jobs:&lt;/strong&gt; Move expensive tasks such as large exports, report generation, or bulk processing out of the main request cycle.&lt;/p&gt;
&lt;p&gt;Not every optimization is appropriate for every application. For example, caching frequently changing inventory counts without a clear invalidation strategy can show incorrect information.&lt;/p&gt;
&lt;p&gt;Good user experience also requires clear loading, empty, error, and success states. An empty table should explain that no records match the current filters instead of appearing broken.&lt;/p&gt;
&lt;h3 id=&quot;15-test-the-application-thoroughly&quot;&gt;15. Test the Application Thoroughly&lt;/h3&gt;
&lt;p&gt;Testing reduces the risk of administrative mistakes and unexpected production failures.&lt;/p&gt;
&lt;p&gt;Use several levels of testing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Unit tests:&lt;/strong&gt; Verify individual functions, validation rules, and business logic.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Integration tests:&lt;/strong&gt; Confirm that API endpoints interact correctly with the database and middleware.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;End-to-end tests:&lt;/strong&gt; Simulate workflows such as logging in, creating a product, updating a user, and generating a report.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Authorization tests:&lt;/strong&gt; Verify that each role can perform permitted actions and receives an appropriate denial for restricted actions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Security tests:&lt;/strong&gt; Check input validation, session handling, rate limits, and access-control boundaries.&lt;/p&gt;
&lt;p&gt;For example, an authorization test should verify that an editor cannot access an endpoint requiring &lt;code&gt;users.delete&lt;/code&gt;, even when the editor manually sends the request.&lt;/p&gt;
&lt;p&gt;Also test failure scenarios. What happens when the database is unavailable? What happens when two administrators update the same record? What happens when an upload exceeds the size limit?&lt;/p&gt;
&lt;p&gt;These situations matter because admin panels often control operations that cannot simply be ignored when something goes wrong.&lt;/p&gt;
&lt;p&gt;Use test databases or isolated test environments. Never run destructive automated tests against production data.&lt;/p&gt;
&lt;h3 id=&quot;16-deploy-the-admin-panel&quot;&gt;16. Deploy the Admin Panel&lt;/h3&gt;
&lt;p&gt;A custom admin panel typically requires a frontend hosting environment, an API server, and a database.&lt;/p&gt;
&lt;p&gt;A practical deployment workflow includes the following steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Configure separate development, staging, and production environments.&lt;/li&gt;
&lt;li&gt;Store secrets in environment variables or a secrets manager.&lt;/li&gt;
&lt;li&gt;Provision a managed PostgreSQL database or a properly maintained database server.&lt;/li&gt;
&lt;li&gt;Configure HTTPS and secure domain routing.&lt;/li&gt;
&lt;li&gt;Run automated tests in the deployment pipeline.&lt;/li&gt;
&lt;li&gt;Apply reviewed database migrations.&lt;/li&gt;
&lt;li&gt;Deploy the frontend and backend.&lt;/li&gt;
&lt;li&gt;Verify health checks, logging, and error monitoring.&lt;/li&gt;
&lt;li&gt;Confirm that backups are configured and restoration has been tested.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The deployment process should support reliable rollbacks. Database changes require special care because rolling back application code does not automatically reverse every schema change safely.&lt;/p&gt;
&lt;p&gt;Use a least-privilege database account for the application. Database administration credentials should not be embedded in frontend code or committed to a public repository.&lt;/p&gt;
&lt;p&gt;If the admin panel is intended only for internal staff, consider additional network restrictions or an identity-aware access gateway. Such controls should complement application-level authentication and authorization rather than replace them.&lt;/p&gt;
&lt;h3 id=&quot;17-monitor-and-maintain-the-system&quot;&gt;17. Monitor and Maintain the System&lt;/h3&gt;
&lt;p&gt;Deployment is not the end of development.&lt;/p&gt;
&lt;p&gt;Monitor API response times, error rates, database performance, failed login attempts, and background-job failures. Set alerts for conditions that could affect business operations.&lt;/p&gt;
&lt;p&gt;Keep dependencies updated and review security advisories. Apply database maintenance and verify backups regularly.&lt;/p&gt;
&lt;p&gt;Review administrative permissions as employees change responsibilities. Remove access when accounts are no longer needed and establish a process for responding to compromised accounts.&lt;/p&gt;
&lt;p&gt;Collect feedback from actual administrators. If users repeatedly struggle to find a feature, the navigation or information architecture may need improvement.&lt;/p&gt;
&lt;p&gt;For a growing application, consider adding configurable dashboards, bulk actions, export tools, notification workflows, and more advanced reporting. Introduce these features according to real operational needs rather than adding complexity without a clear purpose.&lt;/p&gt;
&lt;h3 id=&quot;18-common-mistakes-to-avoid&quot;&gt;18. Common Mistakes to Avoid&lt;/h3&gt;
&lt;p&gt;Several mistakes can make a custom admin panel difficult to maintain or unsafe to operate.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Building everything in one component:&lt;/strong&gt; Large components are difficult to test and reuse. Separate layouts, pages, forms, tables, and feature-specific logic.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Trusting the frontend for authorization:&lt;/strong&gt; Hidden buttons do not protect API endpoints. Every sensitive backend operation must enforce permissions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Skipping validation:&lt;/strong&gt; Validate incoming data on the server and enforce important integrity rules in the database.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ignoring auditability:&lt;/strong&gt; Important administrative changes should be traceable to an authenticated actor and a specific action.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Loading every record at once:&lt;/strong&gt; Use server-side pagination and appropriate database queries.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Overengineering the architecture:&lt;/strong&gt; Microservices, complex permission engines, and distributed caching are not automatically necessary. Start with a well-structured application and evolve when evidence justifies it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Neglecting accessibility:&lt;/strong&gt; Administrative tools are productivity software. Keyboard navigation, clear labels, readable tables, and understandable errors improve daily work.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Failing to test recovery procedures:&lt;/strong&gt; Backups are useful only when they can be restored successfully.&lt;/p&gt;
&lt;p&gt;Avoiding these mistakes early can save significant maintenance effort later.&lt;/p&gt;
&lt;h3 id=&quot;19-a-practical-development-roadmap&quot;&gt;19. A Practical Development Roadmap&lt;/h3&gt;
&lt;p&gt;If you are building your first custom admin panel, develop it in manageable stages.&lt;/p&gt;
&lt;h4 id=&quot;phase-1-foundation&quot;&gt;Phase 1: Foundation&lt;/h4&gt;
&lt;p&gt;Define the requirements, choose the stack, create the project structure, design the database, and configure development environments.&lt;/p&gt;
&lt;h4 id=&quot;phase-2-authentication-and-permissions&quot;&gt;Phase 2: Authentication and permissions&lt;/h4&gt;
&lt;p&gt;Implement secure login, session management, role definitions, permission checks, and account recovery where required.&lt;/p&gt;
&lt;h4 id=&quot;phase-3-core-administration&quot;&gt;Phase 3: Core administration&lt;/h4&gt;
&lt;p&gt;Build the dashboard layout, navigation, user management, and one complete CRUD module.&lt;/p&gt;
&lt;h4 id=&quot;phase-4-data-operations&quot;&gt;Phase 4: Data operations&lt;/h4&gt;
&lt;p&gt;Add search, filtering, pagination, sorting, validation, and carefully designed deletion or archival workflows.&lt;/p&gt;
&lt;h4 id=&quot;phase-5-operational-features&quot;&gt;Phase 5: Operational features&lt;/h4&gt;
&lt;p&gt;Implement audit logs, reports, file uploads, notifications, and background processing according to the application's requirements.&lt;/p&gt;
&lt;h4 id=&quot;phase-6-quality-and-security&quot;&gt;Phase 6: Quality and security&lt;/h4&gt;
&lt;p&gt;Add automated tests, review access control, inspect dependency risks, test performance, and verify error handling.&lt;/p&gt;
&lt;h4 id=&quot;phase-7-production-deployment&quot;&gt;Phase 7: Production deployment&lt;/h4&gt;
&lt;p&gt;Deploy to staging, verify migrations and backups, configure monitoring, and release to production with a rollback plan.&lt;/p&gt;
&lt;p&gt;This roadmap keeps the project focused. It also allows developers to deliver useful functionality before investing in optional features.&lt;/p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Building a custom admin panel without WordPress gives developers greater control over application architecture, data management, user permissions, and business workflows. With React, Node.js, Express, and PostgreSQL, it is possible to build an administrative interface that is flexible, maintainable, and capable of supporting complex operations.&lt;/p&gt;
&lt;p&gt;However, the quality of an admin panel depends on more than its appearance. Secure authentication, server-side authorization, data validation, reliable database design, audit logging, testing, and operational monitoring are equally important.&lt;/p&gt;
&lt;p&gt;The best approach is to begin with clearly defined requirements, build a modular foundation, and add features according to real user needs. A well-designed custom admin panel should simplify administrative work while protecting the data and operations it manages.&lt;/p&gt;
&lt;p&gt;WordPress remains a useful option for many content-driven websites, but a custom solution can be a better fit when an application needs specialized functionality or tighter control over its technical architecture.&lt;/p&gt;
&lt;p&gt;Ultimately, the goal is not to avoid a particular platform simply for the sake of it. The goal is to build an administrative system that solves the right problems, protects its users, and remains easy to improve as the application grows.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32925/how-to-build-a-custom-admin-panel-without-using-wordpress</guid>
<pubDate>Thu, 29 Oct 2026 04:26:52 +0000</pubDate>
</item>
<item>
<title>&quot;'use client' Doesn't Turn Your Whole App Into a Client Component (The Children Trick Nobody Explain</title>
<link>https://coderlegion.com/32265/use-client-doesnt-turn-your-whole-app-into-client-component-children-trick-nobody-explain</link>
<description>&lt;p&gt;You need a theme provider, so you create a file, add &lt;code&gt;'use client'&lt;/code&gt; at the top, and wrap your app in it.&lt;/p&gt;
&lt;p&gt;Then a nagging thought arrives: &quot;I just wrapped my entire layout in a client component. Did I turn the whole app into client-side React?&quot;&lt;/p&gt;
&lt;p&gt;Most developers either panic and avoid providers, or give up and put &lt;code&gt;'use client'&lt;/code&gt; at the top of every file &quot;just to be safe&quot;. Both are the wrong response, and the actual rule is simpler than you expect.&lt;/p&gt;
&lt;h3 id=&quot;the-setup&quot;&gt;The Setup&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// components/ThemeProvider.tsx
'use client';

import { createContext, useState } from 'react';

export const ThemeContext = createContext('light');

export function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme] = useState('light');
  return &amp;lt;ThemeContext value={theme}&amp;gt;{children}&amp;lt;/ThemeContext&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/layout.tsx  (a Server Component)
import { ThemeProvider } from '@/components/ThemeProvider';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    &amp;lt;html lang=&quot;en&quot;&amp;gt;
      &amp;lt;body&amp;gt;
        &amp;lt;ThemeProvider&amp;gt;{children}&amp;lt;/ThemeProvider&amp;gt;
      &amp;lt;/body&amp;gt;
    &amp;lt;/html&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ThemeProvider&lt;/code&gt; is a Client Component. It wraps every page. So are all the pages client components now?&lt;/p&gt;
&lt;p&gt;No.&lt;/p&gt;
&lt;h3 id=&quot;what-use-client-actually-does&quot;&gt;What 'use client' Actually Does&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;'use client'&lt;/code&gt; marks a &lt;strong&gt;boundary&lt;/strong&gt;. It says: &quot;this file, and everything this file &lt;em&gt;imports&lt;/em&gt;, is part of the JavaScript sent to the browser.&quot;&lt;/p&gt;
&lt;p&gt;The key word is &lt;em&gt;imports&lt;/em&gt;. The boundary follows the import graph, not the position in the rendered tree.&lt;/p&gt;
&lt;p&gt;In the layout above, &lt;code&gt;children&lt;/code&gt; is not imported by &lt;code&gt;ThemeProvider&lt;/code&gt;. It is &lt;strong&gt;passed in as a prop&lt;/strong&gt; by the layout, which is a Server Component. The server renders the page first, and hands the finished result to the client component as a slot to fill.&lt;/p&gt;
&lt;p&gt;So your pages stay Server Components. They can still fetch data on the server, use secrets, and ship no JavaScript for their static parts.&lt;/p&gt;
&lt;h3 id=&quot;the-mistake-that-does-change-things&quot;&gt;The Mistake That Does Change Things&lt;/h3&gt;
&lt;p&gt;Now look at this version:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// components/ThemeProvider.tsx
'use client';

import { Dashboard } from './Dashboard'; // imported, not passed in

export function ThemeProvider() {
  // ...
  return &amp;lt;Dashboard /&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here the client file &lt;strong&gt;imports&lt;/strong&gt; &lt;code&gt;Dashboard&lt;/code&gt;, so &lt;code&gt;Dashboard&lt;/code&gt; joins the client bundle, along with everything &lt;em&gt;it&lt;/em&gt; imports. If it was a Server Component that queried your database, it is no longer one. It will either break the build or quietly ship heavy code to every visitor.&lt;/p&gt;
&lt;p&gt;The difference between the two examples is a single word: &lt;strong&gt;import&lt;/strong&gt; versus &lt;strong&gt;prop&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;the-pattern-pass-server-components-through-as-children&quot;&gt;The Pattern: Pass Server Components Through as Children&lt;/h3&gt;
&lt;p&gt;Any time a client component needs to wrap server content, use a slot:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// components/Modal.tsx
'use client';

import { useState } from 'react';

export function Modal({ children }: { children: React.ReactNode }) {
  const [open, setOpen] = useState(false);
  return (
    &amp;lt;&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setOpen(true)}&amp;gt;Show stats&amp;lt;/button&amp;gt;
      {open &amp;amp;&amp;amp; &amp;lt;div role=&quot;dialog&quot;&amp;gt;{children}&amp;lt;/div&amp;gt;}
    &amp;lt;/&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/page.tsx  (Server Component)
import { Modal } from '@/components/Modal';
import { Stats } from '@/components/Stats'; // reads the database

export default function Page() {
  return (
    &amp;lt;Modal&amp;gt;
      &amp;lt;Stats /&amp;gt;
    &amp;lt;/Modal&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Stats&lt;/code&gt; runs on the server, even though it is rendered &lt;em&gt;inside&lt;/em&gt; a client component's JSX. The button and the toggle are interactive in the browser. The data never leaves the server except as finished markup.&lt;/p&gt;
&lt;p&gt;This works for any prop that holds JSX, not only &lt;code&gt;children&lt;/code&gt;. A &lt;code&gt;sidebar&lt;/code&gt; prop or a &lt;code&gt;header&lt;/code&gt; prop behaves the same way.&lt;/p&gt;
&lt;h3 id=&quot;rule-of-thumb-push-the-boundary-down&quot;&gt;Rule of Thumb: Push the Boundary Down&lt;/h3&gt;
&lt;p&gt;Put &lt;code&gt;'use client'&lt;/code&gt; on the smallest piece that needs interactivity, not on the page:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// Bad: the whole page becomes client code
'use client';
export default function ProductPage() { /* ...200 lines... */ }

// Better: only the button is client code
export default function ProductPage() {
  return (
    &amp;lt;main&amp;gt;
      &amp;lt;ProductDetails /&amp;gt;   {/* server */}
      &amp;lt;AddToCartButton /&amp;gt;  {/* 'use client' lives here */}
    &amp;lt;/main&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A page that is mostly text and images, with one button, should send JavaScript for the button, not for the page. Less JavaScript sent means a faster load, which is what I walk through in &lt;a rel=&quot;nofollow&quot; href=&quot;https://pixelanas.com/blog/core-web-vitals-explained&quot;&gt;this plain-language guide to Core Web Vitals&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;two-things-that-still-bite&quot;&gt;Two Things That Still Bite&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. Props crossing the boundary must be serializable.&lt;/strong&gt; Data goes from server to client as text, so you can pass strings, numbers, plain objects, arrays and dates. You cannot pass a regular function. (Server Actions are the exception, because they are designed to cross.)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// Error: functions cannot be passed to Client Components
&amp;lt;ClientButton onClick={() =&amp;gt; console.log('hi')} /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Define the handler inside the client component instead.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. A server-only module can leak into the client by import.&lt;/strong&gt; If a client file imports a helper that touches your database or secrets, the build either fails or bundles code you never meant to ship. The &lt;code&gt;server-only&lt;/code&gt; package turns that into a loud build error:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// lib/db.ts
import 'server-only';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Import this file from a client component and the build fails, instead of shipping the connection code.&lt;/p&gt;
&lt;h3 id=&quot;the-rule-1&quot;&gt;The Rule&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;'use client'&lt;/code&gt; marks an import boundary, not a rendering zone. Whatever a client file imports goes to the browser. Whatever you pass to it as props or children can stay on the server.&lt;/strong&gt; Keep the directive on small interactive leaves, and use the children slot for everything else.&lt;/p&gt;
&lt;p&gt;Quick audit: search your project for &lt;code&gt;'use client'&lt;/code&gt; and look at each file. If it is a page or a layout, ask whether it really needs state or browser APIs. If it is a wrapper, check that it receives its content as &lt;code&gt;children&lt;/code&gt; and does not import it.&lt;/p&gt;
&lt;p&gt;If you want a Next.js starting point built around the App Router, you can browse my &lt;a rel=&quot;nofollow&quot; href=&quot;https://pixelanas.com&quot;&gt;templates on pixelanas.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Get the templates:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://pixelanas.gumroad.com&quot;&gt;https://pixelanas.gumroad.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Where in your project is &lt;code&gt;'use client'&lt;/code&gt; doing more than it needs to? Drop it in the comments, and tell me if the children trick surprised you.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Anas, full-stack Next.js developer building SaaS products and premium templates. X: &lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/ASheikh69751&quot;&gt;@ASheikh69751&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32265/use-client-doesnt-turn-your-whole-app-into-client-component-children-trick-nobody-explain</guid>
<pubDate>Sat, 24 Oct 2026 11:28:48 +0000</pubDate>
</item>
<item>
<title>Building a File Upload System with React and Python</title>
<link>https://coderlegion.com/31930/building-a-file-upload-system-with-react-and-python</link>
<description>&lt;p&gt;File uploads are an essential feature of modern web applications. Whether users are uploading profile pictures, submitting documents, sharing reports, attaching files to messages, or managing media in a SaaS platform, a reliable file upload system plays an important role in the overall user experience.&lt;/p&gt;
&lt;p&gt;Although uploading a file might seem like a simple task, building a production-ready system requires careful planning. Developers must consider frontend usability, backend validation, security, storage management, error handling, performance, and scalability.&lt;/p&gt;
&lt;p&gt;React provides an interactive frontend for selecting files, displaying upload progress, and communicating with backend APIs. Python, particularly with FastAPI, provides an efficient way to receive files, validate their contents, enforce upload limits, and store them securely.&lt;/p&gt;
&lt;p&gt;In this article, we will explore how to build a complete file upload system using React and Python. We will examine the architecture, implementation details, security considerations, and strategies for preparing the system for production.&lt;/p&gt;
&lt;h3 id=&quot;1-understanding-the-file-upload-architecture&quot;&gt;1. Understanding the File Upload Architecture&lt;/h3&gt;
&lt;p&gt;A file upload system typically consists of three major components:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Frontend:&lt;/strong&gt; React handles file selection, previews, validation feedback, and upload progress.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Backend:&lt;/strong&gt; Python receives the uploaded file, performs validation, checks authorization, and manages the upload process.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Storage:&lt;/strong&gt; Files are saved in local storage, a private object storage service, or another appropriate storage solution.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The communication between these components generally follows an HTTP request-response pattern.&lt;/p&gt;
&lt;p&gt;When a user selects a file, React creates a &lt;code&gt;FormData&lt;/code&gt; object and sends it to the backend using an HTTP POST request. The backend validates the request and stores the file. After successful processing, the API returns a response containing information about the uploaded file.&lt;/p&gt;
&lt;p&gt;A typical workflow looks like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The user selects a file.&lt;/li&gt;
&lt;li&gt;React performs basic client-side validation.&lt;/li&gt;
&lt;li&gt;The frontend sends the file using multipart form data.&lt;/li&gt;
&lt;li&gt;FastAPI checks authentication, authorization, file size, and permitted file types.&lt;/li&gt;
&lt;li&gt;The backend generates a safe storage name.&lt;/li&gt;
&lt;li&gt;The file is stored in the configured storage system.&lt;/li&gt;
&lt;li&gt;The API returns a success response.&lt;/li&gt;
&lt;li&gt;React updates the interface with the upload result.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This separation of responsibilities makes the application easier to maintain and extend.&lt;/p&gt;
&lt;h3 id=&quot;2-setting-up-the-python-backend&quot;&gt;2. Setting Up the Python Backend&lt;/h3&gt;
&lt;p&gt;For this implementation, we will use FastAPI because it provides straightforward request handling, automatic API documentation, and support for asynchronous web applications.&lt;/p&gt;
&lt;p&gt;First, create a project directory:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir file-upload-system
cd file-upload-system

python -m venv venv
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Activate the virtual environment.&lt;/p&gt;
&lt;p&gt;On Windows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;venv\Scripts\activate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On Linux or macOS:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;source venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install the required dependencies:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pip install fastapi uvicorn python-multipart
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The packages serve different purposes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;fastapi&lt;/code&gt; provides the backend framework.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uvicorn&lt;/code&gt; runs the application.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;python-multipart&lt;/code&gt; enables multipart form-data parsing.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Create the following project structure:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;file-upload-system/
├── backend/
│   ├── main.py
│   └── uploads/
└── frontend/
    └── src/
        ├── App.jsx
        └── App.css
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;uploads&lt;/code&gt; directory will be used for local development. In a production deployment, private files should generally be stored outside publicly served application directories or in private object storage.&lt;/p&gt;
&lt;h3 id=&quot;3-building-the-file-upload-api-with-fastapi&quot;&gt;3. Building the File Upload API with FastAPI&lt;/h3&gt;
&lt;p&gt;The first step is to create an API endpoint that receives and stores uploaded files.&lt;/p&gt;
&lt;p&gt;Create &lt;code&gt;backend/main.py&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from pathlib import Path
from uuid import uuid4
import os

from fastapi import (
    FastAPI,
    File,
    HTTPException,
    UploadFile,
)
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI(title=&quot;File Upload API&quot;)

BASE_DIR = Path(__file__).resolve().parent
UPLOAD_DIR = BASE_DIR / &quot;uploads&quot;
UPLOAD_DIR.mkdir(parents=True, exist_ok=True)

MAX_FILE_SIZE = 5 * 1024 * 1024

ALLOWED_EXTENSIONS = {
    &quot;.pdf&quot;,
    &quot;.png&quot;,
    &quot;.jpg&quot;,
    &quot;.jpeg&quot;,
}

app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        &quot;http://localhost:5173&quot;
    ],
    allow_credentials=True,
    allow_methods=[&quot;GET&quot;, &quot;POST&quot;],
    allow_headers=[&quot;*&quot;],
)


@app.post(&quot;/api/upload&quot;)
async def upload_file(
    file: UploadFile = File(...)
):
    original_name = Path(
        file.filename or &quot;&quot;
    ).name

    extension = Path(
        original_name
    ).suffix.lower()

    if not original_name:
        raise HTTPException(
            status_code=400,
            detail=&quot;A filename is required.&quot;
        )

    if extension not in ALLOWED_EXTENSIONS:
        raise HTTPException(
            status_code=400,
            detail=&quot;File type is not allowed.&quot;
        )

    file_id = uuid4().hex
    stored_name = f&quot;{file_id}{extension}&quot;
    destination = UPLOAD_DIR / stored_name

    total_size = 0

    try:
        with destination.open(&quot;xb&quot;) as output:
            while True:
                chunk = await file.read(1024 * 1024)

                if not chunk:
                    break

                total_size += len(chunk)

                if total_size &amp;gt; MAX_FILE_SIZE:
                    raise HTTPException(
                        status_code=413,
                        detail=&quot;File exceeds the 5 MB limit.&quot;
                    )

                output.write(chunk)

        return {
            &quot;message&quot;: &quot;File uploaded successfully.&quot;,
            &quot;file_id&quot;: file_id,
            &quot;filename&quot;: original_name,
            &quot;size&quot;: total_size,
            &quot;content_type&quot;: file.content_type,
        }

    except HTTPException:
        destination.unlink(missing_ok=True)
        raise

    except OSError:
        destination.unlink(missing_ok=True)
        raise HTTPException(
            status_code=500,
            detail=&quot;Could not save the uploaded file.&quot;
        )

    finally:
        await file.close()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This endpoint provides a basic foundation for handling uploads.&lt;/p&gt;
&lt;h4 id=&quot;how-the-backend-works&quot;&gt;How the backend works&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;File reception:&lt;/strong&gt; FastAPI uses &lt;code&gt;UploadFile&lt;/code&gt; to receive the uploaded file. Unlike reading the entire file into a Python byte string, &lt;code&gt;UploadFile&lt;/code&gt; uses a spooled temporary file, which is more suitable for many upload scenarios.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Extension validation:&lt;/strong&gt; The code restricts uploads to PDF, PNG, JPG, and JPEG extensions. This helps enforce the application's expected file types, but it does not prove that the file actually contains valid content of that type.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Unique storage names:&lt;/strong&gt; A UUID generates a unique storage identifier. This prevents users from overwriting files simply by uploading files with identical names.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Size enforcement:&lt;/strong&gt; The endpoint reads the file in chunks and stops when the configured size limit is exceeded.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cleanup:&lt;/strong&gt; If writing fails or the file exceeds the limit, the partially written destination is removed.&lt;/p&gt;
&lt;p&gt;This example is suitable for learning and local development. A public production API needs additional safeguards, including authentication, authorization, content inspection, and limits at the reverse proxy or application server.&lt;/p&gt;
&lt;h3 id=&quot;4-creating-the-react-frontend&quot;&gt;4. Creating the React Frontend&lt;/h3&gt;
&lt;p&gt;Now that the backend is available, we can build a React interface that allows users to select and upload files.&lt;/p&gt;
&lt;p&gt;A Vite-based React project can be created with the following commands:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm create vite@latest frontend -- --template react
cd frontend
npm install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Start the development server:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create or replace &lt;code&gt;src/App.jsx&lt;/code&gt; with the following implementation:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import { useState } from &quot;react&quot;;
import &quot;./App.css&quot;;

const MAX_FILE_SIZE = 5 * 1024 * 1024;

const ALLOWED_TYPES = [
  &quot;application/pdf&quot;,
  &quot;image/png&quot;,
  &quot;image/jpeg&quot;,
];

export default function App() {
  const [file, setFile] = useState(null);
  const [status, setStatus] = useState(&quot;&quot;);
  const [error, setError] = useState(&quot;&quot;);
  const [uploading, setUploading] = useState(false);
  const [result, setResult] = useState(null);

  function handleFileChange(event) {
    const selectedFile = event.target.files?.[0];

    setError(&quot;&quot;);
    setStatus(&quot;&quot;);
    setResult(null);

    if (!selectedFile) {
      setFile(null);
      return;
    }

    if (selectedFile.size &amp;gt; MAX_FILE_SIZE) {
      setError(&quot;Please select a file smaller than 5 MB.&quot;);
      setFile(null);
      return;
    }

    if (!ALLOWED_TYPES.includes(selectedFile.type)) {
      setError(&quot;Only PDF, PNG, and JPEG files are allowed.&quot;);
      setFile(null);
      return;
    }

    setFile(selectedFile);
  }

  async function handleUpload() {
    if (!file || uploading) return;

    setUploading(true);
    setError(&quot;&quot;);
    setStatus(&quot;&quot;);
    setResult(null);

    const formData = new FormData();
    formData.append(&quot;file&quot;, file);

    try {
      const response = await fetch(
        &quot;http://localhost:8000/api/upload&quot;,
        {
          method: &quot;POST&quot;,
          body: formData,
        }
      );

      const data = await response.json();

      if (!response.ok) {
        throw new Error(
          data.detail || &quot;Upload failed.&quot;
        );
      }

      setResult(data);
      setStatus(&quot;File uploaded successfully!&quot;);
      setFile(null);
    } catch (err) {
      setError(
        err.message || &quot;Unable to upload the file.&quot;
      );
    } finally {
      setUploading(false);
    }
  }

  return (
    &amp;lt;main className=&quot;upload-page&quot;&amp;gt;
      &amp;lt;section className=&quot;upload-card&quot;&amp;gt;
        &amp;lt;h1&amp;gt;File Upload System&amp;lt;/h1&amp;gt;

        &amp;lt;p&amp;gt;
          Select a PDF, PNG, or JPEG file
          up to 5 MB.
        &amp;lt;/p&amp;gt;

        &amp;lt;label htmlFor=&quot;file-input&quot;&amp;gt;
          Choose a file
        &amp;lt;/label&amp;gt;

        &amp;lt;input
          id=&quot;file-input&quot;
          type=&quot;file&quot;
          accept=&quot;.pdf,.png,.jpg,.jpeg&quot;
          onChange={handleFileChange}
        /&amp;gt;

        {file &amp;amp;&amp;amp; (
          &amp;lt;div className=&quot;file-info&quot;&amp;gt;
            &amp;lt;strong&amp;gt;{file.name}&amp;lt;/strong&amp;gt;
            &amp;lt;span&amp;gt;
              {(file.size / 1024).toFixed(2)} KB
            &amp;lt;/span&amp;gt;
          &amp;lt;/div&amp;gt;
        )}

        &amp;lt;button
          type=&quot;button&quot;
          onClick={handleUpload}
          disabled={!file || uploading}
        &amp;gt;
          {uploading ? &quot;Uploading...&quot; : &quot;Upload File&quot;}
        &amp;lt;/button&amp;gt;

        {status &amp;amp;&amp;amp; (
          &amp;lt;p className=&quot;success&quot;&amp;gt;{status}&amp;lt;/p&amp;gt;
        )}

        {error &amp;amp;&amp;amp; (
          &amp;lt;p className=&quot;error&quot; role=&quot;alert&quot;&amp;gt;
            {error}
          &amp;lt;/p&amp;gt;
        )}

        {result &amp;amp;&amp;amp; (
          &amp;lt;div className=&quot;result&quot;&amp;gt;
            &amp;lt;h2&amp;gt;Upload Details&amp;lt;/h2&amp;gt;
            &amp;lt;p&amp;gt;File: {result.filename}&amp;lt;/p&amp;gt;
            &amp;lt;p&amp;gt;File ID: {result.file_id}&amp;lt;/p&amp;gt;
            &amp;lt;p&amp;gt;Size: {result.size} bytes&amp;lt;/p&amp;gt;
          &amp;lt;/div&amp;gt;
        )}
      &amp;lt;/section&amp;gt;
    &amp;lt;/main&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;important-implementation-details&quot;&gt;Important implementation details&lt;/h4&gt;
&lt;p&gt;The frontend uses React state to keep track of the selected file, upload status, errors, and successful results.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;FormData&lt;/code&gt; API prepares the request body in the format expected by FastAPI's &lt;code&gt;UploadFile&lt;/code&gt; parameter.&lt;/p&gt;
&lt;p&gt;Notice that the code does not manually set the &lt;code&gt;Content-Type&lt;/code&gt; header. The browser automatically generates the correct multipart boundary when sending a &lt;code&gt;FormData&lt;/code&gt; object. Setting this header manually can cause request parsing problems.&lt;/p&gt;
&lt;p&gt;The interface also disables the upload button while a request is in progress. This helps prevent accidental duplicate submissions.&lt;/p&gt;
&lt;p&gt;Client-side validation improves usability, but it must never replace server-side validation. Anyone can bypass browser restrictions and send requests directly to the API.&lt;/p&gt;
&lt;h3 id=&quot;5-styling-the-upload-interface&quot;&gt;5. Styling the Upload Interface&lt;/h3&gt;
&lt;p&gt;A clean interface helps users understand what they are uploading and whether the operation succeeded.&lt;/p&gt;
&lt;p&gt;Create &lt;code&gt;src/App.css&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: Arial, sans-serif;
  background: #f3f5f9;
  color: #1f2937;
}

.upload-page {
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 24px;
}

.upload-card {
  width: 100%;
  max-width: 520px;
  padding: 32px;
  border-radius: 16px;
  background: white;
  box-shadow: 0 10px 35px rgba(0, 0, 0, 0.08);
}

.upload-card h1 {
  margin-top: 0;
  font-size: 28px;
}

.upload-card p {
  line-height: 1.6;
  color: #596579;
}

.upload-card input {
  display: block;
  width: 100%;
  margin: 16px 0;
}

.upload-card button {
  width: 100%;
  padding: 13px 18px;
  border: none;
  border-radius: 8px;
  background: #2563eb;
  color: white;
  font-size: 16px;
  cursor: pointer;
}

.upload-card button:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

.file-info,
.result {
  margin: 18px 0;
  padding: 14px;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  overflow-wrap: anywhere;
}

.file-info {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.success {
  color: #15803d !important;
}

.error {
  color: #b91c1c !important;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This responsive layout works well as a starting point for a small application. A more advanced interface could include drag-and-drop uploads, file previews, progress indicators, cancellation controls, and a history of uploaded files.&lt;/p&gt;
&lt;h3 id=&quot;6-running-the-complete-application&quot;&gt;6. Running the Complete Application&lt;/h3&gt;
&lt;p&gt;Open two terminal windows.&lt;/p&gt;
&lt;p&gt;In the first terminal, start the backend:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd file-upload-system/backend

uvicorn main:app --reload --port 8000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the second terminal, start the React frontend:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd file-upload-system/frontend

npm run dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Open the frontend URL displayed by Vite, usually:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;http://localhost:5173
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Select a supported file and click the upload button.&lt;/p&gt;
&lt;p&gt;If the upload succeeds, the frontend displays the original filename, generated file identifier, and file size. The stored file should appear in the backend's &lt;code&gt;uploads&lt;/code&gt; directory.&lt;/p&gt;
&lt;p&gt;FastAPI also provides interactive API documentation at:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;http://localhost:8000/docs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The documentation is useful for testing the upload endpoint independently of React.&lt;/p&gt;
&lt;h3 id=&quot;7-understanding-file-upload-security&quot;&gt;7. Understanding File Upload Security&lt;/h3&gt;
&lt;p&gt;File uploads introduce security risks because users can submit arbitrary content to an application. A secure system must treat uploaded files as untrusted input.&lt;/p&gt;
&lt;h4 id=&quot;validate-file-size&quot;&gt;Validate file size&lt;/h4&gt;
&lt;p&gt;Large uploads can consume memory, disk space, bandwidth, and processing capacity. Establish clear upload limits and enforce them on the server.&lt;/p&gt;
&lt;p&gt;For larger files, use streaming or multipart upload techniques rather than loading entire files into memory.&lt;/p&gt;
&lt;p&gt;The example reads the file in chunks, but FastAPI and its underlying multipart parser may already have processed or spooled request data before the endpoint begins reading it. Therefore, production deployments should also configure request-body limits at the proxy or gateway and test oversized requests.&lt;/p&gt;
&lt;h4 id=&quot;validate-actual-file-content&quot;&gt;Validate actual file content&lt;/h4&gt;
&lt;p&gt;File extensions and browser-supplied MIME types are not trustworthy on their own. A malicious user can rename an executable or another unexpected file with a &lt;code&gt;.jpg&lt;/code&gt; extension.&lt;/p&gt;
&lt;p&gt;Depending on the supported file types, stronger validation can include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Checking file signatures or magic bytes.&lt;/li&gt;
&lt;li&gt;Parsing documents with appropriate libraries.&lt;/li&gt;
&lt;li&gt;Fully decoding uploaded images.&lt;/li&gt;
&lt;li&gt;Rejecting malformed or unexpected content.&lt;/li&gt;
&lt;li&gt;Running malware scanning where appropriate.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Do not assume that a valid signature guarantees a safe file. File parsers can contain vulnerabilities, and malicious files may have valid headers.&lt;/p&gt;
&lt;h4 id=&quot;generate-server-controlled-filenames&quot;&gt;Generate server-controlled filenames&lt;/h4&gt;
&lt;p&gt;Never rely on a user-provided filename as the storage path.&lt;/p&gt;
&lt;p&gt;A filename can contain unexpected characters, path separators, or misleading extensions. Generating a UUID or another server-controlled identifier reduces path traversal and collision risks.&lt;/p&gt;
&lt;p&gt;Keep the original filename separately as metadata if the application needs to display it to the user.&lt;/p&gt;
&lt;h4 id=&quot;restrict-access-to-uploaded-files&quot;&gt;Restrict access to uploaded files&lt;/h4&gt;
&lt;p&gt;Uploaded documents should not automatically become public.&lt;/p&gt;
&lt;p&gt;For private documents, verify that the authenticated user is authorized to access the requested file. Store ownership information in a database and enforce permissions on every download request.&lt;/p&gt;
&lt;p&gt;Avoid exposing internal storage paths or allowing users to guess file identifiers and retrieve other users' content.&lt;/p&gt;
&lt;h4 id=&quot;configure-cors-correctly&quot;&gt;Configure CORS correctly&lt;/h4&gt;
&lt;p&gt;Cross-Origin Resource Sharing determines which browser origins may access an API. In production, configure an explicit list of trusted origins.&lt;/p&gt;
&lt;p&gt;CORS is not authentication and does not prevent direct requests from scripts or other clients. Backend authentication and authorization are still required.&lt;/p&gt;
&lt;h3 id=&quot;8-adding-authentication-and-authorization&quot;&gt;8. Adding Authentication and Authorization&lt;/h3&gt;
&lt;p&gt;A production file upload service should usually require an authenticated user.&lt;/p&gt;
&lt;p&gt;For example, a FastAPI dependency can verify a bearer token before allowing an upload:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from fastapi import Depends, HTTPException
from fastapi.security import HTTPBearer

security = HTTPBearer()

async def get_current_user(
    credentials = Depends(security)
):
    token = credentials.credentials

    user = verify_access_token(token)

    if user is None:
        raise HTTPException(
            status_code=401,
            detail=&quot;Invalid or expired token.&quot;
        )

    return user
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, &lt;code&gt;verify_access_token&lt;/code&gt; represents application-specific token verification and must be implemented using a trusted authentication library and appropriate token-validation rules.&lt;/p&gt;
&lt;p&gt;The dependency can then be added to an endpoint:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;@app.post(&quot;/api/secure-upload&quot;)
async def secure_upload(
    file: UploadFile = File(...),
    current_user = Depends(get_current_user),
):
    # Validate and store the file.
    # Associate it with current_user.id.
    ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In a real application, the endpoint must also enforce quotas, validate content, and associate the stored file with the correct account.&lt;/p&gt;
&lt;p&gt;Authentication establishes who the user is. Authorization determines what that user is allowed to do. For example, an employee might be allowed to upload documents to their own workspace but not to another organization's workspace.&lt;/p&gt;
&lt;p&gt;These rules must be enforced by the backend, not merely by hiding buttons in React.&lt;/p&gt;
&lt;h3 id=&quot;9-choosing-the-right-storage-strategy&quot;&gt;9. Choosing the Right Storage Strategy&lt;/h3&gt;
&lt;p&gt;The storage solution should match the application's requirements.&lt;/p&gt;
&lt;h4 id=&quot;local-filesystem-storage&quot;&gt;Local filesystem storage&lt;/h4&gt;
&lt;p&gt;Local storage is convenient for development, prototypes, and certain single-server deployments.&lt;/p&gt;
&lt;p&gt;Advantages include straightforward implementation, low initial complexity, and direct access to files.&lt;/p&gt;
&lt;p&gt;However, local storage becomes difficult to manage when multiple application instances need access to the same files. Containers and ephemeral hosting environments may also lose stored data when instances are replaced.&lt;/p&gt;
&lt;h4 id=&quot;object-storage&quot;&gt;Object storage&lt;/h4&gt;
&lt;p&gt;Object storage is usually a better option for scalable applications. Services such as Amazon S3, Google Cloud Storage, and Azure Blob Storage provide durable storage designed for large collections of files.&lt;/p&gt;
&lt;p&gt;Advantages include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Independent scaling of application servers and file storage.&lt;/li&gt;
&lt;li&gt;Lifecycle policies for archiving and deleting files.&lt;/li&gt;
&lt;li&gt;Integration with access controls and encryption.&lt;/li&gt;
&lt;li&gt;Support for direct or multipart uploads.&lt;/li&gt;
&lt;li&gt;Easier distribution of approved public content through a CDN.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For private files, keep buckets private and provide authorized downloads through short-lived signed URLs or a backend download endpoint.&lt;/p&gt;
&lt;h4 id=&quot;database-metadata&quot;&gt;Database metadata&lt;/h4&gt;
&lt;p&gt;Large binary files are often stored outside a relational database, while file metadata is stored in PostgreSQL or another database.&lt;/p&gt;
&lt;p&gt;A metadata record might contain:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;id
owner_id
original_filename
storage_key
content_type
file_size
checksum
status
created_at
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This structure allows the application to search files, track ownership, manage retention, and enforce permissions without storing every file's bytes directly in a database table.&lt;/p&gt;
&lt;h3 id=&quot;10-uploading-directly-to-object-storage&quot;&gt;10. Uploading Directly to Object Storage&lt;/h3&gt;
&lt;p&gt;As an application grows, sending every file through the Python backend can increase server bandwidth usage and operational costs.&lt;/p&gt;
&lt;p&gt;A common alternative is a presigned-upload workflow.&lt;/p&gt;
&lt;p&gt;The process generally works as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;React requests permission to upload a file.&lt;/li&gt;
&lt;li&gt;The backend authenticates the user and checks upload permissions.&lt;/li&gt;
&lt;li&gt;The backend creates a short-lived presigned URL for an approved object key.&lt;/li&gt;
&lt;li&gt;React uploads the file directly to object storage.&lt;/li&gt;
&lt;li&gt;The application verifies that the object was uploaded successfully.&lt;/li&gt;
&lt;li&gt;The backend records the final metadata and marks the upload as complete.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This approach reduces the amount of file data passing through the application server.&lt;/p&gt;
&lt;p&gt;However, the backend must not blindly trust a client-side completion notification. It should verify the object, its size, and relevant metadata using the storage provider's API.&lt;/p&gt;
&lt;p&gt;Presigned URLs should be short-lived and scoped to a specific operation and object. They should not grant broader access than necessary.&lt;/p&gt;
&lt;h3 id=&quot;11-implementing-upload-progress&quot;&gt;11. Implementing Upload Progress&lt;/h3&gt;
&lt;p&gt;Users benefit from seeing how much of a file has been uploaded, particularly when working with larger documents.&lt;/p&gt;
&lt;p&gt;The basic &lt;code&gt;fetch&lt;/code&gt; API does not offer a widely supported browser interface for tracking upload-body progress in the same way that XMLHttpRequest does.&lt;/p&gt;
&lt;p&gt;One option is to use Axios:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install axios
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An upload function could look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import axios from &quot;axios&quot;;

async function uploadFile(file, token, onProgress) {
  const formData = new FormData();

  formData.append(&quot;file&quot;, file);

  const response = await axios.post(
    &quot;http://localhost:8000/api/upload&quot;,
    formData,
    {
      headers: {
        Authorization: `Bearer ${token}`,
      },
      onUploadProgress(event) {
        if (event.total) {
          const percentage = Math.round(
            (event.loaded / event.total) * 100
          );

          onProgress(percentage);
        }
      },
    }
  );

  return response.data;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This example assumes the application already has a valid access token and that the API requires bearer authentication. The token should come from the application's established authentication flow.&lt;/p&gt;
&lt;p&gt;React can store the reported percentage in state and render a progress bar.&lt;/p&gt;
&lt;p&gt;Keep in mind that upload progress measures the request body being transmitted. It does not necessarily mean that server-side validation, malware scanning, or storage processing has finished.&lt;/p&gt;
&lt;p&gt;A complete user experience should distinguish between uploading, processing, completed, and failed states.&lt;/p&gt;
&lt;h3 id=&quot;12-handling-errors-and-network-failures&quot;&gt;12. Handling Errors and Network Failures&lt;/h3&gt;
&lt;p&gt;Network requests can fail for many reasons, including interrupted connections, server errors, invalid files, expired authentication, and storage failures.&lt;/p&gt;
&lt;p&gt;A robust upload interface should display clear and actionable messages.&lt;/p&gt;
&lt;p&gt;Examples include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;400 Bad Request:&lt;/strong&gt; The uploaded file violates an application rule.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;401 Unauthorized:&lt;/strong&gt; The user must authenticate or renew their session.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;403 Forbidden:&lt;/strong&gt; The user does not have permission to perform the operation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;413 Content Too Large:&lt;/strong&gt; The request exceeds an upload limit.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;429 Too Many Requests:&lt;/strong&gt; The user has exceeded a rate limit.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;500 Internal Server Error:&lt;/strong&gt; An unexpected backend failure occurred.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Do not display raw stack traces or sensitive infrastructure details to users. Log technical details on the server and return a safe error message to the frontend.&lt;/p&gt;
&lt;p&gt;For retry support, use an explicit retry button or a carefully designed automatic retry policy. Avoid blindly retrying every failed POST request because the original upload may have succeeded even if the response was lost.&lt;/p&gt;
&lt;p&gt;For critical workflows, use upload identifiers, idempotency mechanisms, or a status-check endpoint to avoid creating duplicate files.&lt;/p&gt;
&lt;h3 id=&quot;13-supporting-multiple-file-uploads&quot;&gt;13. Supporting Multiple File Uploads&lt;/h3&gt;
&lt;p&gt;Some applications allow users to upload several documents at once.&lt;/p&gt;
&lt;p&gt;React can support this by enabling the &lt;code&gt;multiple&lt;/code&gt; attribute:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;&amp;lt;input
  type=&quot;file&quot;
  multiple
  accept=&quot;.pdf,.png,.jpg,.jpeg&quot;
  onChange={(event) =&amp;gt; {
    const selectedFiles = Array.from(
      event.target.files || []
    );

    console.log(selectedFiles);
  }}
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The backend can accept a list of files using FastAPI's &lt;code&gt;list[UploadFile]&lt;/code&gt; type.&lt;/p&gt;
&lt;p&gt;When implementing multi-file uploads, define limits for the number of files, individual file sizes, and total request size.&lt;/p&gt;
&lt;p&gt;It is also useful to track the status of each file independently. If one upload fails, the interface should indicate which file failed and allow the user to retry that file without unnecessarily uploading everything again.&lt;/p&gt;
&lt;p&gt;For large batches, limit concurrency rather than starting hundreds of simultaneous uploads. This reduces pressure on the browser, network, backend, and storage service.&lt;/p&gt;
&lt;h3 id=&quot;14-database-integration-with-postgresql&quot;&gt;14. Database Integration with PostgreSQL&lt;/h3&gt;
&lt;p&gt;A database becomes valuable when users need a file library, search functionality, ownership tracking, or administrative controls.&lt;/p&gt;
&lt;p&gt;A PostgreSQL table could be created as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE uploaded_files (
    id UUID PRIMARY KEY,
    owner_id UUID NOT NULL,
    original_filename TEXT NOT NULL,
    storage_key TEXT NOT NULL UNIQUE,
    content_type TEXT,
    file_size BIGINT NOT NULL,
    status TEXT NOT NULL DEFAULT 'pending',
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;owner_id&lt;/code&gt; should reference the application's user table in a complete schema. The database should also enforce appropriate constraints for valid statuses, file sizes, and other business rules.&lt;/p&gt;
&lt;p&gt;A typical upload workflow creates a pending record, stores the file, and updates the record after successful storage and validation.&lt;/p&gt;
&lt;p&gt;In a production application, failures must be handled carefully. For example, if the file is stored successfully but the database update fails, the application may need a cleanup job to identify orphaned objects.&lt;/p&gt;
&lt;p&gt;Similarly, if a database record is created but storage fails, the system should mark the upload as failed or remove the incomplete record according to its retention policy.&lt;/p&gt;
&lt;p&gt;Because database transactions do not automatically include external object storage, reliable systems often use explicit upload states and background reconciliation jobs.&lt;/p&gt;
&lt;h3 id=&quot;15-background-processing-and-file-scanning&quot;&gt;15. Background Processing and File Scanning&lt;/h3&gt;
&lt;p&gt;Not every upload can be processed immediately.&lt;/p&gt;
&lt;p&gt;An application might need to generate image thumbnails, extract PDF text, inspect document contents, or scan files for malware.&lt;/p&gt;
&lt;p&gt;Performing these operations inside the main HTTP request can increase response times and reduce API throughput.&lt;/p&gt;
&lt;p&gt;A more scalable design places uploaded files into a processing workflow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Receive and validate the initial request.&lt;/li&gt;
&lt;li&gt;Store the file in a private location.&lt;/li&gt;
&lt;li&gt;Record the upload as pending.&lt;/li&gt;
&lt;li&gt;Submit a background job.&lt;/li&gt;
&lt;li&gt;Process or scan the file.&lt;/li&gt;
&lt;li&gt;Update the status to approved, rejected, or failed.&lt;/li&gt;
&lt;li&gt;Notify the frontend when the result becomes available.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Python tools such as Celery or other task queue systems can help manage background processing.&lt;/p&gt;
&lt;p&gt;The worker should operate with limited permissions and should not execute uploaded files. Files that fail security checks should remain inaccessible to normal users and be handled according to a documented quarantine and deletion policy.&lt;/p&gt;
&lt;h3 id=&quot;16-testing-the-file-upload-system&quot;&gt;16. Testing the File Upload System&lt;/h3&gt;
&lt;p&gt;Testing helps prevent regressions and reveals security weaknesses before deployment.&lt;/p&gt;
&lt;h4 id=&quot;backend-tests&quot;&gt;Backend tests&lt;/h4&gt;
&lt;p&gt;Use a test client to verify that the API accepts valid files and rejects invalid requests.&lt;/p&gt;
&lt;p&gt;Test cases should include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A valid PDF within the configured size limit.&lt;/li&gt;
&lt;li&gt;An unsupported file extension.&lt;/li&gt;
&lt;li&gt;An empty file.&lt;/li&gt;
&lt;li&gt;A file that exceeds the size limit.&lt;/li&gt;
&lt;li&gt;A malformed image or document.&lt;/li&gt;
&lt;li&gt;A missing file field.&lt;/li&gt;
&lt;li&gt;An unauthenticated request.&lt;/li&gt;
&lt;li&gt;An authenticated user attempting to access another user's file.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Also test cleanup behavior when writing or validation fails.&lt;/p&gt;
&lt;h4 id=&quot;frontend-tests&quot;&gt;Frontend tests&lt;/h4&gt;
&lt;p&gt;React tests should verify that the interface displays validation messages, disables the upload button during submission, handles failed responses, and shows the success result.&lt;/p&gt;
&lt;p&gt;Test network interruptions and expired authentication as well as ordinary success scenarios.&lt;/p&gt;
&lt;h4 id=&quot;integration-tests&quot;&gt;Integration tests&lt;/h4&gt;
&lt;p&gt;Integration tests confirm that the frontend and backend agree on request field names, response structures, allowed file types, and error formats.&lt;/p&gt;
&lt;p&gt;A file upload system may work correctly in isolation while failing in production because of CORS configuration, reverse-proxy limits, storage permissions, or deployment differences.&lt;/p&gt;
&lt;h3 id=&quot;17-performance-and-scalability-best-practices&quot;&gt;17. Performance and Scalability Best Practices&lt;/h3&gt;
&lt;p&gt;A reliable upload system should be designed around expected file sizes, traffic patterns, and operational requirements.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stream data where appropriate.&lt;/strong&gt; Avoid unnecessarily loading large files entirely into application memory. Chunked reading and direct-to-object-storage uploads can reduce resource pressure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use upload limits at multiple layers.&lt;/strong&gt; Application checks should be combined with gateway or reverse-proxy limits to protect the service from oversized requests.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Apply rate limiting and quotas.&lt;/strong&gt; Prevent a single user or automated client from consuming excessive bandwidth or storage.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep storage independent.&lt;/strong&gt; For distributed deployments, use shared durable storage rather than depending on a single application instance's local filesystem.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use background workers.&lt;/strong&gt; Move expensive scanning, conversion, and extraction tasks out of the main request-response path.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Monitor resource consumption.&lt;/strong&gt; Track upload success rates, file sizes, processing latency, storage costs, and failed jobs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Plan for cleanup.&lt;/strong&gt; Define retention periods for temporary uploads, abandoned multipart sessions, rejected files, and orphaned objects.&lt;/p&gt;
&lt;p&gt;These practices help maintain predictable performance as usage grows.&lt;/p&gt;
&lt;h3 id=&quot;18-deploying-the-application-to-production&quot;&gt;18. Deploying the Application to Production&lt;/h3&gt;
&lt;p&gt;Before deploying the system, review the entire upload lifecycle.&lt;/p&gt;
&lt;p&gt;Configure environment-specific settings rather than hardcoding storage credentials, secret keys, or production URLs in the source code.&lt;/p&gt;
&lt;p&gt;Use HTTPS to protect data in transit. Keep private files outside publicly accessible directories and ensure that download endpoints enforce authorization.&lt;/p&gt;
&lt;p&gt;Configure reverse-proxy body-size limits consistently with the application's intended upload policy. Verify that the hosting platform, application server, and storage service all support the expected file sizes.&lt;/p&gt;
&lt;p&gt;Add structured logging and monitoring while avoiding the logging of authentication tokens, sensitive file contents, or unnecessary personal information.&lt;/p&gt;
&lt;p&gt;A production deployment should also include dependency updates, backups or appropriate storage durability controls, database migrations, and a recovery plan for failed processing jobs.&lt;/p&gt;
&lt;p&gt;Finally, test the application under realistic conditions. A successful upload on a developer's machine does not prove that the system will work correctly with multiple concurrent users or interrupted network connections.&lt;/p&gt;
&lt;h3 id=&quot;conclusion-1&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Building a file upload system with React and Python involves much more than selecting a file and sending an HTTP request. A dependable implementation needs a well-designed API, clear frontend feedback, strict server-side validation, secure storage, appropriate access controls, and reliable error handling.&lt;/p&gt;
&lt;p&gt;React makes it possible to create an intuitive interface for selecting files, reporting progress, and displaying upload results. FastAPI provides a practical foundation for receiving and validating files, while PostgreSQL and object storage can support ownership, metadata management, and long-term scalability.&lt;/p&gt;
&lt;p&gt;For a small application, a local-storage implementation can be a useful starting point. As the application grows, private object storage, presigned uploads, background processing, database-backed metadata, and stronger security controls can provide a more scalable architecture.&lt;/p&gt;
&lt;p&gt;The most important principle is to treat every uploaded file as untrusted input. Validate it on the backend, enforce permissions consistently, limit resource consumption, and design for failure from the beginning.&lt;/p&gt;
&lt;p&gt;With these practices in place, developers can build file upload systems that are maintainable, secure, and ready to evolve alongside modern web applications.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31930/building-a-file-upload-system-with-react-and-python</guid>
<pubDate>Wed, 21 Oct 2026 06:50:08 +0000</pubDate>
</item>
<item>
<title>How to Build a Multi User Dashboard with Role-Based Access</title>
<link>https://coderlegion.com/31802/how-to-build-a-multi-user-dashboard-with-role-based-access</link>
<description>&lt;p&gt;Modern web applications often serve multiple types of users, including administrators, managers, employees, customers, and external partners. Although these users may work within the same application, they do not necessarily need access to the same features, information, or operations.&lt;/p&gt;
&lt;p&gt;For example, an administrator might need to manage users, review reports, and configure application settings. A manager may need to monitor team performance and assign tasks, while an employee may only need to view assigned projects and update their progress. Giving every user the same level of access creates unnecessary security risks and makes the application more difficult to manage.&lt;/p&gt;
&lt;p&gt;This is where a multi-user dashboard with Role-Based Access Control (RBAC) becomes valuable.&lt;/p&gt;
&lt;p&gt;RBAC is a security approach that assigns permissions to roles rather than granting individual permissions to every user separately. When a user signs in, the application determines their role and allows them to access the appropriate features and resources.&lt;/p&gt;
&lt;p&gt;In this article, we will explore how to build a secure, scalable multi-user dashboard using React, FastAPI, PostgreSQL, and JSON Web Tokens (JWT). We will examine the architecture, database schema, authentication workflow, frontend implementation, backend authorization, testing strategies, and deployment considerations.&lt;/p&gt;
&lt;p&gt;By the end, you will understand how to create a dashboard that supports multiple user roles while maintaining a clear separation between authentication, authorization, and business logic.&lt;/p&gt;
&lt;h3 id=&quot;1-understanding-multi-user-dashboards&quot;&gt;1. Understanding Multi-User Dashboards&lt;/h3&gt;
&lt;p&gt;A multi-user dashboard is a centralized interface where different users can interact with an application according to their responsibilities and access rights.&lt;/p&gt;
&lt;p&gt;Unlike a basic dashboard that displays identical information to everyone, a role-aware dashboard adapts its navigation, available actions, and data visibility to the authenticated user.&lt;/p&gt;
&lt;p&gt;Consider a project management platform with three roles:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Administrator&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create, update, and deactivate user accounts.&lt;/li&gt;
&lt;li&gt;Assign roles and permissions.&lt;/li&gt;
&lt;li&gt;Access organization-wide reports.&lt;/li&gt;
&lt;li&gt;Configure application settings.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Manager&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;View team performance.&lt;/li&gt;
&lt;li&gt;Create and assign tasks.&lt;/li&gt;
&lt;li&gt;Review employee progress.&lt;/li&gt;
&lt;li&gt;Access reports related to their team.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Employee&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;View assigned tasks.&lt;/li&gt;
&lt;li&gt;Update task status.&lt;/li&gt;
&lt;li&gt;Edit permitted profile information.&lt;/li&gt;
&lt;li&gt;Access personal performance metrics.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each role has a different set of responsibilities. The dashboard should reflect those differences without requiring separate applications for every user type.&lt;/p&gt;
&lt;p&gt;A well-designed multi-user dashboard offers several benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Improved security:&lt;/strong&gt; Users receive only the permissions necessary for their responsibilities.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Better user experience:&lt;/strong&gt; Unnecessary menus and actions remain hidden.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simplified administration:&lt;/strong&gt; Permissions can be managed centrally.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scalability:&lt;/strong&gt; New roles and features can be introduced without redesigning the entire application.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Maintainability:&lt;/strong&gt; Access rules remain separate from presentation components and business logic.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However, hiding a button is not sufficient security. The backend must independently validate every protected operation.&lt;/p&gt;
&lt;h3 id=&quot;2-choosing-the-technology-stack&quot;&gt;2. Choosing the Technology Stack&lt;/h3&gt;
&lt;p&gt;Before writing code, it is important to choose technologies that support maintainability, security, and future growth.&lt;/p&gt;
&lt;p&gt;For this project, we will use the following stack.&lt;/p&gt;
&lt;h4 id=&quot;react&quot;&gt;React&lt;/h4&gt;
&lt;p&gt;React provides a component-based approach to building interactive interfaces. Components such as navigation menus, data tables, statistics cards, and protected pages can be reused across different parts of the dashboard.&lt;/p&gt;
&lt;p&gt;React is particularly useful when different roles require different interfaces but share common layout components.&lt;/p&gt;
&lt;h4 id=&quot;fastapi&quot;&gt;FastAPI&lt;/h4&gt;
&lt;p&gt;FastAPI is a Python framework for building APIs. It supports request validation, dependency injection, asynchronous programming, and automatic API documentation.&lt;/p&gt;
&lt;p&gt;We can use FastAPI dependencies to verify authenticated users and enforce role-based permissions before executing protected endpoints.&lt;/p&gt;
&lt;h4 id=&quot;postgresql&quot;&gt;PostgreSQL&lt;/h4&gt;
&lt;p&gt;PostgreSQL stores users, roles, permissions, and application data. Its relational structure makes it suitable for applications where users belong to teams or organizations and access depends on several related records.&lt;/p&gt;
&lt;h4 id=&quot;sqlalchemy&quot;&gt;SQLAlchemy&lt;/h4&gt;
&lt;p&gt;SQLAlchemy provides an object-relational mapping layer that allows Python code to interact with PostgreSQL using models and database queries.&lt;/p&gt;
&lt;h4 id=&quot;jwt-and-password-hashing&quot;&gt;JWT and password hashing&lt;/h4&gt;
&lt;p&gt;JWT access tokens can be used to authenticate API requests. Passwords should be stored using a dedicated password-hashing algorithm such as Argon2id or bcrypt, never as plain text.&lt;/p&gt;
&lt;h4 id=&quot;additional-tools&quot;&gt;Additional tools&lt;/h4&gt;
&lt;p&gt;A production application may also use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;React Router for client-side navigation.&lt;/li&gt;
&lt;li&gt;Pydantic for data validation.&lt;/li&gt;
&lt;li&gt;Alembic for database migrations.&lt;/li&gt;
&lt;li&gt;Pytest for backend tests.&lt;/li&gt;
&lt;li&gt;Vitest and React Testing Library for frontend tests.&lt;/li&gt;
&lt;li&gt;Docker for consistent deployment environments.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The specific libraries can change, but the underlying principles of authentication, authorization, and least-privilege access remain the same.&lt;/p&gt;
&lt;h3 id=&quot;3-designing-the-application-architecture&quot;&gt;3. Designing the Application Architecture&lt;/h3&gt;
&lt;p&gt;A reliable dashboard separates the user interface, API, database, and authorization logic.&lt;/p&gt;
&lt;p&gt;The basic request flow looks like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A user opens the React application.&lt;/li&gt;
&lt;li&gt;The user submits their login credentials.&lt;/li&gt;
&lt;li&gt;FastAPI verifies the credentials.&lt;/li&gt;
&lt;li&gt;The server issues an appropriate authentication token or session.&lt;/li&gt;
&lt;li&gt;React requests the current user's profile and permitted dashboard data.&lt;/li&gt;
&lt;li&gt;FastAPI authenticates each request and checks the required permissions.&lt;/li&gt;
&lt;li&gt;PostgreSQL returns only the data the user is authorized to access.&lt;/li&gt;
&lt;li&gt;React renders the permitted information and available actions.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This separation is important because the frontend should never be the ultimate authority for access decisions.&lt;/p&gt;
&lt;p&gt;For example, an employee might modify a JavaScript variable to pretend to be an administrator. If the backend trusts the role supplied by the browser, the employee could access restricted functionality.&lt;/p&gt;
&lt;p&gt;Instead, the backend must derive the authenticated identity from a verified session or token and enforce permissions independently.&lt;/p&gt;
&lt;p&gt;A practical project structure could look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;multi-user-dashboard/
├── frontend/
│   ├── src/
│   │   ├── components/
│   │   │   ├── Sidebar.jsx
│   │   │   ├── Header.jsx
│   │   │   └── ProtectedRoute.jsx
│   │   ├── pages/
│   │   │   ├── Login.jsx
│   │   │   ├── Dashboard.jsx
│   │   │   ├── Users.jsx
│   │   │   └── Reports.jsx
│   │   ├── services/
│   │   │   └── api.js
│   │   ├── auth/
│   │   │   └── AuthContext.jsx
│   │   └── App.jsx
│   └── package.json
├── backend/
│   ├── app/
│   │   ├── main.py
│   │   ├── models.py
│   │   ├── schemas.py
│   │   ├── database.py
│   │   ├── security.py
│   │   ├── permissions.py
│   │   └── routes/
│   │       ├── auth.py
│   │       ├── users.py
│   │       └── reports.py
│   └── requirements.txt
├── tests/
└── docker-compose.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This organization keeps related responsibilities together and prevents the main application file from becoming difficult to maintain.&lt;/p&gt;
&lt;h3 id=&quot;4-designing-the-database-schema&quot;&gt;4. Designing the Database Schema&lt;/h3&gt;
&lt;p&gt;Database design is one of the most important parts of a role-based dashboard.&lt;/p&gt;
&lt;p&gt;A simple application can begin with a users table containing a role column. However, a growing application benefits from separate tables for users, roles, and permissions.&lt;/p&gt;
&lt;p&gt;A flexible design uses four main tables:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Users:&lt;/strong&gt; Stores account information.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Roles:&lt;/strong&gt; Defines roles such as administrator, manager, and employee.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Permissions:&lt;/strong&gt; Defines individual actions, such as &lt;code&gt;users.read&lt;/code&gt;, &lt;code&gt;users.manage&lt;/code&gt;, and &lt;code&gt;reports.read&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Role permissions:&lt;/strong&gt; Connects roles to their permitted actions.&lt;/p&gt;
&lt;p&gt;The relationships can be represented as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Users
  |
  | role_id
  v
Roles
  |
  | role_id
  v
RolePermissions
  |
  | permission_id
  v
Permissions
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A simplified SQL schema might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE roles (
    id SERIAL PRIMARY KEY,
    name VARCHAR(50) UNIQUE NOT NULL
);

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL,
    password_hash TEXT NOT NULL,
    role_id INTEGER NOT NULL REFERENCES roles(id),
    is_active BOOLEAN NOT NULL DEFAULT TRUE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE permissions (
    id SERIAL PRIMARY KEY,
    code VARCHAR(100) UNIQUE NOT NULL,
    description TEXT
);

CREATE TABLE role_permissions (
    role_id INTEGER NOT NULL REFERENCES roles(id)
        ON DELETE CASCADE,
    permission_id INTEGER NOT NULL REFERENCES permissions(id)
        ON DELETE CASCADE,
    PRIMARY KEY (role_id, permission_id)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In a real application, database migrations should create and modify these tables instead of relying on ad hoc manual changes.&lt;/p&gt;
&lt;p&gt;The schema can be extended with organizations, teams, projects, and audit logs when the business requirements demand them.&lt;/p&gt;
&lt;p&gt;It is also important to distinguish role-based permissions from data ownership. A manager may have permission to read reports but should not automatically be able to read reports belonging to every organization.&lt;/p&gt;
&lt;h3 id=&quot;5-implementing-authentication-with-fastapi&quot;&gt;5. Implementing Authentication with FastAPI&lt;/h3&gt;
&lt;p&gt;Authentication answers the question: Who is making this request?&lt;/p&gt;
&lt;p&gt;Authorization answers a different question: What is this user allowed to do?&lt;/p&gt;
&lt;p&gt;These concepts should remain separate.&lt;/p&gt;
&lt;p&gt;First, install the necessary backend packages:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pip install fastapi uvicorn sqlalchemy \
    psycopg[binary] pyjwt pwdlib[argon2]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The precise package versions should be pinned and reviewed for compatibility in the project's dependency file.&lt;/p&gt;
&lt;h4 id=&quot;hashing-passwords&quot;&gt;Hashing passwords&lt;/h4&gt;
&lt;p&gt;Passwords must never be stored in plain text. Password hashing transforms a password into a one-way representation that can be verified later.&lt;/p&gt;
&lt;p&gt;A simplified example using &lt;code&gt;pwdlib&lt;/code&gt; is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from pwdlib import PasswordHash

password_hasher = PasswordHash.recommended()

def hash_password(password: str) -&amp;gt; str:
    return password_hasher.hash(password)

def verify_password(
    password: str,
    password_hash: str
) -&amp;gt; bool:
    return password_hasher.verify(
        password,
        password_hash
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When registering a user, save the resulting hash rather than the original password.&lt;/p&gt;
&lt;p&gt;When logging in, retrieve the account by email and verify the submitted password against the stored hash.&lt;/p&gt;
&lt;p&gt;The registration process should also validate email addresses, enforce suitable password policies, and avoid exposing sensitive details in error messages.&lt;/p&gt;
&lt;h4 id=&quot;creating-an-authentication-endpoint&quot;&gt;Creating an authentication endpoint&lt;/h4&gt;
&lt;p&gt;The login endpoint should validate credentials, check that the account is active, and establish an authenticated session.&lt;/p&gt;
&lt;p&gt;A simplified endpoint structure is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from fastapi import APIRouter, HTTPException, status

router = APIRouter(prefix=&quot;/auth&quot;, tags=[&quot;Authentication&quot;])

@router.post(&quot;/login&quot;)
def login(credentials: LoginRequest):
    user = find_user_by_email(credentials.email)

    if (
        user is None
        or not verify_password(
            credentials.password,
            user.password_hash
        )
        or not user.is_active
    ):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail=&quot;Invalid credentials&quot;
        )

    return create_authenticated_session(user)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The functions and request schema shown here represent application-specific helpers that must be implemented using the database and security configuration.&lt;/p&gt;
&lt;p&gt;For JWT-based authentication, the server signs tokens using a securely managed key and a suitable signing algorithm. Tokens should include an expiration time and an appropriate subject identifier.&lt;/p&gt;
&lt;p&gt;The application must validate the signature, expiration, issuer, audience, and other relevant claims.&lt;/p&gt;
&lt;p&gt;For browser applications, an alternative is a server-managed session in a cookie configured with &lt;code&gt;HttpOnly&lt;/code&gt;, &lt;code&gt;Secure&lt;/code&gt;, and an appropriate &lt;code&gt;SameSite&lt;/code&gt; policy. Cookie-based authentication also requires appropriate CSRF protections.&lt;/p&gt;
&lt;p&gt;Neither approach should expose long-lived credentials to unnecessary JavaScript access.&lt;/p&gt;
&lt;h3 id=&quot;6-building-the-authorization-layer&quot;&gt;6. Building the Authorization Layer&lt;/h3&gt;
&lt;p&gt;Authorization is where role-based access control becomes effective.&lt;/p&gt;
&lt;p&gt;Instead of writing separate role checks in every API endpoint, create reusable dependencies that verify a user's permissions.&lt;/p&gt;
&lt;p&gt;Consider a FastAPI dependency:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from fastapi import Depends, HTTPException, status

def require_permission(permission_code: str):
    def permission_checker(
        current_user = Depends(get_current_user)
    ):
        if not user_has_permission(
            current_user,
            permission_code
        ):
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail=&quot;Insufficient permissions&quot;
            )

        return current_user

    return permission_checker
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, &lt;code&gt;get_current_user&lt;/code&gt; must validate the incoming session or token, while &lt;code&gt;user_has_permission&lt;/code&gt; must resolve the user's current effective permissions.&lt;/p&gt;
&lt;p&gt;A protected endpoint can then declare its access requirement:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;@router.get(&quot;/users&quot;)
def list_users(
    current_user = Depends(
        require_permission(&quot;users.read&quot;)
    )
):
    return get_authorized_users(current_user)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The example assumes that the database query and data-scope restrictions are implemented correctly.&lt;/p&gt;
&lt;p&gt;The key advantage is consistency. Every endpoint using the dependency follows the same authorization policy.&lt;/p&gt;
&lt;p&gt;An administrator can be granted &lt;code&gt;users.read&lt;/code&gt; and &lt;code&gt;users.manage&lt;/code&gt;, while a manager might receive &lt;code&gt;users.read&lt;/code&gt; only for team members. An employee may have neither permission.&lt;/p&gt;
&lt;p&gt;For a small application, a role-based check can be sufficient:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def require_role(*allowed_roles: str):
    def role_checker(
        current_user = Depends(get_current_user)
    ):
        if current_user.role.name not in allowed_roles:
            raise HTTPException(
                status_code=403,
                detail=&quot;Access denied&quot;
            )

        return current_user

    return role_checker
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, permission-based checks generally offer greater flexibility as the number of features and roles increases.&lt;/p&gt;
&lt;p&gt;A critical rule is that the backend must never trust a role name submitted by the frontend. It should derive authorization from a verified identity and trusted server-side data.&lt;/p&gt;
&lt;h3 id=&quot;7-implementing-role-based-permissions&quot;&gt;7. Implementing Role-Based Permissions&lt;/h3&gt;
&lt;p&gt;A permission model should describe actions rather than merely naming users.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Permission &lt;/th&gt;&lt;th&gt;Administrator &lt;/th&gt;&lt;th&gt;Manager &lt;/th&gt;&lt;th&gt;Employee&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;View own dashboard &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Manage all users &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;View team reports &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;View organization reports &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Update assigned tasks &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Change application settings &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;These are example policies, not universal rules. A real project should define permissions according to its business requirements.&lt;/p&gt;
&lt;p&gt;A permission can follow a naming convention such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;users.read&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;users.create&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;users.update&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;users.delete&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tasks.read&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tasks.update&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reports.read&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;settings.manage&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This structure makes the policy easier to understand and extend.&lt;/p&gt;
&lt;p&gt;Instead of scattering conditions throughout the codebase, maintain a central authorization layer and test the policy systematically.&lt;/p&gt;
&lt;h4 id=&quot;avoiding-permission-escalation&quot;&gt;Avoiding permission escalation&lt;/h4&gt;
&lt;p&gt;Only authorized administrators should be able to assign privileged roles.&lt;/p&gt;
&lt;p&gt;For example, an employee update endpoint should not accept unrestricted changes to fields such as &lt;code&gt;role_id&lt;/code&gt;, &lt;code&gt;is_active&lt;/code&gt;, or &lt;code&gt;is_superuser&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Use separate request schemas for ordinary profile updates and administrative account management.&lt;/p&gt;
&lt;p&gt;Always validate the target user's permissions and the acting user's authority before changing roles.&lt;/p&gt;
&lt;p&gt;Role changes should also be logged so that unexpected privilege escalation can be investigated.&lt;/p&gt;
&lt;h3 id=&quot;8-building-the-react-authentication-context&quot;&gt;8. Building the React Authentication Context&lt;/h3&gt;
&lt;p&gt;The React frontend needs to know whether the user is authenticated and which interface to display.&lt;/p&gt;
&lt;p&gt;An authentication context can share this information between components without passing user details through every component manually.&lt;/p&gt;
&lt;p&gt;A simplified example is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import {
  createContext,
  useContext,
  useEffect,
  useState
} from &quot;react&quot;;

const AuthContext = createContext(null);

export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() =&amp;gt; {
    async function loadCurrentUser() {
      try {
        const response = await fetch(&quot;/api/auth/me&quot;, {
          credentials: &quot;include&quot;
        });

        if (response.ok) {
          setUser(await response.json());
        }
      } finally {
        setLoading(false);
      }
    }

    loadCurrentUser();
  }, []);

  return (
    &amp;lt;AuthContext.Provider
      value={{ user, setUser, loading }}
    &amp;gt;
      {children}
    &amp;lt;/AuthContext.Provider&amp;gt;
  );
}

export function useAuth() {
  return useContext(AuthContext);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This example assumes that the application uses an authenticated cookie and that &lt;code&gt;/api/auth/me&lt;/code&gt; returns the current user's safe profile.&lt;/p&gt;
&lt;p&gt;The endpoint must not return password hashes, signing secrets, or other sensitive fields.&lt;/p&gt;
&lt;p&gt;In a complete implementation, network errors should be handled explicitly, session expiry should be addressed, and login and logout operations should update the context consistently.&lt;/p&gt;
&lt;p&gt;The loading state is important because React should not briefly display protected content before the authentication check finishes.&lt;/p&gt;
&lt;h3 id=&quot;9-protecting-react-routes&quot;&gt;9. Protecting React Routes&lt;/h3&gt;
&lt;p&gt;React Router can prevent users from navigating to pages that should not be visible to them.&lt;/p&gt;
&lt;p&gt;A route guard might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import { Navigate, Outlet } from &quot;react-router-dom&quot;;
import { useAuth } from &quot;./AuthContext&quot;;

export default function ProtectedRoute({
  allowedRoles
}) {
  const { user, loading } = useAuth();

  if (loading) {
    return &amp;lt;p&amp;gt;Loading dashboard...&amp;lt;/p&amp;gt;;
  }

  if (!user) {
    return &amp;lt;Navigate to=&quot;/login&quot; replace /&amp;gt;;
  }

  if (
    allowedRoles &amp;amp;&amp;amp;
    !allowedRoles.includes(user.role)
  ) {
    return &amp;lt;Navigate to=&quot;/forbidden&quot; replace /&amp;gt;;
  }

  return &amp;lt;Outlet /&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The route configuration can group pages by role:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;&amp;lt;Route element={
  &amp;lt;ProtectedRoute
    allowedRoles={[&quot;admin&quot;]}
  /&amp;gt;
}&amp;gt;
  &amp;lt;Route
    path=&quot;/admin/users&quot;
    element={&amp;lt;UserManagement /&amp;gt;}
  /&amp;gt;
&amp;lt;/Route&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The exact role representation must match the profile returned by the backend.&lt;/p&gt;
&lt;p&gt;Route protection improves the user experience, but it is not a substitute for server-side authorization. Anyone can bypass frontend navigation by calling an API directly.&lt;/p&gt;
&lt;p&gt;Therefore, every protected API endpoint must enforce the relevant permissions even when its corresponding React route is already protected.&lt;/p&gt;
&lt;h3 id=&quot;10-creating-a-role-aware-sidebar&quot;&gt;10. Creating a Role-Aware Sidebar&lt;/h3&gt;
&lt;p&gt;A useful dashboard should display only the navigation options that are relevant to the current user.&lt;/p&gt;
&lt;p&gt;Define the navigation items in one configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;const navigationItems = [
  {
    label: &quot;Dashboard&quot;,
    path: &quot;/dashboard&quot;,
    roles: [&quot;admin&quot;, &quot;manager&quot;, &quot;employee&quot;]
  },
  {
    label: &quot;User Management&quot;,
    path: &quot;/admin/users&quot;,
    roles: [&quot;admin&quot;]
  },
  {
    label: &quot;Team Reports&quot;,
    path: &quot;/reports/team&quot;,
    roles: [&quot;admin&quot;, &quot;manager&quot;]
  },
  {
    label: &quot;My Tasks&quot;,
    path: &quot;/tasks&quot;,
    roles: [&quot;admin&quot;, &quot;manager&quot;, &quot;employee&quot;]
  }
];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The sidebar can filter the list according to the authenticated user's role:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;function Sidebar({ role }) {
  const visibleItems = navigationItems.filter(
    item =&amp;gt; item.roles.includes(role)
  );

  return (
    &amp;lt;nav&amp;gt;
      {visibleItems.map(item =&amp;gt; (
        &amp;lt;a key={item.path} href={item.path}&amp;gt;
          {item.label}
        &amp;lt;/a&amp;gt;
      ))}
    &amp;lt;/nav&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In a real application, use React Router's &lt;code&gt;Link&lt;/code&gt; or &lt;code&gt;NavLink&lt;/code&gt; components instead of ordinary anchors for internal navigation.&lt;/p&gt;
&lt;p&gt;For permission-based navigation, filter items against a permission set rather than hard-coding role names. This approach supports custom roles without requiring every navigation component to understand the entire authorization model.&lt;/p&gt;
&lt;p&gt;The same principle applies to action buttons. For example, a user without &lt;code&gt;users.create&lt;/code&gt; should not see an option to create accounts.&lt;/p&gt;
&lt;p&gt;Nevertheless, the backend must reject unauthorized creation requests even if a user manually constructs one.&lt;/p&gt;
&lt;h3 id=&quot;11-creating-different-dashboard-views&quot;&gt;11. Creating Different Dashboard Views&lt;/h3&gt;
&lt;p&gt;After authentication and authorization are in place, the next step is designing the dashboard itself.&lt;/p&gt;
&lt;p&gt;A reusable layout should contain common elements such as the header, sidebar, content area, notification panel, and profile menu.&lt;/p&gt;
&lt;p&gt;Inside that layout, each user role can have a different dashboard view.&lt;/p&gt;
&lt;h4 id=&quot;administrator-dashboard&quot;&gt;Administrator dashboard&lt;/h4&gt;
&lt;p&gt;Administrators may need a high-level overview of the organization.&lt;/p&gt;
&lt;p&gt;Useful components include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Total registered users.&lt;/li&gt;
&lt;li&gt;Active and inactive accounts.&lt;/li&gt;
&lt;li&gt;Recent administrative activities.&lt;/li&gt;
&lt;li&gt;User management shortcuts.&lt;/li&gt;
&lt;li&gt;System health indicators.&lt;/li&gt;
&lt;li&gt;Organization-wide performance reports.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sensitive administrative metrics should be retrieved through endpoints that enforce the required permissions.&lt;/p&gt;
&lt;h4 id=&quot;manager-dashboard&quot;&gt;Manager dashboard&lt;/h4&gt;
&lt;p&gt;Managers generally need team-level information.&lt;/p&gt;
&lt;p&gt;Their dashboard could include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Number of assigned employees.&lt;/li&gt;
&lt;li&gt;Tasks in progress.&lt;/li&gt;
&lt;li&gt;Overdue tasks.&lt;/li&gt;
&lt;li&gt;Team completion rates.&lt;/li&gt;
&lt;li&gt;Pending approvals.&lt;/li&gt;
&lt;li&gt;Team performance trends.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The backend should limit these metrics to teams the manager is authorized to oversee.&lt;/p&gt;
&lt;h4 id=&quot;employee-dashboard&quot;&gt;Employee dashboard&lt;/h4&gt;
&lt;p&gt;Employees benefit from a simpler interface focused on their daily responsibilities.&lt;/p&gt;
&lt;p&gt;Possible components include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Assigned tasks.&lt;/li&gt;
&lt;li&gt;Upcoming deadlines.&lt;/li&gt;
&lt;li&gt;Recent notifications.&lt;/li&gt;
&lt;li&gt;Personal performance metrics.&lt;/li&gt;
&lt;li&gt;Profile information.&lt;/li&gt;
&lt;li&gt;Quick links to common actions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Avoid loading every dashboard widget from one enormous endpoint. Separate API resources make permission enforcement clearer and allow each page to load the data it actually needs.&lt;/p&gt;
&lt;h3 id=&quot;12-enforcing-data-level-access-control&quot;&gt;12. Enforcing Data-Level Access Control&lt;/h3&gt;
&lt;p&gt;One of the most common RBAC mistakes is checking whether a user can access a feature without checking which records the user may access.&lt;/p&gt;
&lt;p&gt;For example, a manager might be permitted to read task details but should not automatically be able to access every employee's tasks.&lt;/p&gt;
&lt;p&gt;Imagine a request to retrieve a task:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;@router.get(&quot;/tasks/{task_id}&quot;)
def get_task(
    task_id: int,
    current_user = Depends(get_current_user)
):
    task = get_task_from_database(task_id)

    if task is None:
        raise HTTPException(
            status_code=404,
            detail=&quot;Task not found&quot;
        )

    if not can_view_task(current_user, task):
        raise HTTPException(
            status_code=403,
            detail=&quot;Access denied&quot;
        )

    return task
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The helper &lt;code&gt;can_view_task&lt;/code&gt; must implement the application's actual ownership, team, and organizational rules.&lt;/p&gt;
&lt;p&gt;For greater reliability, enforce data scopes directly in database queries whenever practical.&lt;/p&gt;
&lt;p&gt;For example, an employee's task query should include the employee's authenticated user ID. A manager's query should include only the IDs of teams that manager is permitted to access.&lt;/p&gt;
&lt;p&gt;This reduces the risk of insecure direct object references, where changing a record identifier exposes another user's data.&lt;/p&gt;
&lt;p&gt;A robust access policy often combines RBAC with ownership checks or attribute-based access control (ABAC).&lt;/p&gt;
&lt;p&gt;RBAC answers which actions a role can perform. Ownership and attributes determine whether that action is permitted on a particular record.&lt;/p&gt;
&lt;h3 id=&quot;13-building-dashboard-apis-efficiently&quot;&gt;13. Building Dashboard APIs Efficiently&lt;/h3&gt;
&lt;p&gt;The frontend should retrieve data through clearly defined endpoints.&lt;/p&gt;
&lt;p&gt;A possible API design is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;POST   /api/auth/login
POST   /api/auth/logout
GET    /api/auth/me

GET    /api/dashboard/summary

GET    /api/users
POST   /api/users
PATCH  /api/users/{user_id}

GET    /api/tasks
POST   /api/tasks
PATCH  /api/tasks/{task_id}

GET    /api/reports/team
GET    /api/reports/organization
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every endpoint should have an explicit authentication requirement and authorization policy.&lt;/p&gt;
&lt;p&gt;For example, &lt;code&gt;GET /api/reports/organization&lt;/code&gt; may require an organization-wide reporting permission, while &lt;code&gt;GET /api/reports/team&lt;/code&gt; may allow managers to access only their assigned teams.&lt;/p&gt;
&lt;p&gt;Avoid relying on the endpoint's name or location to provide security. The policy must be implemented in the actual request handler or its dependencies.&lt;/p&gt;
&lt;h4 id=&quot;pagination-and-filtering&quot;&gt;Pagination and filtering&lt;/h4&gt;
&lt;p&gt;Dashboard tables can become slow when they retrieve thousands of records in a single request.&lt;/p&gt;
&lt;p&gt;Use pagination and server-side filtering.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;GET /api/tasks?page=1&amp;amp;page_size=25&amp;amp;status=pending
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The backend should validate pagination parameters, apply the authenticated user's data scope, and then execute the query.&lt;/p&gt;
&lt;p&gt;Filtering and sorting must be implemented safely. User-provided column names should not be inserted directly into raw SQL statements.&lt;/p&gt;
&lt;h4 id=&quot;caching-considerations&quot;&gt;Caching considerations&lt;/h4&gt;
&lt;p&gt;Caching can improve dashboard performance, but it can also expose data if cache entries are shared across users with different permissions.&lt;/p&gt;
&lt;p&gt;Cache keys should reflect the relevant user, organization, and authorization scope. Sensitive responses should not be placed in shared caches without a clear security model.&lt;/p&gt;
&lt;p&gt;When permissions change, the system should also consider how cached authorization decisions and dashboard data are invalidated.&lt;/p&gt;
&lt;h3 id=&quot;14-handling-authentication-and-session-security&quot;&gt;14. Handling Authentication and Session Security&lt;/h3&gt;
&lt;p&gt;Security must be part of the design from the beginning.&lt;/p&gt;
&lt;p&gt;A role-based dashboard may contain employee information, customer records, financial reports, or administrative operations. Poor session management can undermine even a carefully designed permission model.&lt;/p&gt;
&lt;p&gt;Important practices include the following.&lt;/p&gt;
&lt;h4 id=&quot;use-short-lived-access-credentials&quot;&gt;Use short-lived access credentials&lt;/h4&gt;
&lt;p&gt;Access tokens should have reasonable expiration times. If refresh tokens are used, implement rotation, revocation, and appropriate reuse detection.&lt;/p&gt;
&lt;h4 id=&quot;protect-browser-storage&quot;&gt;Protect browser storage&lt;/h4&gt;
&lt;p&gt;Avoid storing long-lived authentication tokens in browser storage where injected JavaScript could read them.&lt;/p&gt;
&lt;p&gt;HttpOnly cookies can reduce token theft through JavaScript, although they require proper CSRF defenses and careful cookie configuration.&lt;/p&gt;
&lt;h4 id=&quot;configure-cors-correctly-1&quot;&gt;Configure CORS correctly&lt;/h4&gt;
&lt;p&gt;Cross-Origin Resource Sharing should allow only the origins required by the application.&lt;/p&gt;
&lt;p&gt;Avoid permissive wildcard configurations, especially when credentials are involved.&lt;/p&gt;
&lt;h4 id=&quot;validate-every-request&quot;&gt;Validate every request&lt;/h4&gt;
&lt;p&gt;Validate request bodies, query parameters, identifiers, and update fields. Do not assume the frontend has already performed validation.&lt;/p&gt;
&lt;h4 id=&quot;prevent-brute-force-attacks&quot;&gt;Prevent brute-force attacks&lt;/h4&gt;
&lt;p&gt;Apply appropriate rate limits to login and other sensitive endpoints. Monitor repeated failed login attempts and consider additional protections for high-risk accounts.&lt;/p&gt;
&lt;h4 id=&quot;protect-privileged-accounts&quot;&gt;Protect privileged accounts&lt;/h4&gt;
&lt;p&gt;Require stronger authentication controls for administrative users where practical, including multifactor authentication.&lt;/p&gt;
&lt;h4 id=&quot;use-https&quot;&gt;Use HTTPS&lt;/h4&gt;
&lt;p&gt;All production authentication traffic should use HTTPS. Secure cookies and appropriate transport security configurations help protect credentials in transit.&lt;/p&gt;
&lt;h4 id=&quot;avoid-leaking-sensitive-information&quot;&gt;Avoid leaking sensitive information&lt;/h4&gt;
&lt;p&gt;Do not include secrets, password hashes, or internal authorization details in API responses or application logs.&lt;/p&gt;
&lt;p&gt;Security depends on the complete request lifecycle, not a single authentication library.&lt;/p&gt;
&lt;h3 id=&quot;15-managing-role-changes-and-permission-updates&quot;&gt;15. Managing Role Changes and Permission Updates&lt;/h3&gt;
&lt;p&gt;User roles often change as employees move between teams or receive new responsibilities.&lt;/p&gt;
&lt;p&gt;A manager might become an administrator, or an employee might lose access after leaving a project.&lt;/p&gt;
&lt;p&gt;These changes should take effect according to a clearly defined policy.&lt;/p&gt;
&lt;p&gt;If authorization depends on permissions stored in the database, the backend can resolve current permissions on each request or use a carefully designed cache with invalidation.&lt;/p&gt;
&lt;p&gt;If roles and permissions are embedded in JWT claims, those claims may become outdated before the token expires.&lt;/p&gt;
&lt;p&gt;For that reason, highly privileged operations should not rely exclusively on long-lived role claims without considering revocation and role-change behavior.&lt;/p&gt;
&lt;p&gt;Possible solutions include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Short-lived access tokens.&lt;/li&gt;
&lt;li&gt;Server-side sessions.&lt;/li&gt;
&lt;li&gt;Token versioning.&lt;/li&gt;
&lt;li&gt;Session revocation.&lt;/li&gt;
&lt;li&gt;Rechecking current permissions for sensitive operations.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The appropriate approach depends on the application's scale and risk profile.&lt;/p&gt;
&lt;p&gt;Role assignment should also be auditable. Record who changed a role, which account was affected, when the change occurred, and whether the operation succeeded.&lt;/p&gt;
&lt;h3 id=&quot;16-testing-the-authorization-model&quot;&gt;16. Testing the Authorization Model&lt;/h3&gt;
&lt;p&gt;A dashboard can look correct while containing serious access-control vulnerabilities. Testing should therefore cover both successful operations and forbidden requests.&lt;/p&gt;
&lt;p&gt;Start by creating test accounts for each role.&lt;/p&gt;
&lt;p&gt;Then build a permission matrix that identifies which actions should succeed or fail for each account.&lt;/p&gt;
&lt;p&gt;Example tests include:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Administrator tests&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Can access user management.&lt;/li&gt;
&lt;li&gt;Can create authorized user accounts.&lt;/li&gt;
&lt;li&gt;Can access permitted organization-wide reports.&lt;/li&gt;
&lt;li&gt;Cannot bypass validation or access data outside their organization.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Manager tests&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Can view permitted team reports.&lt;/li&gt;
&lt;li&gt;Can update tasks within their authorized scope.&lt;/li&gt;
&lt;li&gt;Cannot assign themselves administrative privileges.&lt;/li&gt;
&lt;li&gt;Cannot retrieve unrelated teams' private records.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Employee tests&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Can view their own dashboard.&lt;/li&gt;
&lt;li&gt;Can update assigned tasks where allowed.&lt;/li&gt;
&lt;li&gt;Cannot access administrator endpoints.&lt;/li&gt;
&lt;li&gt;Cannot modify another user's profile or task by changing an identifier.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;testing-with-pytest&quot;&gt;Testing with Pytest&lt;/h4&gt;
&lt;p&gt;A backend test might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def test_employee_cannot_list_users(
    employee_client
):
    response = employee_client.get(&quot;/api/users&quot;)

    assert response.status_code == 403
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The test assumes that the employee lacks the permission required by the endpoint and that &lt;code&gt;employee_client&lt;/code&gt; represents a correctly authenticated test client.&lt;/p&gt;
&lt;p&gt;Additional tests should cover expired tokens, inactive accounts, revoked sessions, invalid permissions, missing credentials, and unexpected database failures.&lt;/p&gt;
&lt;p&gt;For resource-level access, create records belonging to different users and verify that changing the record identifier never grants unauthorized access.&lt;/p&gt;
&lt;p&gt;Frontend tests should also confirm that restricted navigation items and actions are hidden or disabled appropriately.&lt;/p&gt;
&lt;p&gt;However, frontend tests cannot establish backend security on their own. The API must be tested directly.&lt;/p&gt;
&lt;h3 id=&quot;17-improving-dashboard-performance&quot;&gt;17. Improving Dashboard Performance&lt;/h3&gt;
&lt;p&gt;Performance becomes important when a dashboard displays multiple data tables, charts, notifications, and activity feeds.&lt;/p&gt;
&lt;p&gt;A few architectural decisions can make a significant difference.&lt;/p&gt;
&lt;h4 id=&quot;optimize-database-queries&quot;&gt;Optimize database queries&lt;/h4&gt;
&lt;p&gt;Use appropriate indexes for commonly filtered fields, including foreign keys and frequently queried status columns.&lt;/p&gt;
&lt;p&gt;Inspect query plans before adding indexes indiscriminately.&lt;/p&gt;
&lt;h4 id=&quot;avoid-unnecessary-requests&quot;&gt;Avoid unnecessary requests&lt;/h4&gt;
&lt;p&gt;Fetch data when the relevant page needs it. Use request caching and invalidation through an appropriate data-fetching library when the application becomes more complex.&lt;/p&gt;
&lt;h4 id=&quot;split-large-components&quot;&gt;Split large components&lt;/h4&gt;
&lt;p&gt;Separate dashboard cards, tables, filters, and chart components into manageable units.&lt;/p&gt;
&lt;p&gt;This makes the interface easier to test and reduces unnecessary rendering.&lt;/p&gt;
&lt;h4 id=&quot;use-lazy-loading&quot;&gt;Use lazy loading&lt;/h4&gt;
&lt;p&gt;Load large reports or less frequently visited pages only when users open them.&lt;/p&gt;
&lt;h4 id=&quot;paginate-large-datasets&quot;&gt;Paginate large datasets&lt;/h4&gt;
&lt;p&gt;Return manageable pages of records instead of sending the entire dataset to the browser.&lt;/p&gt;
&lt;h4 id=&quot;monitor-real-world-behavior&quot;&gt;Monitor real-world behavior&lt;/h4&gt;
&lt;p&gt;Track API latency, error rates, database query performance, and frontend loading times.&lt;/p&gt;
&lt;p&gt;Performance optimization should be driven by actual measurements rather than assumptions.&lt;/p&gt;
&lt;h3 id=&quot;18-improving-the-user-experience&quot;&gt;18. Improving the User Experience&lt;/h3&gt;
&lt;p&gt;A secure dashboard should also be easy to understand.&lt;/p&gt;
&lt;p&gt;Users should be able to determine what they can do without navigating through irrelevant features.&lt;/p&gt;
&lt;p&gt;Consider these improvements:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Clear navigation:&lt;/strong&gt; Organize features according to the user's responsibilities.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Useful empty states:&lt;/strong&gt; Explain what a user can do when there are no tasks, reports, or notifications.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Accessible design:&lt;/strong&gt; Provide keyboard navigation, clear focus indicators, semantic HTML, and suitable color contrast.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Responsive layouts:&lt;/strong&gt; Ensure tables, menus, and dashboard cards work on desktop, tablet, and mobile screens.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Meaningful feedback:&lt;/strong&gt; Display clear success and error messages after important operations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Confirmation for destructive actions:&lt;/strong&gt; Require appropriate confirmation before deleting records or performing other irreversible operations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Consistent loading states:&lt;/strong&gt; Show skeletons, spinners, or loading indicators while data is being retrieved.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Permission-aware error handling:&lt;/strong&gt; Explain when a user lacks access without exposing internal security details.&lt;/p&gt;
&lt;p&gt;Avoid treating role-based design as an excuse to create completely separate user interfaces. Reuse common components wherever possible and customize only the sections that genuinely differ.&lt;/p&gt;
&lt;h3 id=&quot;19-logging-and-auditing-administrative-actions&quot;&gt;19. Logging and Auditing Administrative Actions&lt;/h3&gt;
&lt;p&gt;A production dashboard should provide visibility into important security events.&lt;/p&gt;
&lt;p&gt;Useful audit events include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Successful and failed authentication attempts.&lt;/li&gt;
&lt;li&gt;Role and permission changes.&lt;/li&gt;
&lt;li&gt;User account creation and deactivation.&lt;/li&gt;
&lt;li&gt;Sensitive report access where justified.&lt;/li&gt;
&lt;li&gt;Changes to important business records.&lt;/li&gt;
&lt;li&gt;Rejected privileged operations.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;An audit record might contain:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;event_id
actor_user_id
action
resource_type
resource_id
timestamp
request_id
outcome
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Additional fields can be introduced where necessary, but logs should not contain passwords, access tokens, or unnecessary personal information.&lt;/p&gt;
&lt;p&gt;Audit logs should be protected against unauthorized modification and access. For important compliance requirements, consider exporting them to a dedicated monitoring or logging platform.&lt;/p&gt;
&lt;p&gt;Logging is not a replacement for access control. Its purpose is to support monitoring, investigation, and accountability.&lt;/p&gt;
&lt;h3 id=&quot;20-deploying-the-application&quot;&gt;20. Deploying the Application&lt;/h3&gt;
&lt;p&gt;Once the dashboard passes testing, prepare it for production.&lt;/p&gt;
&lt;p&gt;The React frontend can be built as static assets and hosted on an appropriate web server or frontend hosting platform. FastAPI can run behind a production-ready application server and reverse proxy. PostgreSQL should run in a managed database service or a properly secured database environment.&lt;/p&gt;
&lt;p&gt;Docker can help make development and deployment environments more consistent.&lt;/p&gt;
&lt;p&gt;A production deployment checklist should include:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Configure environment variables and secret management.&lt;/li&gt;
&lt;li&gt;Use a dedicated database account with limited privileges.&lt;/li&gt;
&lt;li&gt;Apply database migrations.&lt;/li&gt;
&lt;li&gt;Enable HTTPS.&lt;/li&gt;
&lt;li&gt;Configure allowed frontend origins.&lt;/li&gt;
&lt;li&gt;Set appropriate cookie and session policies.&lt;/li&gt;
&lt;li&gt;Configure database backups and recovery procedures.&lt;/li&gt;
&lt;li&gt;Add health checks and application monitoring.&lt;/li&gt;
&lt;li&gt;Apply rate limits to sensitive endpoints.&lt;/li&gt;
&lt;li&gt;Verify role and resource-level authorization.&lt;/li&gt;
&lt;li&gt;Test account deactivation and session revocation.&lt;/li&gt;
&lt;li&gt;Review dependency updates and security advisories.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Never commit production secrets, signing keys, or database passwords to a public repository.&lt;/p&gt;
&lt;p&gt;Also, avoid running database migrations automatically in every application worker at startup. A controlled deployment step is easier to reason about and reduces migration race conditions.&lt;/p&gt;
&lt;p&gt;After deployment, monitor errors and latency, test backup restoration periodically, and establish a process for responding to security incidents.&lt;/p&gt;
&lt;h3 id=&quot;21-common-mistakes-to-avoid&quot;&gt;21. Common Mistakes to Avoid&lt;/h3&gt;
&lt;p&gt;Several mistakes repeatedly appear in role-based dashboard implementations.&lt;/p&gt;
&lt;h4 id=&quot;trusting-frontend-roles&quot;&gt;Trusting frontend roles&lt;/h4&gt;
&lt;p&gt;A role stored in React state can be changed by a user. The backend must verify permissions independently.&lt;/p&gt;
&lt;h4 id=&quot;giving-every-administrator-unlimited-access&quot;&gt;Giving every administrator unlimited access&lt;/h4&gt;
&lt;p&gt;Administrative privileges should be carefully defined and restricted to legitimate needs. Separate high-risk permissions when appropriate.&lt;/p&gt;
&lt;h4 id=&quot;ignoring-record-ownership&quot;&gt;Ignoring record ownership&lt;/h4&gt;
&lt;p&gt;Permission to access a feature does not necessarily mean permission to access every record within that feature.&lt;/p&gt;
&lt;h4 id=&quot;using-one-oversized-api-endpoint&quot;&gt;Using one oversized API endpoint&lt;/h4&gt;
&lt;p&gt;Returning all application data from a single endpoint complicates authorization, increases response size, and can reveal information unnecessarily.&lt;/p&gt;
&lt;h4 id=&quot;hard-coding-role-checks-everywhere&quot;&gt;Hard-coding role checks everywhere&lt;/h4&gt;
&lt;p&gt;Repeated conditional logic becomes difficult to maintain. Centralize authorization rules and test them systematically.&lt;/p&gt;
&lt;h4 id=&quot;forgetting-permission-revocation&quot;&gt;Forgetting permission revocation&lt;/h4&gt;
&lt;p&gt;Role changes, account deactivation, and session revocation must have defined effects on existing sessions and tokens.&lt;/p&gt;
&lt;h4 id=&quot;skipping-negative-tests&quot;&gt;Skipping negative tests&lt;/h4&gt;
&lt;p&gt;It is not enough to prove that administrators can access restricted resources. Tests must also prove that unauthorized users cannot.&lt;/p&gt;
&lt;h4 id=&quot;returning-sensitive-fields&quot;&gt;Returning sensitive fields&lt;/h4&gt;
&lt;p&gt;Use response schemas that expose only the data a particular endpoint is intended to return.&lt;/p&gt;
&lt;p&gt;Avoiding these mistakes early is generally easier than correcting them after a security incident.&lt;/p&gt;
&lt;h3 id=&quot;22-a-practical-development-roadmap&quot;&gt;22. A Practical Development Roadmap&lt;/h3&gt;
&lt;p&gt;For developers building their first multi-user dashboard, an incremental implementation plan is often the most effective approach.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 1: Requirements and design&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Identify user roles, permissions, data ownership rules, and core dashboard features. Document the permission matrix before writing authorization code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 2: Database and authentication&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Create the user and role models, implement password hashing, establish login and logout flows, and define how sessions will be validated.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 3: Backend authorization&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Implement reusable permission dependencies and apply them to every protected endpoint. Add data-scope checks for teams, organizations, and owned records.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 4: React application&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Build the authentication context, protected routes, reusable dashboard layout, and role-aware navigation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 5: Dashboard functionality&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Implement the statistics cards, data tables, filters, reports, task management, and user management features required by the application.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 6: Testing&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Test each role against both permitted and forbidden actions. Include direct API requests, cross-user record access, expired sessions, and role changes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 7: Production readiness&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Configure HTTPS, secure cookies or tokens, monitoring, audit logs, database backups, and deployment automation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 8: Iteration&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Review user feedback and performance metrics. Introduce new permissions or roles only when there is a clear business need.&lt;/p&gt;
&lt;p&gt;This roadmap keeps the implementation manageable while ensuring that security is considered throughout development.&lt;/p&gt;
&lt;h3 id=&quot;conclusion-2&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Building a multi-user dashboard with role-based access requires more than creating a few conditional React components. It involves designing a clear authorization model, securing authentication, protecting API endpoints, limiting access to individual records, and testing the complete system against unauthorized requests.&lt;/p&gt;
&lt;p&gt;React provides a flexible interface for presenting role-specific dashboards. FastAPI offers a practical foundation for reusable authentication and authorization dependencies, while PostgreSQL provides reliable storage for users, roles, permissions, and application data.&lt;/p&gt;
&lt;p&gt;By combining least-privilege permissions, resource-level authorization, secure session management, comprehensive testing, and structured deployment practices, developers can build dashboards that are maintainable today and ready to grow with future business requirements.&lt;/p&gt;
&lt;p&gt;A well-designed role-based dashboard is not simply an interface that changes depending on who logs in. It is a secure application architecture that ensures every user can perform the right actions, access the right information, and do so without compromising the privacy or integrity of other users' data.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31802/how-to-build-a-multi-user-dashboard-with-role-based-access</guid>
<pubDate>Tue, 20 Oct 2026 06:04:12 +0000</pubDate>
</item>
<item>
<title>Building a Real Time Notification System with React and WebSockets</title>
<link>https://coderlegion.com/31801/building-a-real-time-notification-system-with-react-and-websockets</link>
<description>&lt;p&gt;Modern web applications need to communicate with users instantly. Whether someone receives a new message, a payment confirmation, an order update, a project assignment, or a security alert, they expect the information to appear without refreshing the page. Real-time notifications have become an essential feature of dashboards, social media platforms, e-commerce applications, collaboration tools, and customer management systems.&lt;/p&gt;
&lt;p&gt;Traditional web applications often rely on polling to check whether new information is available. Although polling is straightforward to implement, it can generate unnecessary HTTP requests and introduce delays. WebSockets provide a more efficient communication model by establishing a persistent, bidirectional connection between the browser and the server.&lt;/p&gt;
&lt;p&gt;When combined with React, WebSockets make it possible to build responsive notification interfaces that update automatically as events occur.&lt;/p&gt;
&lt;p&gt;In this article, we will explore how to design, implement, secure, test, and scale a real-time notification system using React, JavaScript, Node.js, and WebSockets. We will also discuss notification persistence, unread counters, reconnection strategies, database integration, and production deployment.&lt;/p&gt;
&lt;h3 id=&quot;1-understanding-real-time-notifications&quot;&gt;1. Understanding Real-Time Notifications&lt;/h3&gt;
&lt;p&gt;A real-time notification is a message delivered to a user when a particular event happens. Unlike traditional alerts that require the user to refresh a page or revisit a section, real-time notifications can appear immediately while the application remains open.&lt;/p&gt;
&lt;p&gt;Consider a project management application. When a team member assigns a task to another employee, the backend records the assignment and sends a notification to the appropriate user. If that user is currently online, the notification appears in the React interface almost immediately.&lt;/p&gt;
&lt;p&gt;Common notification events include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;New messages in a conversation.&lt;/li&gt;
&lt;li&gt;Changes to an order or delivery status.&lt;/li&gt;
&lt;li&gt;Payment confirmations and billing alerts.&lt;/li&gt;
&lt;li&gt;New comments, mentions, or reactions.&lt;/li&gt;
&lt;li&gt;Task assignments and project updates.&lt;/li&gt;
&lt;li&gt;System maintenance and security warnings.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A reliable notification system should do more than display a temporary message. It should identify the recipient, preserve important events, maintain unread counts, support marking notifications as read, and recover gracefully when a connection is interrupted.&lt;/p&gt;
&lt;p&gt;The goal is to deliver a consistent experience whether users are actively interacting with the application or returning after being offline.&lt;/p&gt;
&lt;h3 id=&quot;2-what-are-websockets-&quot;&gt;2. What Are WebSockets?&lt;/h3&gt;
&lt;p&gt;WebSockets are a communication protocol that enables two-way communication between a client and a server over a persistent connection. After the initial connection is established, both sides can send messages independently without creating a new HTTP request for every update.&lt;/p&gt;
&lt;p&gt;According to the MDN WebSockets documentation, this communication model allows applications to exchange data without repeatedly polling the server.&lt;/p&gt;
&lt;p&gt;Reference: &lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WebSocket&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/API/WebSocket&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In a traditional polling approach, the browser might request notification updates every five seconds. If thousands of users keep polling, the backend must process a large number of requests even when no new notifications exist.&lt;/p&gt;
&lt;p&gt;With WebSockets, the browser establishes a connection and waits. When an event occurs, the server sends the notification through that connection.&lt;/p&gt;
&lt;h4 id=&quot;websockets-versus-polling&quot;&gt;WebSockets versus polling&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Polling&lt;/strong&gt; is easy to understand and works with almost any HTTP backend. However, it can waste resources when updates are infrequent and may introduce delays between polling intervals.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Long polling&lt;/strong&gt; keeps an HTTP request open until the server has an update or a timeout occurs. It can reduce unnecessary requests but introduces additional request-management complexity.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;WebSockets&lt;/strong&gt; maintain a persistent connection and support bidirectional messaging. They are particularly useful when updates must be delivered quickly or when the client also needs to send real-time events.&lt;/p&gt;
&lt;p&gt;Server-Sent Events (SSE) are another option when the browser primarily needs one-way updates from the server. The right choice depends on the application's communication requirements.&lt;/p&gt;
&lt;p&gt;For a notification system that needs low-latency delivery and may later support features such as live chat or collaborative updates, WebSockets are a strong option.&lt;/p&gt;
&lt;h3 id=&quot;3-designing-the-system-architecture&quot;&gt;3. Designing the System Architecture&lt;/h3&gt;
&lt;p&gt;Before writing code, it is important to understand the responsibilities of each component.&lt;/p&gt;
&lt;p&gt;A practical architecture contains five major parts:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;React frontend:&lt;/strong&gt; Displays notifications, unread counts, connection status, and user interactions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;WebSocket server:&lt;/strong&gt; Maintains active connections and delivers events to authenticated users.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Application backend:&lt;/strong&gt; Generates notifications when business events occur.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Database:&lt;/strong&gt; Stores notification records so users can access them after reconnecting.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Message broker:&lt;/strong&gt; Coordinates event delivery across multiple backend instances when the application scales.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The basic event flow looks like this:&lt;/p&gt;
&lt;p&gt;A user performs an action, such as creating an order. The backend validates the action and stores the resulting business data. It then creates a notification event for the intended recipient. The WebSocket layer sends the event to the user's active connection, and React updates the interface.&lt;/p&gt;
&lt;p&gt;If the recipient is offline, the notification remains in persistent storage and can be retrieved through an HTTP API when the user returns.&lt;/p&gt;
&lt;p&gt;This distinction is essential: WebSockets provide a delivery channel, but they should not be the only place where important notification data exists.&lt;/p&gt;
&lt;h3 id=&quot;4-choosing-the-technology-stack&quot;&gt;4. Choosing the Technology Stack&lt;/h3&gt;
&lt;p&gt;For this implementation, we will use a JavaScript-based stack.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;React:&lt;/strong&gt; Builds the notification interface.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Node.js:&lt;/strong&gt; Runs the server-side JavaScript environment.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Express:&lt;/strong&gt; Provides HTTP routes for the backend.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ws:&lt;/strong&gt; Provides a WebSocket server implementation for Node.js.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PostgreSQL or another database:&lt;/strong&gt; Stores notifications and read states.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Redis or a message broker:&lt;/strong&gt; Supports cross-instance event distribution when required.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;ws&lt;/code&gt; library is a useful choice for demonstrating the underlying WebSocket protocol directly. Socket.IO is another popular option that adds features such as automatic reconnection and fallback transports. However, Socket.IO and native WebSockets are different protocols and are not interchangeable without compatible client and server implementations.&lt;/p&gt;
&lt;p&gt;We will use the &lt;code&gt;ws&lt;/code&gt; library for the server and the browser's native &lt;code&gt;WebSocket&lt;/code&gt; API for the React client.&lt;/p&gt;
&lt;h3 id=&quot;5-creating-the-backend-websocket-server&quot;&gt;5. Creating the Backend WebSocket Server&lt;/h3&gt;
&lt;p&gt;First, create a project directory and install the required dependencies.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir realtime-notifications
cd realtime-notifications
npm init -y
npm install express ws cors
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a file named &lt;code&gt;server.js&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The following example demonstrates a basic WebSocket server that associates connections with users and sends notifications to the correct recipient. It uses a simplified demonstration authentication mechanism; production applications must replace it with real authentication and authorization.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const express = require(&quot;express&quot;);
const http = require(&quot;http&quot;);
const WebSocket = require(&quot;ws&quot;);
const cors = require(&quot;cors&quot;);

const app = express();

app.use(cors({
  origin: &quot;http://localhost:5173&quot;,
}));

app.use(express.json());

const server = http.createServer(app);

const wss = new WebSocket.Server({
  server,
  path: &quot;/ws&quot;,
});

const clients = new Map();

wss.on(&quot;connection&quot;, (socket, request) =&amp;gt; {
  // Demonstration only: do not trust a user ID
  // supplied directly by a client in production.
  const url = new URL(
    request.url,
    &quot;http://localhost:3000&quot;
  );

  const userId = url.searchParams.get(&quot;userId&quot;);

  if (!userId) {
    socket.close(1008, &quot;Authentication required&quot;);
    return;
  }

  if (!clients.has(userId)) {
    clients.set(userId, new Set());
  }

  clients.get(userId).add(socket);

  socket.on(&quot;close&quot;, () =&amp;gt; {
    const connections = clients.get(userId);

    if (!connections) return;

    connections.delete(socket);

    if (connections.size === 0) {
      clients.delete(userId);
    }
  });

  socket.on(&quot;error&quot;, (error) =&amp;gt; {
    console.error(&quot;WebSocket error:&quot;, error);
  });
});

function sendNotification(userId, notification) {
  const connections = clients.get(String(userId));

  if (!connections) return;

  const message = JSON.stringify({
    type: &quot;notification&quot;,
    payload: notification,
  });

  for (const socket of connections) {
    if (socket.readyState === WebSocket.OPEN) {
      socket.send(message);
    }
  }
}

app.post(&quot;/api/notifications/demo&quot;, (req, res) =&amp;gt; {
  const { userId, title, message } = req.body;

  if (!userId || !title || !message) {
    return res.status(400).json({
      error: &quot;userId, title, and message are required&quot;,
    });
  }

  const notification = {
    id: crypto.randomUUID(),
    title,
    message,
    createdAt: new Date().toISOString(),
    read: false,
  };

  sendNotification(userId, notification);

  res.status(202).json({
    success: true,
    notification,
  });
});

server.listen(3000, () =&amp;gt; {
  console.log(&quot;Server running on port 3000&quot;);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add the following import near the top of the file if your Node.js environment does not expose &lt;code&gt;crypto&lt;/code&gt; globally:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const crypto = require(&quot;node:crypto&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The example uses an in-memory &lt;code&gt;Map&lt;/code&gt; to track active connections. Each user maps to a set of WebSocket connections, allowing the same account to connect from multiple browser tabs or devices.&lt;/p&gt;
&lt;p&gt;When a connection closes, the server removes it from the set and deletes the user entry if no active connections remain.&lt;/p&gt;
&lt;p&gt;There are two important limitations. First, this example does not persist notifications. Second, the &lt;code&gt;userId&lt;/code&gt; query parameter is not secure authentication. Anyone who can connect and choose an arbitrary user ID could impersonate another recipient.&lt;/p&gt;
&lt;p&gt;We will address both issues in the production design sections below.&lt;/p&gt;
&lt;h3 id=&quot;6-building-the-react-notification-component&quot;&gt;6. Building the React Notification Component&lt;/h3&gt;
&lt;p&gt;Next, create a React application. If you are using Vite, the following commands will get you started.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm create vite@latest client -- --template react
cd client
npm install
npm run dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a component called &lt;code&gt;NotificationCenter.jsx&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This component will establish a WebSocket connection, listen for incoming messages, update the notification list, and calculate the unread count.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import { useEffect, useState } from &quot;react&quot;;

export default function NotificationCenter({ userId }) {
  const [notifications, setNotifications] = useState([]);
  const [connectionStatus, setConnectionStatus] =
    useState(&quot;connecting&quot;);

  useEffect(() =&amp;gt; {
    if (!userId) return;

    const socket = new WebSocket(
      `ws://localhost:3000/ws?userId=${encodeURIComponent(userId)}`
    );

    socket.addEventListener(&quot;open&quot;, () =&amp;gt; {
      setConnectionStatus(&quot;connected&quot;);
    });

    socket.addEventListener(&quot;message&quot;, (event) =&amp;gt; {
      try {
        const data = JSON.parse(event.data);

        if (data.type !== &quot;notification&quot;) return;

        setNotifications((previous) =&amp;gt; [
          data.payload,
          ...previous,
        ]);
      } catch (error) {
        console.error(&quot;Invalid WebSocket message:&quot;, error);
      }
    });

    socket.addEventListener(&quot;error&quot;, () =&amp;gt; {
      setConnectionStatus(&quot;error&quot;);
    });

    socket.addEventListener(&quot;close&quot;, () =&amp;gt; {
      setConnectionStatus(&quot;disconnected&quot;);
    });

    return () =&amp;gt; {
      socket.close();
    };
  }, [userId]);

  const unreadCount = notifications.filter(
    (notification) =&amp;gt; !notification.read
  ).length;

  function markAsRead(id) {
    setNotifications((previous) =&amp;gt;
      previous.map((notification) =&amp;gt;
        notification.id === id
          ? { ...notification, read: true }
          : notification
      )
    );
  }

  return (
    &amp;lt;section className=&quot;notification-center&quot;&amp;gt;
      &amp;lt;header&amp;gt;
        &amp;lt;h2&amp;gt;Notifications&amp;lt;/h2&amp;gt;
        &amp;lt;span&amp;gt;Unread: {unreadCount}&amp;lt;/span&amp;gt;
      &amp;lt;/header&amp;gt;

      &amp;lt;p&amp;gt;Connection: {connectionStatus}&amp;lt;/p&amp;gt;

      {notifications.length === 0 ? (
        &amp;lt;p&amp;gt;No notifications yet.&amp;lt;/p&amp;gt;
      ) : (
        &amp;lt;ul&amp;gt;
          {notifications.map((notification) =&amp;gt; (
            &amp;lt;li key={notification.id}&amp;gt;
              &amp;lt;strong&amp;gt;{notification.title}&amp;lt;/strong&amp;gt;
              &amp;lt;p&amp;gt;{notification.message}&amp;lt;/p&amp;gt;

              {!notification.read &amp;amp;&amp;amp; (
                &amp;lt;button
                  onClick={() =&amp;gt; markAsRead(notification.id)}
                &amp;gt;
                  Mark as read
                &amp;lt;/button&amp;gt;
              )}
            &amp;lt;/li&amp;gt;
          ))}
        &amp;lt;/ul&amp;gt;
      )}
    &amp;lt;/section&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Render the component from &lt;code&gt;App.jsx&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import NotificationCenter from &quot;./NotificationCenter&quot;;

export default function App() {
  return (
    &amp;lt;main&amp;gt;
      &amp;lt;h1&amp;gt;Dashboard&amp;lt;/h1&amp;gt;
      &amp;lt;NotificationCenter userId=&quot;user-123&quot; /&amp;gt;
    &amp;lt;/main&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The hard-coded user ID is only for local demonstration. A real application should obtain the authenticated user identity from its session or authentication provider.&lt;/p&gt;
&lt;p&gt;The React effect creates the connection when the component mounts and closes it when the component unmounts or the &lt;code&gt;userId&lt;/code&gt; changes. This cleanup prevents an obsolete connection from continuing to receive messages.&lt;/p&gt;
&lt;p&gt;React state is updated using the previous state value. This pattern avoids losing notifications when multiple events arrive close together.&lt;/p&gt;
&lt;p&gt;The component also parses incoming messages inside a &lt;code&gt;try...catch&lt;/code&gt; block. A malformed message should not crash the notification interface.&lt;/p&gt;
&lt;h3 id=&quot;7-adding-a-reconnection-strategy&quot;&gt;7. Adding a Reconnection Strategy&lt;/h3&gt;
&lt;p&gt;Network interruptions are inevitable. Users switch between Wi-Fi and mobile data, laptops enter sleep mode, and servers may restart during deployments. A production notification system must recover from these interruptions.&lt;/p&gt;
&lt;p&gt;The initial React component detects when the connection closes, but it does not reconnect automatically. We can improve it with a retry strategy that waits progressively longer between attempts.&lt;/p&gt;
&lt;p&gt;A simple exponential backoff strategy calculates the delay using the number of failed attempts:&lt;/p&gt;
&lt;p&gt;\[
delay = \min(baseDelay \times 2^{attempt}, maxDelay)
\]&lt;/p&gt;
&lt;p&gt;Random jitter can be added to prevent thousands of clients from reconnecting simultaneously after a server outage.&lt;/p&gt;
&lt;p&gt;Here is a reusable React hook demonstrating this approach:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import { useEffect, useState } from &quot;react&quot;;

export function useNotificationSocket(userId) {
  const [status, setStatus] = useState(&quot;disconnected&quot;);
  const [messages, setMessages] = useState([]);

  useEffect(() =&amp;gt; {
    if (!userId) return;

    let socket;
    let retryTimer;
    let attempts = 0;
    let stopped = false;

    function connect() {
      if (stopped) return;

      setStatus(&quot;connecting&quot;);

      socket = new WebSocket(
        `ws://localhost:3000/ws?userId=${encodeURIComponent(userId)}`
      );

      socket.onopen = () =&amp;gt; {
        attempts = 0;
        setStatus(&quot;connected&quot;);
      };

      socket.onmessage = (event) =&amp;gt; {
        try {
          const data = JSON.parse(event.data);

          if (data.type === &quot;notification&quot;) {
            setMessages((previous) =&amp;gt; [
              data.payload,
              ...previous,
            ]);
          }
        } catch {
          console.error(&quot;Unable to parse socket message&quot;);
        }
      };

      socket.onerror = () =&amp;gt; {
        setStatus(&quot;error&quot;);
      };

      socket.onclose = () =&amp;gt; {
        if (stopped) return;

        setStatus(&quot;disconnected&quot;);

        const delay = Math.min(
          1000 * 2 ** attempts,
          30000
        );

        const jitter = Math.random() * 1000;
        attempts += 1;

        retryTimer = setTimeout(
          connect,
          delay + jitter
        );
      };
    }

    connect();

    return () =&amp;gt; {
      stopped = true;
      clearTimeout(retryTimer);

      if (socket) {
        socket.close();
      }
    };
  }, [userId]);

  return { status, messages };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This hook reconnects after an unexpected disconnection and stops retrying when the component is cleaned up.&lt;/p&gt;
&lt;p&gt;The retry delay starts around one second and increases up to approximately thirty seconds, with additional jitter. In a more advanced implementation, retry attempts can be reset after the connection remains healthy for a specified period.&lt;/p&gt;
&lt;p&gt;The browser's native WebSocket API does not provide automatic reconnection. Developers must implement it themselves or use a library that offers this feature. See the &lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WebSocket&quot;&gt;MDN WebSocket guide&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Reconnection alone does not guarantee that every notification will be received. The application must retrieve missed notifications from persistent storage after reconnecting.&lt;/p&gt;
&lt;h3 id=&quot;8-persisting-notifications-in-a-database&quot;&gt;8. Persisting Notifications in a Database&lt;/h3&gt;
&lt;p&gt;An in-memory notification list is useful for demonstrating the interface, but it has an important limitation: data disappears when the browser reloads or the server restarts.&lt;/p&gt;
&lt;p&gt;A real application should store notifications in a database.&lt;/p&gt;
&lt;p&gt;For example, a PostgreSQL table might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE notifications (
    id UUID PRIMARY KEY,
    recipient_id UUID NOT NULL,
    title VARCHAR(200) NOT NULL,
    message TEXT NOT NULL,
    event_type VARCHAR(100),
    resource_id UUID,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    read_at TIMESTAMPTZ
);

CREATE INDEX idx_notifications_recipient_created
ON notifications (recipient_id, created_at DESC);

CREATE INDEX idx_notifications_unread
ON notifications (recipient_id, created_at DESC)
WHERE read_at IS NULL;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each notification has a unique identifier, a recipient, a title, a message, a creation timestamp, and an optional read timestamp. The &lt;code&gt;resource_id&lt;/code&gt; field can connect the notification to an order, project, conversation, or other application record.&lt;/p&gt;
&lt;p&gt;The recipient index makes it easier to retrieve notifications for an individual user. The partial index can improve queries that retrieve only unread notifications.&lt;/p&gt;
&lt;p&gt;When a business event occurs, the backend should create the notification record before attempting delivery.&lt;/p&gt;
&lt;p&gt;A typical flow is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Validate and process the business action.&lt;/li&gt;
&lt;li&gt;Store the business data.&lt;/li&gt;
&lt;li&gt;Create the notification record.&lt;/li&gt;
&lt;li&gt;Publish a notification event.&lt;/li&gt;
&lt;li&gt;Deliver the event to connected clients.&lt;/li&gt;
&lt;li&gt;Allow the client to retrieve stored notifications later.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For important workflows, a transactional outbox pattern can help prevent the database record and event publication from becoming inconsistent. The application writes the business change and an outbox event in the same database transaction, then a background worker publishes pending events.&lt;/p&gt;
&lt;p&gt;This approach helps avoid situations where a transaction succeeds but the notification event is never published because the process crashes.&lt;/p&gt;
&lt;h3 id=&quot;9-loading-existing-notifications-through-an-http-api&quot;&gt;9. Loading Existing Notifications Through an HTTP API&lt;/h3&gt;
&lt;p&gt;WebSockets should handle live updates, while HTTP endpoints can retrieve notification history and manage read states.&lt;/p&gt;
&lt;p&gt;For example, an API could provide these endpoints:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Method &lt;/th&gt;&lt;th&gt;Endpoint &lt;/th&gt;&lt;th&gt;Purpose&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;GET &lt;/td&gt;&lt;td&gt;&lt;code&gt;/api/notifications&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Retrieve paginated notifications&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;GET &lt;/td&gt;&lt;td&gt;&lt;code&gt;/api/notifications/unread-count&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Retrieve the unread count&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;PATCH &lt;/td&gt;&lt;td&gt;&lt;code&gt;/api/notifications/:id/read&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Mark a notification as read&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;PATCH &lt;/td&gt;&lt;td&gt;&lt;code&gt;/api/notifications/read-all&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Mark all notifications as read&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Every endpoint must derive the recipient from the authenticated session or verified access token. It must never trust a user ID supplied by the request as proof of ownership.&lt;/p&gt;
&lt;p&gt;When React loads the notification center, it should first retrieve recent notifications through the HTTP API. It can then listen for incoming WebSocket events.&lt;/p&gt;
&lt;p&gt;Because a notification could arrive while the initial HTTP request is running, the frontend should merge the results using notification IDs rather than blindly replacing its entire list.&lt;/p&gt;
&lt;p&gt;The same principle applies when the user reconnects after being offline: fetch notifications created after the last successfully processed cursor or timestamp.&lt;/p&gt;
&lt;p&gt;A timestamp-only cursor may be insufficient when several events share the same timestamp. A stable, unique ordering key or event sequence is preferable for reliable pagination.&lt;/p&gt;
&lt;h3 id=&quot;10-implementing-read-and-unread-states-correctly&quot;&gt;10. Implementing Read and Unread States Correctly&lt;/h3&gt;
&lt;p&gt;Unread counters are among the most visible features of a notification system. However, they can become inaccurate if read state exists only in React memory.&lt;/p&gt;
&lt;p&gt;Consider a user who marks a notification as read on a laptop and later opens the application on a mobile device. Both devices should display the same read state.&lt;/p&gt;
&lt;p&gt;The backend should therefore own the authoritative notification status.&lt;/p&gt;
&lt;p&gt;When the user selects &lt;strong&gt;Mark as read&lt;/strong&gt;, React sends a request to the API. The server verifies that the notification belongs to the authenticated user, updates &lt;code&gt;read_at&lt;/code&gt;, and returns the updated record.&lt;/p&gt;
&lt;p&gt;The frontend can use optimistic updates to make the interface feel immediate. If the request fails, it should revert the local change or display a retry option.&lt;/p&gt;
&lt;p&gt;For a system with several connected devices, the server can also broadcast a &lt;code&gt;notification.read&lt;/code&gt; event to the user's other active connections.&lt;/p&gt;
&lt;p&gt;A useful event structure is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;type&quot;: &quot;notification.read&quot;,
  &quot;payload&quot;: {
    &quot;notificationId&quot;: &quot;notification-uuid&quot;,
    &quot;readAt&quot;: &quot;2026-10-20T09:00:00Z&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The server must validate every read operation. A notification identifier is not a permission token, and knowing another user's notification ID must not grant access to it.&lt;/p&gt;
&lt;p&gt;For bulk updates, use a database transaction where appropriate and return the authoritative unread count or enough information for the client to calculate it consistently.&lt;/p&gt;
&lt;h3 id=&quot;11-defining-a-consistent-event-protocol&quot;&gt;11. Defining a Consistent Event Protocol&lt;/h3&gt;
&lt;p&gt;As an application grows, sending arbitrary JSON objects through WebSockets becomes difficult to maintain. Establishing a consistent message format makes the system easier to test and extend.&lt;/p&gt;
&lt;p&gt;A notification event could follow this structure:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;type&quot;: &quot;notification.created&quot;,
  &quot;eventId&quot;: &quot;event-uuid&quot;,
  &quot;version&quot;: 1,
  &quot;occurredAt&quot;: &quot;2026-10-20T09:00:00Z&quot;,
  &quot;payload&quot;: {
    &quot;id&quot;: &quot;notification-uuid&quot;,
    &quot;title&quot;: &quot;New task assigned&quot;,
    &quot;message&quot;: &quot;A task has been assigned to you.&quot;,
    &quot;createdAt&quot;: &quot;2026-10-20T09:00:00Z&quot;,
    &quot;read&quot;: false
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;type&lt;/code&gt; field tells the client which operation to perform. The &lt;code&gt;eventId&lt;/code&gt; helps with tracing and deduplication. The &lt;code&gt;version&lt;/code&gt; field supports gradual protocol changes.&lt;/p&gt;
&lt;p&gt;A client-side event handler can route messages by type:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function handleNotificationEvent(event) {
  switch (event.type) {
    case &quot;notification.created&quot;:
      // Add the new notification.
      break;

    case &quot;notification.read&quot;:
      // Update the notification's read state.
      break;

    case &quot;notification.deleted&quot;:
      // Remove the notification if permitted.
      break;

    default:
      console.warn(&quot;Unknown event type:&quot;, event.type);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In production, validate incoming data against a schema and reject malformed or unsupported messages. Avoid rendering arbitrary server-provided HTML as notification content.&lt;/p&gt;
&lt;p&gt;Stable event contracts also make it easier to introduce mobile applications, additional backend services, and alternative delivery channels without redesigning the entire system.&lt;/p&gt;
&lt;h3 id=&quot;12-securing-websocket-connections&quot;&gt;12. Securing WebSocket Connections&lt;/h3&gt;
&lt;p&gt;Security must be part of the architecture from the beginning.&lt;/p&gt;
&lt;h4 id=&quot;use-secure-transport&quot;&gt;Use secure transport&lt;/h4&gt;
&lt;p&gt;Production applications should use &lt;code&gt;wss://&lt;/code&gt; for encrypted WebSocket connections. When a website is served over HTTPS, browsers generally block insecure WebSocket connections from that page.&lt;/p&gt;
&lt;h4 id=&quot;authenticate-users&quot;&gt;Authenticate users&lt;/h4&gt;
&lt;p&gt;The server must authenticate each connection using a verified session or token. It should derive the user identity from trusted authentication information rather than accepting an arbitrary &lt;code&gt;userId&lt;/code&gt; query parameter.&lt;/p&gt;
&lt;p&gt;Query parameters can appear in logs and monitoring systems. Avoid placing long-lived secrets in WebSocket URLs. Depending on the architecture, a secure session cookie, a short-lived single-use connection ticket, or an appropriate authentication handshake may be preferable.&lt;/p&gt;
&lt;h4 id=&quot;authorize-every-event&quot;&gt;Authorize every event&lt;/h4&gt;
&lt;p&gt;Authentication establishes who the user is; authorization determines what they may access. The server must ensure that a notification is delivered only to its intended recipient and that incoming actions affect only resources the user is allowed to access.&lt;/p&gt;
&lt;h4 id=&quot;validate-origins&quot;&gt;Validate origins&lt;/h4&gt;
&lt;p&gt;Validate the &lt;code&gt;Origin&lt;/code&gt; header against an explicit allowlist when serving browser clients. Origin checks are particularly important when cookies authenticate connections, but they are not a replacement for authentication.&lt;/p&gt;
&lt;h4 id=&quot;validate-payloads-and-limit-message-sizes&quot;&gt;Validate payloads and limit message sizes&lt;/h4&gt;
&lt;p&gt;Reject malformed JSON, unexpected event types, oversized messages, and unsupported operations. Apply rate limits to connection attempts and incoming messages.&lt;/p&gt;
&lt;h4 id=&quot;protect-sensitive-information&quot;&gt;Protect sensitive information&lt;/h4&gt;
&lt;p&gt;Notifications should contain only the information necessary for the user to understand the event. Sensitive financial, personal, or account data should be retrieved through appropriately authorized API endpoints.&lt;/p&gt;
&lt;h4 id=&quot;plan-for-expired-sessions&quot;&gt;Plan for expired sessions&lt;/h4&gt;
&lt;p&gt;If a user's session expires or access is revoked, close or reauthorize existing WebSocket connections. A connection that was valid when first opened should not remain trusted indefinitely.&lt;/p&gt;
&lt;p&gt;The &lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications&quot;&gt;MDN WebSocket client guide&lt;/a&gt; provides additional guidance on connection handling and security considerations.&lt;/p&gt;
&lt;h3 id=&quot;13-scaling-beyond-a-single-server&quot;&gt;13. Scaling Beyond a Single Server&lt;/h3&gt;
&lt;p&gt;The in-memory &lt;code&gt;Map&lt;/code&gt; used in our demonstration works for one server process. However, it creates a problem when the application runs across multiple instances.&lt;/p&gt;
&lt;p&gt;Imagine that a user connects to server A, while an event is processed by server B. Server B cannot see server A's local connection map. Without additional coordination, the notification may never reach the connected client.&lt;/p&gt;
&lt;p&gt;A distributed architecture can solve this problem using a message broker such as Redis Pub/Sub or a durable event streaming platform.&lt;/p&gt;
&lt;p&gt;The flow becomes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;An application service creates a notification.&lt;/li&gt;
&lt;li&gt;The notification is persisted in the database.&lt;/li&gt;
&lt;li&gt;An event is published to a broker.&lt;/li&gt;
&lt;li&gt;WebSocket server instances subscribe to relevant events.&lt;/li&gt;
&lt;li&gt;The instance holding the recipient's connection delivers the notification.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For larger systems, a broker with durable retention and consumer acknowledgments can help with reliable processing. Redis Pub/Sub provides lightweight message distribution but does not itself guarantee that disconnected subscribers will receive missed messages.&lt;/p&gt;
&lt;p&gt;Regardless of the broker, the database remains the source of truth for notification history.&lt;/p&gt;
&lt;p&gt;Horizontal scaling also requires attention to connection limits, memory usage, heartbeat handling, reverse-proxy timeouts, and load-balancer configuration. WebSocket connections are long-lived, so infrastructure must be configured to support them.&lt;/p&gt;
&lt;p&gt;Sticky sessions may be needed in some architectures, but they do not replace cross-instance event distribution.&lt;/p&gt;
&lt;h3 id=&quot;14-handling-duplicate-and-missing-notifications&quot;&gt;14. Handling Duplicate and Missing Notifications&lt;/h3&gt;
&lt;p&gt;Networks and distributed systems can deliver the same event more than once. Events may also be delayed, arrive out of order, or be missed during disconnections.&lt;/p&gt;
&lt;p&gt;A robust system should be designed around these possibilities.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Deduplication:&lt;/strong&gt; Store or track processed notification IDs and ignore duplicates when merging the list.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Persistence:&lt;/strong&gt; Save notifications before delivery so that reconnecting clients can retrieve them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Catch-up synchronization:&lt;/strong&gt; After reconnecting, request notifications created since the last confirmed cursor.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ordering:&lt;/strong&gt; Use stable event identifiers and sequence numbers when ordering is important.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Acknowledgments:&lt;/strong&gt; For critical workflows, define an acknowledgment protocol and retry policy. A WebSocket &lt;code&gt;send()&lt;/code&gt; call alone does not prove that the recipient processed or displayed the notification.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Idempotency:&lt;/strong&gt; Make repeated operations safe. For example, marking an already-read notification as read should not create an inconsistent state.&lt;/p&gt;
&lt;p&gt;The appropriate delivery guarantee depends on the product. A casual social reaction may tolerate a small delay, while a financial transaction alert requires stronger persistence, auditability, and recovery behavior.&lt;/p&gt;
&lt;h3 id=&quot;15-improving-frontend-performance&quot;&gt;15. Improving Frontend Performance&lt;/h3&gt;
&lt;p&gt;A notification center may eventually receive hundreds or thousands of events. React rendering and memory usage should be considered as early as possible.&lt;/p&gt;
&lt;p&gt;Useful optimization techniques include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Limit the number of notifications kept in active component state.&lt;/li&gt;
&lt;li&gt;Retrieve older notifications through pagination.&lt;/li&gt;
&lt;li&gt;Virtualize long lists to avoid rendering every row simultaneously.&lt;/li&gt;
&lt;li&gt;Batch related state updates where appropriate.&lt;/li&gt;
&lt;li&gt;Use stable notification IDs as React keys.&lt;/li&gt;
&lt;li&gt;Avoid creating a separate WebSocket connection for every notification component.&lt;/li&gt;
&lt;li&gt;Deduplicate events before adding them to state.&lt;/li&gt;
&lt;li&gt;Keep notification rendering lightweight.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The standard browser WebSocket API does not provide automatic backpressure. If messages arrive faster than the application can process them, memory usage and responsiveness may suffer. The MDN WebSocket documentation discusses this limitation.&lt;/p&gt;
&lt;p&gt;For high-volume systems, consider server-side filtering, bounded client queues, event batching, message coalescing, and flow-control strategies.&lt;/p&gt;
&lt;p&gt;For example, an application dashboard may not need to render every intermediate progress update. It can keep only the latest progress value while preserving important events in persistent storage.&lt;/p&gt;
&lt;h3 id=&quot;16-testing-the-notification-system&quot;&gt;16. Testing the Notification System&lt;/h3&gt;
&lt;p&gt;Testing should cover both the expected workflow and failure scenarios.&lt;/p&gt;
&lt;h4 id=&quot;unit-testing&quot;&gt;Unit testing&lt;/h4&gt;
&lt;p&gt;Test notification reducers, event parsers, deduplication logic, unread counts, and read-state updates independently of the WebSocket server.&lt;/p&gt;
&lt;h4 id=&quot;integration-testing&quot;&gt;Integration testing&lt;/h4&gt;
&lt;p&gt;Start the backend and connect a test client. Verify that a notification created for user A reaches user A but not user B.&lt;/p&gt;
&lt;h4 id=&quot;reconnection-testing&quot;&gt;Reconnection testing&lt;/h4&gt;
&lt;p&gt;Disconnect the client, create notifications, reconnect, and confirm that the HTTP catch-up process retrieves missed events.&lt;/p&gt;
&lt;h4 id=&quot;authorization-testing&quot;&gt;Authorization testing&lt;/h4&gt;
&lt;p&gt;Attempt to read another user's notification, mark another user's notification as read, and connect without valid authentication. All such attempts should be rejected.&lt;/p&gt;
&lt;h4 id=&quot;load-testing&quot;&gt;Load testing&lt;/h4&gt;
&lt;p&gt;Simulate many concurrent connections and bursts of events. Measure delivery latency, CPU utilization, memory consumption, connection failures, and database performance.&lt;/p&gt;
&lt;h4 id=&quot;browser-testing&quot;&gt;Browser testing&lt;/h4&gt;
&lt;p&gt;Verify behavior during navigation, page refreshes, temporary network failures, session expiration, and multiple browser tabs.&lt;/p&gt;
&lt;p&gt;React's development Strict Mode may run an additional setup-and-cleanup cycle for effects. Ensure that socket cleanup is correct and that the application does not accumulate unnecessary connections during development.&lt;/p&gt;
&lt;h3 id=&quot;17-monitoring-and-production-deployment&quot;&gt;17. Monitoring and Production Deployment&lt;/h3&gt;
&lt;p&gt;A system that works locally may fail under real traffic unless it is monitored and deployed correctly.&lt;/p&gt;
&lt;p&gt;Track metrics such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Active WebSocket connections.&lt;/li&gt;
&lt;li&gt;Successful and failed connection attempts.&lt;/li&gt;
&lt;li&gt;Notification delivery latency.&lt;/li&gt;
&lt;li&gt;Reconnection frequency.&lt;/li&gt;
&lt;li&gt;Broker queue depth and processing delays.&lt;/li&gt;
&lt;li&gt;Database query duration.&lt;/li&gt;
&lt;li&gt;Invalid messages and authorization failures.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Use structured logs and correlation IDs to trace a notification from the original business event to database persistence, broker publication, WebSocket delivery, and client acknowledgment.&lt;/p&gt;
&lt;p&gt;Do not log authentication tokens or sensitive notification payloads.&lt;/p&gt;
&lt;p&gt;For deployment, configure a reverse proxy or load balancer to support WebSocket upgrades and long-lived connections. Ensure that proxy timeouts are appropriate, and implement heartbeat or ping/pong mechanisms to detect stale connections.&lt;/p&gt;
&lt;p&gt;Configure environment variables for service URLs, authentication settings, and database credentials. Secrets should be managed through an appropriate secrets-management system rather than committed to source control.&lt;/p&gt;
&lt;p&gt;Finally, use health checks and graceful shutdown procedures. When a server instance is about to stop, it should stop accepting new connections, notify clients when possible, close existing connections cleanly, and allow clients to reconnect to a healthy instance.&lt;/p&gt;
&lt;h3 id=&quot;18-common-mistakes-to-avoid-1&quot;&gt;18. Common Mistakes to Avoid&lt;/h3&gt;
&lt;p&gt;Several implementation mistakes can undermine a real-time notification system.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Using WebSockets without persistence:&lt;/strong&gt; Users may lose access to important events after refreshing the browser.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Trusting client-provided user IDs:&lt;/strong&gt; This can expose notifications to unauthorized users.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ignoring cleanup:&lt;/strong&gt; Old connections may remain active after a React component unmounts or its user changes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Assuming delivery is guaranteed:&lt;/strong&gt; A successful connection does not guarantee that every event was received and processed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Creating too many connections:&lt;/strong&gt; Multiple components opening independent sockets can waste server resources.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Skipping reconnect synchronization:&lt;/strong&gt; Reconnecting without fetching missed events leaves gaps in notification history.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ignoring cross-instance delivery:&lt;/strong&gt; Local in-memory connection tracking is insufficient for a multi-server deployment.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sending sensitive information through notification payloads:&lt;/strong&gt; Excessive data increases the impact of accidental exposure.&lt;/p&gt;
&lt;p&gt;Addressing these issues early can prevent expensive architectural changes later.&lt;/p&gt;
&lt;h3 id=&quot;conclusion-3&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Building a real-time notification system with React and WebSockets is an excellent way to improve the responsiveness of modern web applications. WebSockets provide a persistent communication channel, while React makes it straightforward to render incoming events and update notification counts.&lt;/p&gt;
&lt;p&gt;However, a production-ready solution requires more than opening a socket and displaying a message. It needs authenticated connections, authorization checks, persistent notification records, reconnection handling, duplicate protection, missed-event recovery, and a scalable event-distribution architecture.&lt;/p&gt;
&lt;p&gt;A practical development path is to begin with a single WebSocket server and React notification component, introduce database persistence, add HTTP endpoints for notification history and read states, and then implement distributed messaging when scaling demands it.&lt;/p&gt;
&lt;p&gt;By combining these practices, developers can build notification systems that remain responsive, secure, maintainable, and reliable as an application grows.&lt;/p&gt;
&lt;p&gt;Whether you are developing a project management dashboard, an e-commerce platform, a customer relationship management system, or a collaborative workspace, the same core principles apply: persist important events, deliver them efficiently, authorize every operation, and design for interruptions from the beginning.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31801/building-a-real-time-notification-system-with-react-and-websockets</guid>
<pubDate>Tue, 20 Oct 2026 03:24:57 +0000</pubDate>
</item>
<item>
<title>🔥 30 Days of Frontend — Day 5: React DevTools Profiler</title>
<link>https://coderlegion.com/31532/30-days-of-frontend-day-5-react-devtools-profiler</link>
<description>&lt;p&gt;When a React application starts feeling slow, one of the first things developers often do is reach for &lt;code&gt;useMemo&lt;/code&gt;, &lt;code&gt;useCallback&lt;/code&gt;, or &lt;code&gt;React.memo&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We've all seen this pattern:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A component renders more often than expected.&lt;/li&gt;
&lt;li&gt;The UI feels sluggish when interacting with a page.&lt;/li&gt;
&lt;li&gt;A large list becomes noticeably slower as more items are added.&lt;/li&gt;
&lt;li&gt;Updating one piece of state seems to trigger work across an entire component tree.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The natural reaction is to start memoizing components and functions.&lt;/p&gt;
&lt;p&gt;But here's the problem: &lt;strong&gt;you might be optimizing code that isn't responsible for the slowdown in the first place.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;React performance optimization should start with understanding what is happening, not guessing what might be happening.&lt;/p&gt;
&lt;p&gt;That's where the &lt;strong&gt;React DevTools Profiler&lt;/strong&gt; becomes useful.&lt;/p&gt;
&lt;h3 id=&quot;1-what-is-the-react-devtools-profiler-&quot;&gt;1. What Is the React DevTools Profiler?&lt;/h3&gt;
&lt;p&gt;React DevTools is a browser extension that helps developers inspect React component trees, props, state, and other component-related information.&lt;/p&gt;
&lt;p&gt;It includes two particularly useful panels:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Components:&lt;/strong&gt; Inspect the component hierarchy, props, state, and component details.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Profiler:&lt;/strong&gt; Record rendering activity and investigate how React updated the component tree.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Profiler helps you understand the rendering work associated with a particular interaction.&lt;/p&gt;
&lt;p&gt;For example, imagine you're working on an e-commerce application with a product grid containing hundreds of products.&lt;/p&gt;
&lt;p&gt;When a user changes a filter, several things might happen:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The parent component updates.&lt;/li&gt;
&lt;li&gt;The product list renders again.&lt;/li&gt;
&lt;li&gt;Individual product cards render.&lt;/li&gt;
&lt;li&gt;Sorting or filtering calculations run.&lt;/li&gt;
&lt;li&gt;Some components render even though their visible data hasn't changed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some of this work might be necessary. Some might be avoidable.&lt;/p&gt;
&lt;p&gt;Instead of assuming which component is responsible, you can record the interaction and inspect the results.&lt;/p&gt;
&lt;p&gt;The goal isn't to eliminate every render. It's to understand whether the work is necessary and whether it affects the user experience.&lt;/p&gt;
&lt;h3 id=&quot;2-understanding-how-react-rendering-relates-to-profiling&quot;&gt;2. Understanding How React Rendering Relates to Profiling&lt;/h3&gt;
&lt;p&gt;Before opening the Profiler, it's worth understanding what it actually measures.&lt;/p&gt;
&lt;p&gt;A React update generally involves two important phases.&lt;/p&gt;
&lt;h4 id=&quot;the-render-phase&quot;&gt;The render phase&lt;/h4&gt;
&lt;p&gt;During the render phase, React calls components as needed to determine what the next UI should look like.&lt;/p&gt;
&lt;p&gt;This phase includes calculating the next React element tree and determining the changes required.&lt;/p&gt;
&lt;p&gt;A component rendering doesn't automatically mean that the browser DOM will change.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;function Counter({ count }) {
  console.log(&quot;Counter rendered&quot;);

  return &amp;lt;p&amp;gt;Count: {count}&amp;lt;/p&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Whenever React calls &lt;code&gt;Counter&lt;/code&gt;, the component function executes and returns a React element describing the UI.&lt;/p&gt;
&lt;p&gt;React may then determine that the resulting output doesn't require a DOM mutation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A render is not the same thing as a DOM update.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This distinction is important because developers sometimes interpret every component render as expensive browser work.&lt;/p&gt;
&lt;h4 id=&quot;the-commit-phase&quot;&gt;The commit phase&lt;/h4&gt;
&lt;p&gt;After React finishes the necessary render work, it can commit the changes to the host environment, such as updating the DOM in a browser.&lt;/p&gt;
&lt;p&gt;The Profiler helps you investigate rendering activity, but its measurements shouldn't be interpreted as a complete measurement of every part of browser performance.&lt;/p&gt;
&lt;p&gt;For example, a page can have relatively inexpensive React rendering but still feel slow because of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Expensive JavaScript outside React.&lt;/li&gt;
&lt;li&gt;Large amounts of DOM layout or painting.&lt;/li&gt;
&lt;li&gt;Long-running event handlers.&lt;/li&gt;
&lt;li&gt;Network delays.&lt;/li&gt;
&lt;li&gt;Large images or other resources.&lt;/li&gt;
&lt;li&gt;Main-thread work that blocks user interactions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is why the React Profiler is one tool in a broader performance investigation, not a replacement for the browser's Performance panel.&lt;/p&gt;
&lt;h3 id=&quot;3-what-can-you-learn-from-the-profiler-&quot;&gt;3. What Can You Learn From the Profiler?&lt;/h3&gt;
&lt;p&gt;When you record a profiling session, the Profiler provides several useful ways to understand React's work.&lt;/p&gt;
&lt;h4 id=&quot;a-which-components-rendered-&quot;&gt;A. Which components rendered?&lt;/h4&gt;
&lt;p&gt;The Profiler helps you identify the components involved in an update.&lt;/p&gt;
&lt;p&gt;Suppose your component tree looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;App
├── Header
├── SearchBar
├── FilterSidebar
└── ProductGrid
    ├── ProductCard
    ├── ProductCard
    ├── ProductCard
    └── ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the user types into the search field, you might discover that &lt;code&gt;SearchBar&lt;/code&gt;, &lt;code&gt;ProductGrid&lt;/code&gt;, and many &lt;code&gt;ProductCard&lt;/code&gt; instances rendered.&lt;/p&gt;
&lt;p&gt;That doesn't automatically mean all those renders are unnecessary.&lt;/p&gt;
&lt;p&gt;You need to understand why they rendered and whether the work was expensive.&lt;/p&gt;
&lt;p&gt;The component tree gives you a starting point for that investigation.&lt;/p&gt;
&lt;h4 id=&quot;b-how-often-did-components-render-&quot;&gt;B. How often did components render?&lt;/h4&gt;
&lt;p&gt;Repeated rendering can be perfectly normal.&lt;/p&gt;
&lt;p&gt;For example, typing into a controlled input typically updates state as the user types:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;function SearchBar() {
  const [query, setQuery] = React.useState(&quot;&quot;);

  return (
    &amp;lt;input
      value={query}
      onChange={(event) =&amp;gt; {
        setQuery(event.target.value);
      }}
      placeholder=&quot;Search products...&quot;
    /&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each keystroke can trigger an update.&lt;/p&gt;
&lt;p&gt;That's expected behavior.&lt;/p&gt;
&lt;p&gt;The more interesting question is what else happens during those updates.&lt;/p&gt;
&lt;p&gt;If typing into the search field also triggers expensive calculations or unnecessary rendering across a large product grid, the Profiler can help you investigate that work.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Render frequency is a clue, not a performance verdict.&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id=&quot;c-what-caused-a-component-to-render-&quot;&gt;C. What caused a component to render?&lt;/h4&gt;
&lt;p&gt;This is one of the most useful parts of profiling.&lt;/p&gt;
&lt;p&gt;A component might render because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Its state changed.&lt;/li&gt;
&lt;li&gt;Its parent rendered.&lt;/li&gt;
&lt;li&gt;Its props changed.&lt;/li&gt;
&lt;li&gt;Its context value changed.&lt;/li&gt;
&lt;li&gt;An external store or another subscription triggered an update.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The exact cause depends on the component and the update mechanism.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;function ProductPage() {
  const [query, setQuery] = React.useState(&quot;&quot;);
  const [selectedCategory, setSelectedCategory] =
    React.useState(&quot;all&quot;);

  return (
    &amp;lt;&amp;gt;
      &amp;lt;SearchInput
        value={query}
        onChange={setQuery}
      /&amp;gt;

      &amp;lt;CategoryFilter
        value={selectedCategory}
        onChange={setSelectedCategory}
      /&amp;gt;

      &amp;lt;ProductGrid query={query} /&amp;gt;
    &amp;lt;/&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When &lt;code&gt;selectedCategory&lt;/code&gt; changes, &lt;code&gt;ProductPage&lt;/code&gt; renders again.&lt;/p&gt;
&lt;p&gt;Because &lt;code&gt;ProductGrid&lt;/code&gt; is a child of &lt;code&gt;ProductPage&lt;/code&gt;, React may also render &lt;code&gt;ProductGrid&lt;/code&gt;, even though its &lt;code&gt;query&lt;/code&gt; prop hasn't changed.&lt;/p&gt;
&lt;p&gt;That can be a candidate for optimization, but it isn't automatically a problem. First, determine whether the work is expensive enough to matter.&lt;/p&gt;
&lt;p&gt;Also, remember that React DevTools' ability to display detailed render reasons depends on the React version, development tooling, and available settings. Don't assume every render will always have a definitive explanation displayed.&lt;/p&gt;
&lt;h3 id=&quot;4-how-to-use-react-devtools-profiler-a-step-by-step-workflow&quot;&gt;4. How to Use React DevTools Profiler: A Step-by-Step Workflow&lt;/h3&gt;
&lt;p&gt;Let's turn this into a practical workflow.&lt;/p&gt;
&lt;h4 id=&quot;step-1-reproduce-the-actual-performance-problem&quot;&gt;Step 1: Reproduce the actual performance problem&lt;/h4&gt;
&lt;p&gt;Start with the user interaction that feels slow.&lt;/p&gt;
&lt;p&gt;Examples include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Typing into a search field.&lt;/li&gt;
&lt;li&gt;Changing a product filter.&lt;/li&gt;
&lt;li&gt;Switching tabs.&lt;/li&gt;
&lt;li&gt;Expanding a large navigation menu.&lt;/li&gt;
&lt;li&gt;Updating a dashboard with multiple charts.&lt;/li&gt;
&lt;li&gt;Scrolling through a long list.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Avoid profiling random interactions without a clear question.&lt;/p&gt;
&lt;p&gt;A useful starting question might be:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Why does changing a product filter cause so much rendering work?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This gives you a specific behavior to investigate.&lt;/p&gt;
&lt;h4 id=&quot;step-2-open-react-devtools&quot;&gt;Step 2: Open React DevTools&lt;/h4&gt;
&lt;p&gt;Install React Developer Tools for your browser if you haven't already.&lt;/p&gt;
&lt;p&gt;Open your application, launch the browser's developer tools, and locate the &lt;strong&gt;Profiler&lt;/strong&gt; tab.&lt;/p&gt;
&lt;p&gt;Make sure you're profiling the application and interaction you want to investigate.&lt;/p&gt;
&lt;p&gt;For realistic performance measurements, prefer a production build or a profiling-enabled production build where appropriate. Development mode can introduce extra overhead, and Strict Mode may intentionally invoke rendering-related logic more than once to help reveal bugs.&lt;/p&gt;
&lt;p&gt;Development profiling is still valuable for understanding component behavior, but don't assume its timings represent production performance.&lt;/p&gt;
&lt;h4 id=&quot;step-3-start-recording&quot;&gt;Step 3: Start recording&lt;/h4&gt;
&lt;p&gt;In the Profiler tab, start a recording.&lt;/p&gt;
&lt;p&gt;Then perform the exact interaction that causes the slowdown.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start recording.&lt;/li&gt;
&lt;li&gt;Type a search query.&lt;/li&gt;
&lt;li&gt;Change a category filter.&lt;/li&gt;
&lt;li&gt;Wait for the UI to settle.&lt;/li&gt;
&lt;li&gt;Stop recording.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Keep the recording focused.&lt;/p&gt;
&lt;h4 id=&quot;step-4-inspect-the-recorded-commits&quot;&gt;Step 4: Inspect the recorded commits&lt;/h4&gt;
&lt;p&gt;The Profiler organizes recorded updates into commits.&lt;/p&gt;
&lt;p&gt;A commit represents a point at which React applied an update to the host environment&lt;/p&gt;
&lt;p&gt;Select a commit related to your interaction, then inspect the available visualization and component details.&lt;/p&gt;
&lt;p&gt;Look for patterns such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A large subtree rendering during a small interaction.&lt;/li&gt;
&lt;li&gt;An expensive component repeatedly appearing in relevant commits.&lt;/li&gt;
&lt;li&gt;Components rendering even though their inputs appear unchanged.&lt;/li&gt;
&lt;li&gt;Expensive calculations associated with frequently occurring updates.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Don't immediately optimize the component with the largest-looking bar. First establish what the measurement represents and whether that component is related to the user-visible problem.&lt;/p&gt;
&lt;h4 id=&quot;step-5-make-one-targeted-change&quot;&gt;Step 5: Make one targeted change&lt;/h4&gt;
&lt;p&gt;Change one thing at a time whenever possible.&lt;/p&gt;
&lt;p&gt;If you simultaneously add &lt;code&gt;React.memo&lt;/code&gt;, introduce several &lt;code&gt;useMemo&lt;/code&gt; calls, restructure the component tree, and change how state is managed, you won't know which change made the difference.&lt;/p&gt;
&lt;p&gt;A focused experiment makes the results easier to interpret.&lt;/p&gt;
&lt;h4 id=&quot;step-6-profile-again&quot;&gt;Step 6: Profile again&lt;/h4&gt;
&lt;p&gt;Repeat the same interaction under comparable conditions.&lt;/p&gt;
&lt;p&gt;Compare:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Actual rendering duration.&lt;/li&gt;
&lt;li&gt;Which components rendered.&lt;/li&gt;
&lt;li&gt;How much of the subtree participated in the update.&lt;/li&gt;
&lt;li&gt;Whether the interaction feels more responsive.&lt;/li&gt;
&lt;li&gt;Whether the optimization introduced additional complexity or bugs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If the measured React work decreased but the interaction still feels slow, investigate other parts of the performance pipeline.&lt;/p&gt;
&lt;p&gt;An optimization isn't complete until you've verified its effect.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The React DevTools Profiler helps turn vague performance complaints into concrete questions about rendering behavior.&lt;/p&gt;
&lt;p&gt;Instead of asking:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;Why is React so slow?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Start by asking:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;Where is my application actually spending time, and what evidence do I have?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That shift—from guessing to measuring—is what makes performance optimization more systematic.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/31532/30-days-of-frontend-day-5-react-devtools-profiler</guid>
<pubDate>Sat, 17 Oct 2026 16:39:44 +0000</pubDate>
</item>
<item>
<title>How to Build a Lead Management Dashboard from Scratch</title>
<link>https://coderlegion.com/31356/how-to-build-a-lead-management-dashboard-from-scratch</link>
<description>&lt;p&gt;In today's competitive digital business environment, generating leads is only the first step toward acquiring customers. Businesses also need an effective way to organize prospects, track communication, monitor sales activities, and measure conversion rates. Without a centralized system, important leads can get lost, follow-ups may be delayed, and sales opportunities can disappear.&lt;/p&gt;
&lt;p&gt;A lead management dashboard solves these problems by bringing essential customer and sales information into one organized interface. It helps sales representatives, marketing teams, business owners, and customer relationship managers understand what is happening throughout the sales pipeline.&lt;/p&gt;
&lt;p&gt;Building a lead management dashboard from scratch is an excellent full-stack development project because it combines frontend design, backend development, database management, authentication, data visualization, and business logic.&lt;/p&gt;
&lt;p&gt;In this article, we will explore how to plan, design, develop, secure, test, and deploy a lead management dashboard using modern web technologies such as React, FastAPI, PostgreSQL, and supporting tools.&lt;/p&gt;
&lt;p&gt;By the end, you will understand the architecture and essential features needed to build a practical lead management application.&lt;/p&gt;
&lt;h3 id=&quot;1-what-is-a-lead-management-dashboard-&quot;&gt;1. What Is a Lead Management Dashboard?&lt;/h3&gt;
&lt;p&gt;A lead management dashboard is a centralized web application that allows businesses to manage potential customers from their initial interaction through qualification, follow-up, and conversion.&lt;/p&gt;
&lt;p&gt;A lead may come from a website contact form, social media campaign, paid advertisement, email subscription, referral, or direct sales inquiry.&lt;/p&gt;
&lt;p&gt;The dashboard stores this information and makes it accessible to authorized team members.&lt;/p&gt;
&lt;p&gt;For example, imagine a digital marketing agency receiving 200 leads each month from Google Ads, Facebook campaigns, and its website. Managing every prospect manually through spreadsheets can quickly become confusing.&lt;/p&gt;
&lt;p&gt;A lead management dashboard helps the agency:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Store lead information in one place.&lt;/li&gt;
&lt;li&gt;Identify the source of each lead.&lt;/li&gt;
&lt;li&gt;Assign leads to sales representatives.&lt;/li&gt;
&lt;li&gt;Track follow-up activities.&lt;/li&gt;
&lt;li&gt;Monitor lead status and sales progress.&lt;/li&gt;
&lt;li&gt;Measure campaign performance.&lt;/li&gt;
&lt;li&gt;Identify leads that need immediate attention.&lt;/li&gt;
&lt;li&gt;Generate reports for business decisions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Unlike a simple contact list, a lead management dashboard focuses on the complete process of turning prospects into customers.&lt;/p&gt;
&lt;h3 id=&quot;2-define-the-project-requirements&quot;&gt;2. Define the Project Requirements&lt;/h3&gt;
&lt;p&gt;Before writing code, establish what the application should accomplish. Clear requirements prevent unnecessary complexity and help developers prioritize the features that deliver the most value.&lt;/p&gt;
&lt;p&gt;For the first version, focus on a minimum viable product (MVP).&lt;/p&gt;
&lt;h4 id=&quot;essential-features&quot;&gt;Essential features&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Lead management:&lt;/strong&gt; Users should be able to create, view, edit, and delete lead records according to their permissions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dashboard statistics:&lt;/strong&gt; Display total leads, new leads, qualified leads, converted leads, and other relevant metrics.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sales pipeline:&lt;/strong&gt; Organize prospects into stages such as New, Contacted, Qualified, Proposal, Won, and Lost.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Search and filtering:&lt;/strong&gt; Allow users to find leads by name, email, source, status, or assigned sales representative.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Task and follow-up management:&lt;/strong&gt; Record upcoming calls, meetings, emails, and reminders.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lead assignment:&lt;/strong&gt; Assign prospects to individual sales representatives or teams.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reports and analytics:&lt;/strong&gt; Visualize lead sources, conversion rates, and changes in lead volume over time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Authentication and authorization:&lt;/strong&gt; Ensure that users can access only the features and records permitted by their roles.&lt;/p&gt;
&lt;h4 id=&quot;advanced-features&quot;&gt;Advanced features&lt;/h4&gt;
&lt;p&gt;Once the core application works reliably, consider adding email integrations, automated notifications, CSV imports and exports, duplicate detection, lead scoring, and marketing platform integrations.&lt;/p&gt;
&lt;p&gt;Starting with a manageable scope is better than attempting to build an entire enterprise CRM in the first release.&lt;/p&gt;
&lt;h3 id=&quot;3-choose-the-technology-stack&quot;&gt;3. Choose the Technology Stack&lt;/h3&gt;
&lt;p&gt;Selecting the right technology stack affects development speed, performance, maintainability, and scalability.&lt;/p&gt;
&lt;p&gt;For this project, React, FastAPI, and PostgreSQL provide a practical combination.&lt;/p&gt;
&lt;h4 id=&quot;frontend-react-1&quot;&gt;Frontend: React&lt;/h4&gt;
&lt;p&gt;React is a JavaScript library for building interactive user interfaces. It allows developers to create reusable components for dashboard cards, tables, filters, forms, and charts.&lt;/p&gt;
&lt;p&gt;Useful supporting tools include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;React Router for page navigation.&lt;/li&gt;
&lt;li&gt;Axios or the Fetch API for HTTP requests.&lt;/li&gt;
&lt;li&gt;Recharts for data visualization.&lt;/li&gt;
&lt;li&gt;React Hook Form for form management.&lt;/li&gt;
&lt;li&gt;Tailwind CSS for responsive styling.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;backend-fastapi&quot;&gt;Backend: FastAPI&lt;/h4&gt;
&lt;p&gt;FastAPI is a Python framework for developing APIs. It supports request validation, asynchronous endpoints, automatic API documentation, and integration with Python libraries.&lt;/p&gt;
&lt;p&gt;It can handle lead creation, authentication, filtering, reporting, assignment, and other business operations.&lt;/p&gt;
&lt;h4 id=&quot;database-postgresql-1&quot;&gt;Database: PostgreSQL&lt;/h4&gt;
&lt;p&gt;PostgreSQL is a relational database suitable for structured business data. It supports relationships, transactions, indexing, constraints, and advanced queries.&lt;/p&gt;
&lt;p&gt;These features are important when leads belong to organizations, sales representatives own leads, and activities reference individual prospects.&lt;/p&gt;
&lt;h4 id=&quot;additional-tools-1&quot;&gt;Additional tools&lt;/h4&gt;
&lt;p&gt;Consider using SQLAlchemy for database access, Alembic for schema migrations, Pydantic for validation, pytest for automated testing, Git for version control, and Docker for consistent deployment environments.&lt;/p&gt;
&lt;p&gt;The objective is not to use every available library. Choose tools that solve specific project requirements.&lt;/p&gt;
&lt;h3 id=&quot;4-design-the-application-architecture&quot;&gt;4. Design the Application Architecture&lt;/h3&gt;
&lt;p&gt;A clean architecture separates the user interface, business logic, and data storage.&lt;/p&gt;
&lt;p&gt;A typical request follows this path:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A user interacts with the React dashboard.&lt;/li&gt;
&lt;li&gt;React sends an HTTP request to the FastAPI backend.&lt;/li&gt;
&lt;li&gt;FastAPI validates the request and checks the user's permissions.&lt;/li&gt;
&lt;li&gt;The backend executes the required business logic.&lt;/li&gt;
&lt;li&gt;SQLAlchemy communicates with PostgreSQL.&lt;/li&gt;
&lt;li&gt;The backend returns a structured JSON response.&lt;/li&gt;
&lt;li&gt;React updates the dashboard with the latest information.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For example, when a sales representative creates a new lead, the frontend submits the form data to an API endpoint. The backend validates the information, associates the record with the appropriate organization, saves it in PostgreSQL, and returns the newly created lead.&lt;/p&gt;
&lt;p&gt;The frontend then updates the lead table and relevant statistics.&lt;/p&gt;
&lt;p&gt;Separating these responsibilities makes the application easier to maintain, test, and extend.&lt;/p&gt;
&lt;h3 id=&quot;5-plan-the-database-schema&quot;&gt;5. Plan the Database Schema&lt;/h3&gt;
&lt;p&gt;Database design is one of the most important steps in building a reliable lead management system.&lt;/p&gt;
&lt;p&gt;A poorly designed schema can produce duplicate records, inconsistent statuses, slow queries, and difficult reporting.&lt;/p&gt;
&lt;p&gt;Start by identifying the main entities.&lt;/p&gt;
&lt;h4 id=&quot;leads-table&quot;&gt;Leads table&lt;/h4&gt;
&lt;p&gt;The leads table stores prospect information.&lt;/p&gt;
&lt;p&gt;Recommended fields include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;id&lt;/code&gt;: Unique lead identifier.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;first_name&lt;/code&gt;: Prospect's first name.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;last_name&lt;/code&gt;: Prospect's last name.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;email&lt;/code&gt;: Email address.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;phone&lt;/code&gt;: Contact number.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;company&lt;/code&gt;: Company name.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;source&lt;/code&gt;: Origin of the lead.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;status&lt;/code&gt;: Current sales stage.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;assigned_to&lt;/code&gt;: Assigned user identifier.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;organization_id&lt;/code&gt;: Organization that owns the record.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;created_at&lt;/code&gt;: Creation timestamp.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;updated_at&lt;/code&gt;: Last modification timestamp.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;users-table&quot;&gt;Users table&lt;/h4&gt;
&lt;p&gt;The users table stores employee or account information.&lt;/p&gt;
&lt;p&gt;Typical fields include a unique identifier, name, email address, password hash, role, organization identifier, and account status.&lt;/p&gt;
&lt;p&gt;Never store passwords as plain text.&lt;/p&gt;
&lt;h4 id=&quot;activities-table&quot;&gt;Activities table&lt;/h4&gt;
&lt;p&gt;An activities table records interactions associated with leads.&lt;/p&gt;
&lt;p&gt;Its fields may include the lead identifier, activity type, description, scheduled time, completion status, creator, and creation timestamp.&lt;/p&gt;
&lt;p&gt;Examples of activities include phone calls, email conversations, meetings, and follow-up tasks.&lt;/p&gt;
&lt;h4 id=&quot;organizations-table&quot;&gt;Organizations table&lt;/h4&gt;
&lt;p&gt;If the dashboard serves multiple businesses, create an organizations table. Every relevant record should be associated with the correct organization.&lt;/p&gt;
&lt;p&gt;This helps prevent one company's users from accessing another company's customer information.&lt;/p&gt;
&lt;h4 id=&quot;campaigns-table&quot;&gt;Campaigns table&lt;/h4&gt;
&lt;p&gt;A campaigns table can store marketing campaign names, channels, budgets, and external identifiers. Leads can reference campaigns to support more detailed attribution reporting.&lt;/p&gt;
&lt;p&gt;Use foreign keys to maintain relationships and database constraints to enforce important rules.&lt;/p&gt;
&lt;h3 id=&quot;6-create-the-backend-project&quot;&gt;6. Create the Backend Project&lt;/h3&gt;
&lt;p&gt;Begin by creating a dedicated backend directory.&lt;/p&gt;
&lt;p&gt;A simple project structure could look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;backend/
├── app/
│   ├── main.py
│   ├── database.py
│   ├── models.py
│   ├── schemas.py
│   ├── auth.py
│   ├── dependencies.py
│   └── routers/
│       ├── leads.py
│       ├── users.py
│       ├── activities.py
│       └── analytics.py
├── tests/
├── alembic/
├── requirements.txt
└── .env
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This structure separates API endpoints from database models, validation schemas, and authentication logic.&lt;/p&gt;
&lt;p&gt;Install FastAPI, Uvicorn, SQLAlchemy, a PostgreSQL driver, Pydantic settings, and the authentication libraries you choose.&lt;/p&gt;
&lt;p&gt;Create the FastAPI application in &lt;code&gt;main.py&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from fastapi import FastAPI
from app.routers import leads, users, activities, analytics

app = FastAPI(title=&quot;Lead Management API&quot;)

app.include_router(leads.router, prefix=&quot;/api/leads&quot;)
app.include_router(users.router, prefix=&quot;/api/users&quot;)
app.include_router(
    activities.router,
    prefix=&quot;/api/activities&quot;
)
app.include_router(
    analytics.router,
    prefix=&quot;/api/analytics&quot;
)

@app.get(&quot;/health&quot;)
def health_check():
    return {&quot;status&quot;: &quot;healthy&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This example assumes each router module exports a configured &lt;code&gt;router&lt;/code&gt; object.&lt;/p&gt;
&lt;p&gt;The health endpoint provides a simple way to verify that the backend is running.&lt;/p&gt;
&lt;p&gt;Keep configuration values such as database credentials in environment variables rather than hardcoding them into source files.&lt;/p&gt;
&lt;h3 id=&quot;7-connect-postgresql-to-fastapi&quot;&gt;7. Connect PostgreSQL to FastAPI&lt;/h3&gt;
&lt;p&gt;Create a PostgreSQL database for the application and configure its connection string through an environment variable.&lt;/p&gt;
&lt;p&gt;SQLAlchemy can manage database connections and transactions.&lt;/p&gt;
&lt;p&gt;A simplified database setup might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import os
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

DATABASE_URL = os.environ[&quot;DATABASE_URL&quot;]

engine = create_engine(
    DATABASE_URL,
    pool_pre_ping=True
)

SessionLocal = sessionmaker(
    bind=engine,
    autoflush=False,
    expire_on_commit=False
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In production, choose an appropriate connection pool size and configure database timeouts according to the hosting environment.&lt;/p&gt;
&lt;p&gt;Create a FastAPI dependency that provides a database session for each request and reliably closes it afterward.&lt;/p&gt;
&lt;p&gt;Use Alembic to manage schema changes. Avoid relying on automatic table creation as your only production migration strategy.&lt;/p&gt;
&lt;h3 id=&quot;8-build-the-lead-api&quot;&gt;8. Build the Lead API&lt;/h3&gt;
&lt;p&gt;The backend should expose endpoints that represent the core operations of the application.&lt;/p&gt;
&lt;p&gt;A practical API design might include:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Method &lt;/th&gt;&lt;th&gt;Endpoint &lt;/th&gt;&lt;th&gt;Purpose&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;GET &lt;/td&gt;&lt;td&gt;&lt;code&gt;/api/leads&lt;/code&gt; &lt;/td&gt;&lt;td&gt;List and filter leads&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;POST &lt;/td&gt;&lt;td&gt;&lt;code&gt;/api/leads&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Create a lead&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;GET &lt;/td&gt;&lt;td&gt;&lt;code&gt;/api/leads/{id}&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Retrieve one lead&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;PATCH &lt;/td&gt;&lt;td&gt;&lt;code&gt;/api/leads/{id}&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Update lead details&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;DELETE &lt;/td&gt;&lt;td&gt;&lt;code&gt;/api/leads/{id}&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Delete or archive a lead&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;GET &lt;/td&gt;&lt;td&gt;&lt;code&gt;/api/analytics/summary&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Retrieve dashboard statistics&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Use Pydantic schemas to validate incoming data and control which fields appear in responses.&lt;/p&gt;
&lt;p&gt;For example, a lead creation schema could look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from pydantic import BaseModel, EmailStr
from typing import Optional

class LeadCreate(BaseModel):
    first_name: str
    last_name: str
    email: EmailStr
    phone: Optional[str] = None
    company: Optional[str] = None
    source: str = &quot;Website&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Additional validation should check allowed source values, required fields, field lengths, and business-specific rules.&lt;/p&gt;
&lt;p&gt;Do not allow a client to choose arbitrary ownership or organization identifiers without verifying authorization on the server.&lt;/p&gt;
&lt;h4 id=&quot;implement-lead-creation&quot;&gt;Implement lead creation&lt;/h4&gt;
&lt;p&gt;The create endpoint should:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Authenticate the user.&lt;/li&gt;
&lt;li&gt;Validate the submitted fields.&lt;/li&gt;
&lt;li&gt;Determine the organization from the authenticated user's context.&lt;/li&gt;
&lt;li&gt;Apply the correct assignment rules.&lt;/li&gt;
&lt;li&gt;Save the record within a database transaction.&lt;/li&gt;
&lt;li&gt;Return the created lead using a response schema.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Return appropriate HTTP status codes. For example, a successful creation typically returns &lt;code&gt;201 Created&lt;/code&gt;, invalid input returns &lt;code&gt;422 Unprocessable Entity&lt;/code&gt;, and an unauthorized request may return &lt;code&gt;401 Unauthorized&lt;/code&gt; or &lt;code&gt;403 Forbidden&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;implement-filtering-and-pagination&quot;&gt;Implement filtering and pagination&lt;/h4&gt;
&lt;p&gt;As the database grows, returning every lead in a single request becomes inefficient.&lt;/p&gt;
&lt;p&gt;Support query parameters such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;GET /api/leads?status=Qualified&amp;amp;page=1&amp;amp;page_size=20
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The backend should apply filters before pagination, enforce a reasonable maximum page size, and use deterministic sorting.&lt;/p&gt;
&lt;p&gt;For larger datasets, consider cursor-based pagination.&lt;/p&gt;
&lt;h3 id=&quot;9-implement-authentication-and-role-based-access&quot;&gt;9. Implement Authentication and Role-Based Access&lt;/h3&gt;
&lt;p&gt;A lead management dashboard often contains personal information, customer communications, and commercially sensitive data.&lt;/p&gt;
&lt;p&gt;Authentication determines who the user is. Authorization determines what that user can do.&lt;/p&gt;
&lt;p&gt;Common roles include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Administrator:&lt;/strong&gt; Manages organization settings and users.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sales manager:&lt;/strong&gt; Views team performance and assigns leads.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sales representative:&lt;/strong&gt; Manages assigned prospects and follow-ups.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Marketing analyst:&lt;/strong&gt; Reviews campaign sources and permitted analytics.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These roles are examples; actual permissions should reflect the business requirements.&lt;/p&gt;
&lt;p&gt;Use a well-established authentication approach, such as secure server-managed sessions or carefully implemented OAuth2-based authentication.&lt;/p&gt;
&lt;p&gt;If using JWTs, validate token signatures, expiration, issuer, and audience where applicable. Use secure password hashing such as Argon2id or an appropriately configured bcrypt implementation.&lt;/p&gt;
&lt;p&gt;For browser applications, secure, HTTP-only cookies can reduce exposure to token theft through JavaScript, but cookie-based authentication also requires appropriate CSRF protections.&lt;/p&gt;
&lt;p&gt;Most importantly, authorization checks must happen on the backend. Hiding a button in React does not prevent a user from directly calling the API.&lt;/p&gt;
&lt;p&gt;Every query involving a lead should enforce organization and user access restrictions.&lt;/p&gt;
&lt;h3 id=&quot;10-build-the-react-dashboard-interface&quot;&gt;10. Build the React Dashboard Interface&lt;/h3&gt;
&lt;p&gt;Once the API is available, build a responsive frontend that makes sales information easy to understand.&lt;/p&gt;
&lt;p&gt;A useful dashboard layout includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A sidebar for navigation.&lt;/li&gt;
&lt;li&gt;A top navigation bar with search and account controls.&lt;/li&gt;
&lt;li&gt;Summary cards for important metrics.&lt;/li&gt;
&lt;li&gt;A sales pipeline overview.&lt;/li&gt;
&lt;li&gt;A recent leads table.&lt;/li&gt;
&lt;li&gt;A follow-up activity panel.&lt;/li&gt;
&lt;li&gt;Charts for sources and conversion performance.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Suggested pages include Dashboard, Leads, Pipeline, Activities, Reports, Team, and Settings.&lt;/p&gt;
&lt;p&gt;Use reusable components instead of building every page independently.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;src/
├── components/
│   ├── Sidebar.jsx
│   ├── Header.jsx
│   ├── MetricCard.jsx
│   ├── LeadTable.jsx
│   ├── LeadForm.jsx
│   └── StatusBadge.jsx
├── pages/
│   ├── Dashboard.jsx
│   ├── Leads.jsx
│   ├── Pipeline.jsx
│   └── Reports.jsx
├── services/
│   └── api.js
├── hooks/
├── App.jsx
└── main.jsx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Keep API communication in a dedicated service layer. This prevents request logic from becoming scattered throughout the user interface.&lt;/p&gt;
&lt;p&gt;The dashboard should provide clear loading indicators, helpful error messages, empty states, and confirmation messages after successful actions.&lt;/p&gt;
&lt;h3 id=&quot;11-create-dashboard-metric-cards&quot;&gt;11. Create Dashboard Metric Cards&lt;/h3&gt;
&lt;p&gt;Metric cards provide an immediate overview of sales activity.&lt;/p&gt;
&lt;p&gt;Useful metrics include:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Total leads:&lt;/strong&gt; The number of accessible lead records.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;New leads:&lt;/strong&gt; Prospects created during a selected reporting period.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Qualified leads:&lt;/strong&gt; Prospects that meet the organization's qualification criteria.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Converted leads:&lt;/strong&gt; Prospects that have become customers or reached the defined Won stage.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pending follow-ups:&lt;/strong&gt; Incomplete follow-up activities that are due or overdue.&lt;/p&gt;
&lt;p&gt;Each metric should have a clearly defined calculation.&lt;/p&gt;
&lt;p&gt;For example, if a business has 500 leads and 50 have converted, its simple lead conversion rate is:&lt;/p&gt;
&lt;p&gt;Conversion rate = (Converted leads ÷ Total leads) × 100&lt;/p&gt;
&lt;p&gt;In this example, the rate is 10%.&lt;/p&gt;
&lt;p&gt;However, the denominator matters. A conversion rate calculated from all historical leads differs from a rate calculated only from leads created during a specific month.&lt;/p&gt;
&lt;p&gt;For accurate analysis, define the reporting period, time zone, lead population, and conversion event.&lt;/p&gt;
&lt;p&gt;Avoid presenting numbers without explaining their meaning.&lt;/p&gt;
&lt;h3 id=&quot;12-build-data-visualization-and-analytics&quot;&gt;12. Build Data Visualization and Analytics&lt;/h3&gt;
&lt;p&gt;Charts help users identify patterns that may not be obvious in tables.&lt;/p&gt;
&lt;p&gt;Consider adding the following visualizations.&lt;/p&gt;
&lt;h4 id=&quot;leads-by-source&quot;&gt;Leads by source&lt;/h4&gt;
&lt;p&gt;A bar chart can compare leads generated by website forms, Google Ads, Facebook Ads, referrals, and other channels.&lt;/p&gt;
&lt;p&gt;This allows marketers to understand which sources contribute the most prospects.&lt;/p&gt;
&lt;h4 id=&quot;lead-pipeline&quot;&gt;Lead pipeline&lt;/h4&gt;
&lt;p&gt;A funnel or horizontal bar chart can show how many leads are in each stage.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;New: 120&lt;/li&gt;
&lt;li&gt;Contacted: 85&lt;/li&gt;
&lt;li&gt;Qualified: 50&lt;/li&gt;
&lt;li&gt;Proposal: 25&lt;/li&gt;
&lt;li&gt;Won: 12&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These numbers are illustrative. Real dashboards should calculate values directly from the database.&lt;/p&gt;
&lt;h4 id=&quot;conversion-trends&quot;&gt;Conversion trends&lt;/h4&gt;
&lt;p&gt;A line chart can show how conversion counts or rates change over time. This helps managers compare reporting periods and evaluate the effects of marketing campaigns.&lt;/p&gt;
&lt;h4 id=&quot;sales-representative-performance&quot;&gt;Sales representative performance&lt;/h4&gt;
&lt;p&gt;A report can compare assigned leads, completed follow-ups, qualified prospects, and conversions across team members.&lt;/p&gt;
&lt;p&gt;Use consistent metric definitions and ensure that users can see only the performance data they are authorized to access.&lt;/p&gt;
&lt;p&gt;Charts should include readable labels, useful tooltips, accessible colors, and alternative text or tabular data where appropriate.&lt;/p&gt;
&lt;h3 id=&quot;13-connect-react-to-the-backend-api&quot;&gt;13. Connect React to the Backend API&lt;/h3&gt;
&lt;p&gt;React needs a reliable way to communicate with FastAPI.&lt;/p&gt;
&lt;p&gt;Create a shared API client that defines the backend base URL, request defaults, and common error handling.&lt;/p&gt;
&lt;p&gt;A basic Fetch API request might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const response = await fetch(
  `${API_URL}/api/leads?page=1&amp;amp;page_size=20`,
  {
    credentials: &quot;include&quot;,
  }
);

if (!response.ok) {
  throw new Error(&quot;Unable to load leads&quot;);
}

const leads = await response.json();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This example assumes cookie-based authentication and an appropriately configured backend. If the application uses bearer tokens, include the authorization header using the chosen secure token-management strategy.&lt;/p&gt;
&lt;p&gt;When developing locally, configure CORS to allow only the frontend origins that need access. CORS is not an authentication mechanism and does not replace server-side permission checks.&lt;/p&gt;
&lt;p&gt;For larger applications, consider a data-fetching library that supports caching, retries, request deduplication, and cache invalidation.&lt;/p&gt;
&lt;p&gt;After a lead is created or updated, refresh the affected list and statistics so the dashboard stays consistent.&lt;/p&gt;
&lt;h3 id=&quot;14-add-lead-search-and-advanced-filtering&quot;&gt;14. Add Lead Search and Advanced Filtering&lt;/h3&gt;
&lt;p&gt;A dashboard becomes significantly more useful when users can locate the right prospect quickly.&lt;/p&gt;
&lt;p&gt;Implement filters for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lead status.&lt;/li&gt;
&lt;li&gt;Lead source.&lt;/li&gt;
&lt;li&gt;Assigned sales representative.&lt;/li&gt;
&lt;li&gt;Creation date.&lt;/li&gt;
&lt;li&gt;Company.&lt;/li&gt;
&lt;li&gt;Follow-up date.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Combine filters with keyword search.&lt;/p&gt;
&lt;p&gt;For example, a sales manager might search for qualified leads from Facebook Ads created during the current month.&lt;/p&gt;
&lt;p&gt;The frontend should send filter parameters to the backend rather than downloading the entire database and filtering everything in the browser.&lt;/p&gt;
&lt;p&gt;Use database indexes on frequently queried columns where measurements justify them. For text search, PostgreSQL offers several options, including case-insensitive matching and full-text search.&lt;/p&gt;
&lt;p&gt;Debounce keyword input to avoid sending a request for every keystroke.&lt;/p&gt;
&lt;p&gt;Also, make filters visible and easy to clear. Users should be able to understand why a particular result appears in the table.&lt;/p&gt;
&lt;h3 id=&quot;15-create-the-sales-pipeline&quot;&gt;15. Create the Sales Pipeline&lt;/h3&gt;
&lt;p&gt;The sales pipeline represents the journey from a new prospect to a completed sale.&lt;/p&gt;
&lt;p&gt;A common workflow includes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;New.&lt;/li&gt;
&lt;li&gt;Contacted.&lt;/li&gt;
&lt;li&gt;Qualified.&lt;/li&gt;
&lt;li&gt;Proposal Sent.&lt;/li&gt;
&lt;li&gt;Won.&lt;/li&gt;
&lt;li&gt;Lost.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Organizations should customize these stages according to their actual sales process.&lt;/p&gt;
&lt;p&gt;When a representative changes a lead's status, the backend should validate whether the transition is allowed and record the change.&lt;/p&gt;
&lt;p&gt;A lead history table can preserve the previous status, new status, user who made the change, and timestamp.&lt;/p&gt;
&lt;p&gt;This history supports reporting and helps managers understand how opportunities move through the pipeline.&lt;/p&gt;
&lt;p&gt;A drag-and-drop interface can improve usability, but it should call the same validated backend endpoint as a normal status-editing form.&lt;/p&gt;
&lt;p&gt;Do not rely on frontend restrictions alone to enforce workflow rules.&lt;/p&gt;
&lt;h3 id=&quot;16-add-follow-up-tasks-and-reminders&quot;&gt;16. Add Follow-Up Tasks and Reminders&lt;/h3&gt;
&lt;p&gt;Many sales opportunities are lost because representatives forget to follow up.&lt;/p&gt;
&lt;p&gt;A follow-up module helps prevent this problem by associating tasks with leads.&lt;/p&gt;
&lt;p&gt;Each task may contain:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lead identifier.&lt;/li&gt;
&lt;li&gt;Activity type.&lt;/li&gt;
&lt;li&gt;Task description.&lt;/li&gt;
&lt;li&gt;Due date and time.&lt;/li&gt;
&lt;li&gt;Assigned user.&lt;/li&gt;
&lt;li&gt;Completion status.&lt;/li&gt;
&lt;li&gt;Creation timestamp.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The dashboard can display upcoming tasks and highlight overdue activities.&lt;/p&gt;
&lt;p&gt;For example, a representative may see a reminder to call a qualified prospect tomorrow morning.&lt;/p&gt;
&lt;p&gt;For automatic reminders, use a background job system or a scheduled worker. Avoid relying on a browser tab remaining open to execute important scheduled tasks.&lt;/p&gt;
&lt;p&gt;Notifications can initially appear inside the dashboard. Later, email or other supported notification channels can be integrated.&lt;/p&gt;
&lt;p&gt;When sending reminders, handle retries carefully to avoid delivering duplicate messages.&lt;/p&gt;
&lt;h3 id=&quot;17-implement-lead-assignment&quot;&gt;17. Implement Lead Assignment&lt;/h3&gt;
&lt;p&gt;Lead assignment distributes incoming prospects among sales representatives.&lt;/p&gt;
&lt;p&gt;The simplest implementation allows managers to assign leads manually.&lt;/p&gt;
&lt;p&gt;More advanced systems can use rules such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Round-robin assignment.&lt;/li&gt;
&lt;li&gt;Assignment based on product category.&lt;/li&gt;
&lt;li&gt;Assignment by region.&lt;/li&gt;
&lt;li&gt;Assignment based on representative workload.&lt;/li&gt;
&lt;li&gt;Assignment according to lead source.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, a round-robin system distributes new leads among eligible representatives in sequence.&lt;/p&gt;
&lt;p&gt;A production implementation must account for simultaneous incoming leads, inactive users, and assignment failures. Database transactions or suitable locking strategies can help prevent multiple requests from incorrectly assigning the same sequence position.&lt;/p&gt;
&lt;p&gt;Keep assignment rules transparent so managers understand why a prospect was assigned to a particular representative.&lt;/p&gt;
&lt;h3 id=&quot;18-build-reporting-and-campaign-attribution&quot;&gt;18. Build Reporting and Campaign Attribution&lt;/h3&gt;
&lt;p&gt;Marketing teams need to understand not only how many leads they generate but also how those leads perform.&lt;/p&gt;
&lt;p&gt;Store source and campaign information when a lead enters the system.&lt;/p&gt;
&lt;p&gt;Where appropriate, capture UTM parameters such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;utm_source&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;utm_medium&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;utm_campaign&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;utm_content&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;utm_term&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Preserve attribution information according to a documented policy. First-touch attribution, last-touch attribution, and multi-touch attribution answer different questions.&lt;/p&gt;
&lt;p&gt;A dashboard can report leads by source, qualified leads by campaign, conversion rates, and cost per lead when reliable cost data is available.&lt;/p&gt;
&lt;p&gt;Cost per lead is calculated as:&lt;/p&gt;
&lt;p&gt;Cost per lead = Marketing spend ÷ Leads generated&lt;/p&gt;
&lt;p&gt;For example, a campaign spending $1,000 and generating 200 leads has a cost per lead of $5.&lt;/p&gt;
&lt;p&gt;However, inexpensive leads are not necessarily valuable leads. Comparing qualified-lead rates and eventual revenue can produce better marketing decisions.&lt;/p&gt;
&lt;p&gt;Avoid attributing conversions to campaigns without a consistent methodology and adequate tracking data.&lt;/p&gt;
&lt;h3 id=&quot;19-improve-performance-as-the-dataset-grows&quot;&gt;19. Improve Performance as the Dataset Grows&lt;/h3&gt;
&lt;p&gt;A dashboard that performs well with 100 records may become slow when it contains hundreds of thousands of leads and activities.&lt;/p&gt;
&lt;p&gt;Performance should be measured before optimization, but several practices provide a strong starting point.&lt;/p&gt;
&lt;h4 id=&quot;database-optimization&quot;&gt;Database optimization&lt;/h4&gt;
&lt;p&gt;Use indexes for common filters and joins. Review slow queries with PostgreSQL's query-planning tools. Avoid fetching columns that the current page does not need.&lt;/p&gt;
&lt;h4 id=&quot;efficient-api-responses&quot;&gt;Efficient API responses&lt;/h4&gt;
&lt;p&gt;Use pagination, limit response sizes, and avoid unnecessary database queries.&lt;/p&gt;
&lt;p&gt;Be careful with ORM relationships that can generate an excessive number of queries. Eager loading or carefully designed aggregate queries may improve performance.&lt;/p&gt;
&lt;h4 id=&quot;frontend-optimization&quot;&gt;Frontend optimization&lt;/h4&gt;
&lt;p&gt;Render only the data needed for the current page. Consider table virtualization for exceptionally large lists.&lt;/p&gt;
&lt;p&gt;Use code splitting when the application contains many pages, and avoid recalculating expensive derived values on every render.&lt;/p&gt;
&lt;h4 id=&quot;caching&quot;&gt;Caching&lt;/h4&gt;
&lt;p&gt;Cache suitable, frequently requested information when doing so provides a measurable benefit.&lt;/p&gt;
&lt;p&gt;Analytics caching must account for updates, permissions, and reporting periods. Never share cached user-specific results across organizations.&lt;/p&gt;
&lt;h3 id=&quot;20-secure-the-application&quot;&gt;20. Secure the Application&lt;/h3&gt;
&lt;p&gt;Security should be part of the initial architecture rather than a final checklist.&lt;/p&gt;
&lt;p&gt;A lead dashboard may store names, email addresses, telephone numbers, company details, and sales notes.&lt;/p&gt;
&lt;p&gt;Important security measures include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Enforce authentication and authorization on every protected API.&lt;/li&gt;
&lt;li&gt;Scope database queries to the correct organization.&lt;/li&gt;
&lt;li&gt;Hash passwords using established algorithms.&lt;/li&gt;
&lt;li&gt;Use HTTPS in production.&lt;/li&gt;
&lt;li&gt;Store secrets in environment variables or a secret manager.&lt;/li&gt;
&lt;li&gt;Validate and normalize input.&lt;/li&gt;
&lt;li&gt;Apply rate limits to sensitive endpoints.&lt;/li&gt;
&lt;li&gt;Protect cookie-based authentication against CSRF.&lt;/li&gt;
&lt;li&gt;Configure appropriate security headers.&lt;/li&gt;
&lt;li&gt;Avoid exposing internal errors and stack traces.&lt;/li&gt;
&lt;li&gt;Restrict access to exports and reports.&lt;/li&gt;
&lt;li&gt;Keep dependencies updated.&lt;/li&gt;
&lt;li&gt;Record important administrative and data-access events when appropriate.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;SQLAlchemy parameterizes normal query operations, but developers should still avoid unsafe raw SQL construction.&lt;/p&gt;
&lt;p&gt;Prevent mass-assignment vulnerabilities by explicitly controlling which fields users can update.&lt;/p&gt;
&lt;p&gt;Establish retention and deletion policies for customer information. Depending on where the business operates and where its customers are located, applicable privacy requirements may include the GDPR or other local data-protection laws.&lt;/p&gt;
&lt;p&gt;Security is an ongoing responsibility that includes code review, testing, monitoring, and incident response.&lt;/p&gt;
&lt;h3 id=&quot;21-make-the-dashboard-responsive-and-accessible&quot;&gt;21. Make the Dashboard Responsive and Accessible&lt;/h3&gt;
&lt;p&gt;Sales teams may use the dashboard on desktops, laptops, tablets, and mobile phones.&lt;/p&gt;
&lt;p&gt;A responsive layout should adapt to different screen widths without making important actions difficult to reach.&lt;/p&gt;
&lt;p&gt;On smaller screens, collapse the sidebar into a navigation menu and allow wide tables to scroll horizontally or switch to a compact card layout.&lt;/p&gt;
&lt;p&gt;Accessibility is equally important.&lt;/p&gt;
&lt;p&gt;Use semantic HTML, properly labeled form fields, keyboard-accessible controls, visible focus indicators, and sufficient color contrast.&lt;/p&gt;
&lt;p&gt;Do not communicate lead status through color alone. Pair status colors with descriptive labels.&lt;/p&gt;
&lt;p&gt;Provide clear validation messages when users submit incomplete or invalid forms.&lt;/p&gt;
&lt;p&gt;Charts should communicate their meaning without requiring users to distinguish subtle colors. Where possible, provide equivalent data tables.&lt;/p&gt;
&lt;p&gt;A dashboard that is accessible and responsive is easier for more people to use effectively.&lt;/p&gt;
&lt;h3 id=&quot;22-test-the-application-thoroughly&quot;&gt;22. Test the Application Thoroughly&lt;/h3&gt;
&lt;p&gt;Testing verifies that the dashboard behaves correctly under normal conditions and unexpected situations.&lt;/p&gt;
&lt;h4 id=&quot;backend-tests-1&quot;&gt;Backend tests&lt;/h4&gt;
&lt;p&gt;Use pytest to test API endpoints, validation rules, permissions, and business logic.&lt;/p&gt;
&lt;p&gt;Examples include verifying that a valid lead can be created, invalid email addresses are rejected, unauthorized users cannot access restricted records, and status changes follow the defined workflow.&lt;/p&gt;
&lt;h4 id=&quot;frontend-tests-1&quot;&gt;Frontend tests&lt;/h4&gt;
&lt;p&gt;Use tools such as Vitest and React Testing Library to test forms, filters, navigation, loading states, and error handling.&lt;/p&gt;
&lt;h4 id=&quot;integration-tests-1&quot;&gt;Integration tests&lt;/h4&gt;
&lt;p&gt;Verify that the frontend, backend, and database work together correctly.&lt;/p&gt;
&lt;p&gt;For example, create a lead through the API and confirm that it appears in the filtered lead list and contributes to the appropriate dashboard metric.&lt;/p&gt;
&lt;h4 id=&quot;end-to-end-tests&quot;&gt;End-to-end tests&lt;/h4&gt;
&lt;p&gt;Playwright can simulate a complete user journey:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Sign in.&lt;/li&gt;
&lt;li&gt;Open the leads page.&lt;/li&gt;
&lt;li&gt;Create a prospect.&lt;/li&gt;
&lt;li&gt;Assign the prospect.&lt;/li&gt;
&lt;li&gt;Schedule a follow-up.&lt;/li&gt;
&lt;li&gt;Update the lead's status.&lt;/li&gt;
&lt;li&gt;Verify the updated dashboard.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Also test empty datasets, duplicate submissions, expired sessions, slow network connections, and concurrent updates.&lt;/p&gt;
&lt;p&gt;Use a separate test database and avoid running destructive tests against production data.&lt;/p&gt;
&lt;h3 id=&quot;23-deploy-the-application&quot;&gt;23. Deploy the Application&lt;/h3&gt;
&lt;p&gt;Once the application has passed its tests, prepare it for deployment.&lt;/p&gt;
&lt;p&gt;A typical production architecture includes a React frontend, a FastAPI application server, a managed PostgreSQL database, and secure HTTPS access.&lt;/p&gt;
&lt;p&gt;Possible deployment options include static frontend hosting, a container platform for FastAPI, and a managed PostgreSQL service.&lt;/p&gt;
&lt;p&gt;Docker can package the backend and its dependencies consistently. A production setup should also define health checks, restart policies, resource limits, and appropriate logging.&lt;/p&gt;
&lt;p&gt;Before deploying:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Configure production environment variables.&lt;/li&gt;
&lt;li&gt;Set up the production database.&lt;/li&gt;
&lt;li&gt;Run database migrations.&lt;/li&gt;
&lt;li&gt;Build and deploy the React frontend.&lt;/li&gt;
&lt;li&gt;Deploy the FastAPI application.&lt;/li&gt;
&lt;li&gt;Configure the correct frontend origins and API URL.&lt;/li&gt;
&lt;li&gt;Enable HTTPS.&lt;/li&gt;
&lt;li&gt;Create a database backup policy.&lt;/li&gt;
&lt;li&gt;Test authentication and organization-level permissions.&lt;/li&gt;
&lt;li&gt;Verify logging, monitoring, and error reporting.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Do not expose PostgreSQL directly to the public internet unless there is a carefully justified and secured requirement.&lt;/p&gt;
&lt;p&gt;Run database migrations as a controlled deployment step. For changes that may affect existing records, use a migration strategy that supports safe rollout and recovery.&lt;/p&gt;
&lt;p&gt;A successful deployment means more than opening the homepage. Users must be able to authenticate, create leads, update records, view analytics, and recover from errors reliably.&lt;/p&gt;
&lt;h3 id=&quot;24-monitor-and-maintain-the-dashboard&quot;&gt;24. Monitor and Maintain the Dashboard&lt;/h3&gt;
&lt;p&gt;After launch, monitor how the application behaves in real usage.&lt;/p&gt;
&lt;p&gt;Track API response times, server errors, database connections, slow queries, background job failures, and frontend exceptions.&lt;/p&gt;
&lt;p&gt;Add structured logs with request identifiers so developers can investigate issues without recording unnecessary personal information.&lt;/p&gt;
&lt;p&gt;Monitor the health of reminder jobs and integrations. A dashboard may appear operational even when its scheduled notifications have silently stopped working.&lt;/p&gt;
&lt;p&gt;Regularly review dependencies, access permissions, backups, and recovery procedures.&lt;/p&gt;
&lt;p&gt;Gather feedback from sales representatives and managers. They may identify practical improvements, such as faster lead entry, clearer status labels, or better overdue-task visibility.&lt;/p&gt;
&lt;p&gt;Use actual user behavior to prioritize improvements instead of adding features simply because they are technically interesting.&lt;/p&gt;
&lt;h3 id=&quot;25-common-mistakes-to-avoid&quot;&gt;25. Common Mistakes to Avoid&lt;/h3&gt;
&lt;p&gt;Developers frequently encounter similar problems when building CRM-style applications.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Building too many features too early:&lt;/strong&gt; Start with the core workflow and expand after the MVP is stable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Using an unclear database structure:&lt;/strong&gt; Define relationships, constraints, and ownership rules before implementing complex features.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Trusting the frontend for security:&lt;/strong&gt; Enforce permissions and validation on the backend.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Loading every record at once:&lt;/strong&gt; Use server-side filtering and pagination.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Calculating metrics inconsistently:&lt;/strong&gt; Document reporting definitions and use consistent date boundaries.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ignoring follow-up workflows:&lt;/strong&gt; Make reminders and activity tracking part of the initial design if sales follow-ups are central to the business.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Skipping automated tests:&lt;/strong&gt; Test critical workflows and access controls before deployment.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ignoring backups and monitoring:&lt;/strong&gt; Prepare for failures rather than assuming the application will always remain available.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Overcomplicating the architecture:&lt;/strong&gt; Start with a well-organized modular application. Introduce microservices only when specific requirements justify their additional operational cost.&lt;/p&gt;
&lt;p&gt;Avoiding these mistakes can save significant development time and reduce the risk of expensive problems after launch.&lt;/p&gt;
&lt;h3 id=&quot;26-suggested-development-roadmap&quot;&gt;26. Suggested Development Roadmap&lt;/h3&gt;
&lt;p&gt;A phased roadmap makes the project easier to manage.&lt;/p&gt;
&lt;h4 id=&quot;phase-1-planning-and-design&quot;&gt;Phase 1: Planning and design&lt;/h4&gt;
&lt;p&gt;Define user roles, lead statuses, database relationships, wireframes, and API contracts.&lt;/p&gt;
&lt;h4 id=&quot;phase-2-backend-development&quot;&gt;Phase 2: Backend development&lt;/h4&gt;
&lt;p&gt;Create the database schema, authentication system, lead CRUD endpoints, filtering, pagination, and validation.&lt;/p&gt;
&lt;h4 id=&quot;phase-3-frontend-development&quot;&gt;Phase 3: Frontend development&lt;/h4&gt;
&lt;p&gt;Build the navigation, dashboard layout, lead forms, data tables, filters, and API integration.&lt;/p&gt;
&lt;h4 id=&quot;phase-4-sales-workflows&quot;&gt;Phase 4: Sales workflows&lt;/h4&gt;
&lt;p&gt;Implement lead assignment, pipeline stages, activity tracking, and follow-up reminders.&lt;/p&gt;
&lt;h4 id=&quot;phase-5-analytics&quot;&gt;Phase 5: Analytics&lt;/h4&gt;
&lt;p&gt;Add metric cards, source reports, conversion charts, and campaign attribution.&lt;/p&gt;
&lt;h4 id=&quot;phase-6-security-and-testing&quot;&gt;Phase 6: Security and testing&lt;/h4&gt;
&lt;p&gt;Test permissions, validate business rules, audit sensitive operations, and run automated tests.&lt;/p&gt;
&lt;h4 id=&quot;phase-7-deployment-and-improvement&quot;&gt;Phase 7: Deployment and improvement&lt;/h4&gt;
&lt;p&gt;Deploy the application, configure monitoring and backups, gather feedback, and prioritize improvements.&lt;/p&gt;
&lt;p&gt;The duration of each phase depends on the developer's experience, team size, integration requirements, and project scope.&lt;/p&gt;
&lt;h3 id=&quot;conclusion-4&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Building a lead management dashboard from scratch is a valuable full-stack development project that combines practical business requirements with modern engineering techniques.&lt;/p&gt;
&lt;p&gt;React provides an interactive frontend, FastAPI handles API requests and business logic, and PostgreSQL stores structured lead and activity data. Together, these technologies support a maintainable foundation for lead tracking, sales pipeline management, team collaboration, and marketing analytics.&lt;/p&gt;
&lt;p&gt;The most important part of the project is not simply creating attractive charts or displaying numbers. It is building a reliable workflow that helps businesses capture prospects, assign responsibility, maintain consistent records, follow up at the right time, and measure results accurately.&lt;/p&gt;
&lt;p&gt;Start with a small, secure, well-tested MVP. Focus on clear database relationships, consistent business rules, meaningful dashboard metrics, and a responsive user experience. Then introduce automation, advanced reporting, and external integrations as the business needs evolve.&lt;/p&gt;
&lt;p&gt;For full-stack developers, this project demonstrates much more than the ability to write React components or create API endpoints. It shows an understanding of system architecture, data modeling, authentication, analytics, testing, deployment, and real-world business processes.&lt;/p&gt;
&lt;p&gt;A well-designed lead management dashboard can become the foundation for a broader CRM platform and a strong portfolio project that demonstrates practical software development skills.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31356/how-to-build-a-lead-management-dashboard-from-scratch</guid>
<pubDate>Fri, 16 Oct 2026 13:24:35 +0000</pubDate>
</item>
<item>
<title>How to Build a Customer Management System with React and FastAPI</title>
<link>https://coderlegion.com/31007/how-to-build-a-customer-management-system-with-react-and-fastapi</link>
<description>&lt;p&gt;Customer relationships are one of the most important parts of running a successful business. Whether a company operates an e-commerce store, a digital marketing agency, a software development firm, or a consulting service, managing customer information efficiently can make a significant difference in productivity and customer satisfaction.&lt;/p&gt;
&lt;p&gt;Many businesses begin by storing customer information in spreadsheets, documents, or disconnected applications. Although these methods may work for a small number of customers, they become increasingly difficult to maintain as a business grows. Duplicate records, missing information, delayed follow-ups, and limited reporting can eventually affect the entire customer management process.&lt;/p&gt;
&lt;p&gt;A Customer Management System (CMS) provides a centralized place to organize customer information, track interactions, update contact details, and manage business relationships. When designed properly, it helps teams access accurate information, collaborate more effectively, and make better decisions.&lt;/p&gt;
&lt;p&gt;In this article, we will explore how to build a customer management system using React for the frontend and FastAPI for the backend. We will discuss the architecture, database design, API development, authentication, CRUD operations, frontend integration, testing, deployment, and security considerations.&lt;/p&gt;
&lt;p&gt;The goal is not simply to create an application that displays customer records. Instead, we will examine how to build a maintainable, secure, and scalable application using modern full-stack development practices.&lt;/p&gt;
&lt;h3 id=&quot;1-understanding-the-customer-management-system&quot;&gt;1. Understanding the Customer Management System&lt;/h3&gt;
&lt;p&gt;Before writing code, we need to understand what our application should accomplish.&lt;/p&gt;
&lt;p&gt;A customer management system stores customer information and provides tools for managing that information throughout the customer lifecycle.&lt;/p&gt;
&lt;p&gt;For our example project, we will build a web application that allows authorized users to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;View a list of customers.&lt;/li&gt;
&lt;li&gt;Add new customer records.&lt;/li&gt;
&lt;li&gt;Update existing customer information.&lt;/li&gt;
&lt;li&gt;Delete customer records when appropriate.&lt;/li&gt;
&lt;li&gt;Search for customers by name or email.&lt;/li&gt;
&lt;li&gt;Filter records by customer status.&lt;/li&gt;
&lt;li&gt;View individual customer details.&lt;/li&gt;
&lt;li&gt;Access the system through an authenticated account.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Additional features, such as activity histories, notes, sales opportunities, and reporting dashboards, can be added as the application evolves.&lt;/p&gt;
&lt;h4 id=&quot;why-choose-react-and-fastapi-&quot;&gt;Why choose React and FastAPI?&lt;/h4&gt;
&lt;p&gt;React and FastAPI complement each other well because they solve different parts of the application.&lt;/p&gt;
&lt;p&gt;React is a JavaScript library for building interactive user interfaces. It allows developers to create reusable components, manage application state, and update the interface when data changes.&lt;/p&gt;
&lt;p&gt;FastAPI is a Python framework for developing APIs. It supports request validation, automatic API documentation, asynchronous programming, and type hints.&lt;/p&gt;
&lt;p&gt;Together, these technologies provide a practical foundation for a modern customer management application.&lt;/p&gt;
&lt;p&gt;React handles presentation and user interactions, while FastAPI handles business logic, validation, authorization, and communication with the database.&lt;/p&gt;
&lt;h3 id=&quot;2-planning-the-application-architecture&quot;&gt;2. Planning the Application Architecture&lt;/h3&gt;
&lt;p&gt;A well-designed application separates responsibilities into distinct layers.&lt;/p&gt;
&lt;p&gt;Our customer management system will use three primary components:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Frontend:&lt;/strong&gt; React provides the user interface, including forms, tables, search fields, and customer detail views.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Backend:&lt;/strong&gt; FastAPI exposes HTTP endpoints that receive requests, validate input, enforce business rules, and return responses.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Database:&lt;/strong&gt; PostgreSQL stores customer records persistently and supports structured queries.&lt;/p&gt;
&lt;p&gt;The application follows a typical client-server architecture.&lt;/p&gt;
&lt;p&gt;When a user opens the customer list, React sends an HTTP request to FastAPI. The backend queries PostgreSQL, retrieves the relevant records, and returns them as JSON. React then renders the returned information in the interface.&lt;/p&gt;
&lt;p&gt;When a user creates a customer, the frontend sends the form data to the backend. FastAPI validates the request, saves the record in the database, and returns the newly created customer.&lt;/p&gt;
&lt;p&gt;This separation makes the application easier to test, maintain, and extend.&lt;/p&gt;
&lt;p&gt;For example, we could later replace the React frontend with a mobile application without rewriting the core customer management logic, provided the API remains compatible.&lt;/p&gt;
&lt;h3 id=&quot;3-setting-up-the-development-environment&quot;&gt;3. Setting Up the Development Environment&lt;/h3&gt;
&lt;p&gt;Before building the application, install the required development tools:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python 3.11 or a compatible modern Python version.&lt;/li&gt;
&lt;li&gt;Node.js and npm.&lt;/li&gt;
&lt;li&gt;PostgreSQL.&lt;/li&gt;
&lt;li&gt;A code editor such as Visual Studio Code.&lt;/li&gt;
&lt;li&gt;Git for version control.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Create a project directory with separate frontend and backend folders.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;customer-management-system/
├── backend/
│   ├── app/
│   │   ├── main.py
│   │   ├── database.py
│   │   ├── models.py
│   │   ├── schemas.py
│   │   └── routers/
│   │       └── customers.py
│   ├── requirements.txt
│   └── .env
│
├── frontend/
│   ├── src/
│   │   ├── components/
│   │   ├── pages/
│   │   ├── services/
│   │   └── App.jsx
│   └── package.json
│
└── README.md
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This structure is intentionally simple. As the application grows, we can introduce dedicated service layers, authentication modules, tests, and database migration files.&lt;/p&gt;
&lt;h4 id=&quot;creating-the-react-application&quot;&gt;Creating the React application&lt;/h4&gt;
&lt;p&gt;Use Vite to initialize the frontend.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm create vite@latest frontend -- --template react
cd frontend
npm install
npm run dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Vite provides a fast development server and a straightforward production build process.&lt;/p&gt;
&lt;h4 id=&quot;creating-the-fastapi-backend&quot;&gt;Creating the FastAPI backend&lt;/h4&gt;
&lt;p&gt;Create a Python virtual environment inside the backend directory.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd backend
python -m venv venv
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Activate the environment on Windows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;venv\Scripts\activate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On Linux or macOS, use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;source venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install the main backend dependencies.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pip install fastapi uvicorn sqlalchemy \
  psycopg[binary] pydantic-settings
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;SQLAlchemy will manage database interactions, while the PostgreSQL driver allows Python to connect to the database.&lt;/p&gt;
&lt;p&gt;Record the installed dependencies in &lt;code&gt;requirements.txt&lt;/code&gt; so other developers can reproduce the environment.&lt;/p&gt;
&lt;h3 id=&quot;4-designing-the-customer-database&quot;&gt;4. Designing the Customer Database&lt;/h3&gt;
&lt;p&gt;Database design is a critical step because customer information must remain consistent as the application grows.&lt;/p&gt;
&lt;p&gt;For our initial version, a customer record will include:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Field &lt;/th&gt;&lt;th&gt;Purpose&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;id &lt;/td&gt;&lt;td&gt;Unique customer identifier&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;full_name &lt;/td&gt;&lt;td&gt;Customer's full name&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;email &lt;/td&gt;&lt;td&gt;Contact email address&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;phone &lt;/td&gt;&lt;td&gt;Optional phone number&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;company &lt;/td&gt;&lt;td&gt;Optional company name&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;status &lt;/td&gt;&lt;td&gt;Current customer status&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;created_at &lt;/td&gt;&lt;td&gt;Record creation timestamp&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The &lt;code&gt;id&lt;/code&gt; field serves as the primary key. Email addresses should generally be unique if the business considers one email address to represent one customer account.&lt;/p&gt;
&lt;p&gt;The status field might contain values such as &lt;code&gt;lead&lt;/code&gt;, &lt;code&gt;active&lt;/code&gt;, or &lt;code&gt;inactive&lt;/code&gt;. The exact categories should reflect the business's actual workflow.&lt;br&gt;
In PostgreSQL, the conceptual table could look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE customers (
    id SERIAL PRIMARY KEY,
    full_name VARCHAR(150) NOT NULL,
    email VARCHAR(255) NOT NULL UNIQUE,
    phone VARCHAR(30),
    company VARCHAR(150),
    status VARCHAR(20) NOT NULL DEFAULT 'lead',
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This SQL example illustrates the table structure. In the application, we should use a database migration system, such as Alembic, to manage schema changes safely.&lt;/p&gt;
&lt;p&gt;We should also consider indexes for frequently searched fields. A unique constraint on email already creates a suitable database index in PostgreSQL. Additional indexes should be introduced when query patterns and performance measurements justify them.&lt;/p&gt;
&lt;p&gt;For a production application, consider storing customer interactions in separate tables rather than placing every activity into the customer record. This allows one customer to have many notes, calls, emails, or sales opportunities without creating an unnecessarily large table.&lt;/p&gt;
&lt;h3 id=&quot;5-connecting-fastapi-to-postgresql&quot;&gt;5. Connecting FastAPI to PostgreSQL&lt;/h3&gt;
&lt;p&gt;The backend needs a reliable database connection before it can retrieve or modify customer records.&lt;/p&gt;
&lt;p&gt;Create a &lt;code&gt;.env&lt;/code&gt; file containing the database connection string.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-env&quot;&gt;DATABASE_URL=postgresql+psycopg://user:password@localhost/customer_db
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replace the example credentials with your own development database details. Never commit real production credentials to Git.&lt;br&gt;
Next, create &lt;code&gt;database.py&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import os

from sqlalchemy import create_engine
from sqlalchemy.orm import DeclarativeBase, sessionmaker

DATABASE_URL = os.environ[&quot;DATABASE_URL&quot;]

engine = create_engine(
    DATABASE_URL,
    pool_pre_ping=True,
)

SessionLocal = sessionmaker(
    bind=engine,
    autoflush=False,
    expire_on_commit=False,
)

class Base(DeclarativeBase):
    pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For local development, you can load environment variables using a configuration library or a development environment loader. In production, prefer your hosting platform's secret management system.&lt;/p&gt;
&lt;p&gt;We can provide a database session to FastAPI endpoints through a dependency.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from collections.abc import Generator

from sqlalchemy.orm import Session

from app.database import SessionLocal

def get_db() -&amp;gt; Generator[Session, None, None]:
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This pattern ensures that each request receives a database session and that the session is closed afterward.&lt;/p&gt;
&lt;p&gt;For synchronous SQLAlchemy sessions, use regular &lt;code&gt;def&lt;/code&gt; endpoints rather than performing blocking database operations inside an &lt;code&gt;async def&lt;/code&gt; endpoint. Alternatively, use SQLAlchemy's asynchronous engine and session APIs when an asynchronous database architecture is appropriate.&lt;/p&gt;
&lt;h3 id=&quot;6-creating-the-customer-model&quot;&gt;6. Creating the Customer Model&lt;/h3&gt;
&lt;p&gt;SQLAlchemy models describe how application objects map to database tables.&lt;/p&gt;
&lt;p&gt;Create &lt;code&gt;models.py&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from datetime import datetime

from sqlalchemy import (
    DateTime,
    String,
    func,
)
from sqlalchemy.orm import Mapped, mapped_column

from app.database import Base

class Customer(Base):
    __tablename__ = &quot;customers&quot;

    id: Mapped[int] = mapped_column(
        primary_key=True
    )

    full_name: Mapped[str] = mapped_column(
        String(150),
        nullable=False,
    )
    email: Mapped[str] = mapped_column(
        String(255),
        unique=True,
        index=True,
        nullable=False,
    )
    phone: Mapped[str | None] = mapped_column(
        String(30),
        nullable=True,
    )
    company: Mapped[str | None] = mapped_column(
        String(150),
        nullable=True,
    )
    status: Mapped[str] = mapped_column(
        String(20),
        default=&quot;lead&quot;,
        nullable=False,
    )
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True),
        server_default=func.now(),
        nullable=False,
    )
The model establishes the relationship between Python objects and the customer table.

Notice that optional values use nullable fields, while required values are explicitly marked as non-nullable.

The database should enforce critical constraints independently of frontend validation. A malicious or incorrectly configured client must not be able to bypass important data integrity rules simply by sending a direct HTTP request.

In this example, `status` is a string. A production implementation should validate it against an explicit set of allowed values and may use a database enum or a check constraint for stronger consistency.

## 7. Defining Request and Response Schemas

Database models and API schemas serve different purposes.

A database model describes how data is stored. A Pydantic schema describes what the API accepts and returns.

Separating these responsibilities prevents clients from controlling internal fields such as database identifiers, timestamps, or administrative flags.

Create `schemas.py`.

```python
from datetime import datetime
from typing import Literal

from pydantic import BaseModel, ConfigDict, EmailStr, Field

CustomerStatus = Literal[&quot;lead&quot;, &quot;active&quot;, &quot;inactive&quot;]

class CustomerCreate(BaseModel):
    full_name: str = Field(
        min_length=2,
        max_length=150,
    )
    email: EmailStr
    phone: str | None = Field(
        default=None,
        max_length=30,
    )
    company: str | None = Field(
        default=None,
        max_length=150,
    )
    status: CustomerStatus = &quot;lead&quot;

class CustomerUpdate(BaseModel):
    full_name: str | None = Field(
        default=None,
        min_length=2,
        max_length=150,
    )
    email: EmailStr | None = None
    phone: str | None = Field(
        default=None,
        max_length=30,
    )
    company: str | None = Field(
        default=None,
        max_length=150,
    )
    status: CustomerStatus | None = None

class CustomerResponse(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    id: int
    full_name: str
    email: str
    phone: str | None
    company: str | None
    status: CustomerStatus
    created_at: datetime
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install the email validation dependency if needed:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pip install email-validator
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pydantic validates incoming values and helps FastAPI produce consistent API responses.&lt;/p&gt;
&lt;p&gt;One important detail concerns partial updates. We will use &lt;code&gt;model_dump(exclude_unset=True)&lt;/code&gt; so that fields omitted from a PATCH request remain unchanged. If the client explicitly sends &lt;code&gt;null&lt;/code&gt; for an optional field, the backend can distinguish that from an omitted field.&lt;/p&gt;
&lt;p&gt;We should also ensure that a missing or null &lt;code&gt;full_name&lt;/code&gt; is rejected during a partial update rather than being written into a required database column.&lt;/p&gt;
&lt;h3 id=&quot;8-building-the-customer-api&quot;&gt;8. Building the Customer API&lt;/h3&gt;
&lt;p&gt;Now we can create the endpoints that power the application.&lt;/p&gt;
&lt;p&gt;A practical API design might include:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Method &lt;/th&gt;&lt;th&gt;Endpoint &lt;/th&gt;&lt;th&gt;Purpose&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;GET &lt;/td&gt;&lt;td&gt;&lt;code&gt;/api/customers&lt;/code&gt; &lt;/td&gt;&lt;td&gt;List customers&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;GET &lt;/td&gt;&lt;td&gt;&lt;code&gt;/api/customers/{id}&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Retrieve one customer&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;POST &lt;/td&gt;&lt;td&gt;&lt;code&gt;/api/customers&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Create a customer&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;PATCH &lt;/td&gt;&lt;td&gt;&lt;code&gt;/api/customers/{id}&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Update selected fields&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;DELETE &lt;/td&gt;&lt;td&gt;&lt;code&gt;/api/customers/{id}&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Delete a customer&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;These endpoints follow common REST conventions.&lt;/p&gt;
&lt;h4 id=&quot;listing-customers&quot;&gt;Listing customers&lt;/h4&gt;
&lt;p&gt;Create &lt;code&gt;routers/customers.py&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy import select
from sqlalchemy.orm import Session

from app.database import get_db
from app.models import Customer
from app.schemas import (
    CustomerCreate,
    CustomerResponse,
    CustomerUpdate,
)
router = APIRouter(
    prefix=&quot;/api/customers&quot;,
    tags=[&quot;Customers&quot;],
)
@router.get(&quot;/&quot;, response_model=list[CustomerResponse])
def list_customers(
    q: str | None = None,
    limit: int = Query(default=20, ge=1, le=100),
    offset: int = Query(default=0, ge=0),
    db: Session = Depends(get_db),
):
    statement = select(Customer)

    if q:
        statement = statement.where(
            Customer.full_name.ilike(f&quot;%{q}%&quot;)
            | Customer.email.ilike(f&quot;%{q}%&quot;)
        )

    statement = (
        statement
        .order_by(Customer.id.desc())
        .limit(limit)
        .offset(offset)
    )

    return db.scalars(statement).all()
This endpoint retrieves customers with pagination and a simple search option.

The `limit` and `offset` parameters prevent the backend from returning an unlimited number of records in a single request.

For larger datasets, consider full-text search or a dedicated search service. Also, escape SQL `LIKE` wildcard characters if the application needs literal substring-search behavior. SQLAlchemy parameterizes values, protecting against ordinary SQL injection, but wildcard characters still have search semantics.

A further improvement would return pagination metadata, including the total record count and the next page token or offset.

### Retrieving a single customer

```python
@router.get(&quot;/{customer_id}&quot;, response_model=CustomerResponse)
def get_customer(
    customer_id: int,
    db: Session = Depends(get_db),
):
    customer = db.get(Customer, customer_id)

    if customer is None:
        raise HTTPException(
            status_code=404,
            detail=&quot;Customer not found&quot;,
        )

    return customer
The endpoint returns a clear `404 Not Found` response when the requested customer does not exist.

### Creating a customer

```python
@router.post(
    &quot;/&quot;,
    response_model=CustomerResponse,
    status_code=201,
)
def create_customer(
    payload: CustomerCreate,
    db: Session = Depends(get_db),
):
    existing = db.scalar(
        select(Customer).where(
            Customer.email == str(payload.email)
        )
    )

    if existing:
        raise HTTPException(
            status_code=409,
            detail=&quot;A customer with this email already exists&quot;,
        )

    customer = Customer(
        **payload.model_dump()
    )

    db.add(customer)

    try:
        db.commit()
        db.refresh(customer)
    except Exception:
        db.rollback()
        raise

    return customer
The endpoint validates the incoming payload, checks for an existing email, saves the new customer, and returns the stored record.

The database's unique constraint remains essential because two simultaneous requests could pass the initial duplicate check. A robust production implementation should handle the resulting database integrity violation and return a consistent conflict response rather than exposing a raw database error.

Email normalization should also be considered. For example, an application may choose to trim surrounding whitespace and normalize email addresses consistently before storage.

### Updating customer information

```python
@router.patch(
    &quot;/{customer_id}&quot;,
    response_model=CustomerResponse,
)
def update_customer(
    customer_id: int,
    payload: CustomerUpdate,
    db: Session = Depends(get_db),
):
    customer = db.get(Customer, customer_id)

    if customer is None:
        raise HTTPException(
            status_code=404,
            detail=&quot;Customer not found&quot;,
        )

    changes = payload.model_dump(exclude_unset=True)

    if changes.get(&quot;full_name&quot;) is None:
        if &quot;full_name&quot; in changes:
            raise HTTPException(
                status_code=422,
                detail=&quot;Full name cannot be null&quot;,
            )

    if &quot;email&quot; in changes and changes[&quot;email&quot;] is not None:
        duplicate = db.scalar(
            select(Customer).where(
                Customer.email == str(changes[&quot;email&quot;]),
                Customer.id != customer_id,
            )
        )

        if duplicate:
            raise HTTPException(
                status_code=409,
                detail=&quot;Email is already assigned to another customer&quot;,
            )

        changes[&quot;email&quot;] = str(changes[&quot;email&quot;])

    for field, value in changes.items():
        setattr(customer, field, value)

    try:
        db.commit()
        db.refresh(customer)
    except Exception:
        db.rollback()
        raise

    return customer
This endpoint updates only the fields included in the request.

In a production codebase, the database integrity error handling should be more specific, and the backend should reject explicit null values for all required fields. We should also define whether an empty update request is valid.

### Deleting a customer

```python
@router.delete(&quot;/{customer_id}&quot;, status_code=204)
def delete_customer(
    customer_id: int,
    db: Session = Depends(get_db),
):
    customer = db.get(Customer, customer_id)

    if customer is None:
        raise HTTPException(
            status_code=404,
            detail=&quot;Customer not found&quot;,
        )

    db.delete(customer)
    db.commit()
    return None
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For business applications, permanent deletion is not always the best choice. Soft deletion or archival may be preferable when the business needs an audit trail or must preserve historical transactions.&lt;/p&gt;
&lt;p&gt;Deletion rules should also account for related records and applicable privacy requirements.&lt;/p&gt;
&lt;h3 id=&quot;9-registering-the-api-router&quot;&gt;9. Registering the API Router&lt;/h3&gt;
&lt;p&gt;Create &lt;code&gt;main.py&lt;/code&gt; to initialize the FastAPI application.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from app.routers.customers import router as customers_router

app = FastAPI(
    title=&quot;Customer Management API&quot;,
    version=&quot;1.0.0&quot;,
)

app.add_middleware(
    CORSMiddleware,
    allow_origins=[&quot;http://localhost:5173&quot;],
    allow_credentials=False,
    allow_methods=[&quot;GET&quot;, &quot;POST&quot;, &quot;PATCH&quot;, &quot;DELETE&quot;],
    allow_headers=[&quot;Content-Type&quot;, &quot;Authorization&quot;],
)

app.include_router(customers_router)

@app.get(&quot;/health&quot;)
def health_check():
    return {&quot;status&quot;: &quot;ok&quot;}
The CORS configuration allows the local React development server to communicate with the backend.

In production, configure the actual frontend origin rather than allowing every website to make cross-origin requests. CORS is a browser security mechanism, not a substitute for authentication or authorization.

Start the development server:

```bash
uvicorn app.main:app --reload
``
Visit `http://localhost:8000/docs` to explore FastAPI's interactive API documentation.

Before running the application, ensure the database schema has been created through migrations. The model definitions alone do not automatically create the tables in this setup.

## 10. Connecting React to the Backend

The frontend should communicate with FastAPI through a dedicated service module rather than scattering request logic across every component.

Create `src/services/customerService.js`.

```javascript
const API_BASE_URL =
  import.meta.env.VITE_API_BASE_URL ||
  &quot;http://localhost:8000&quot;;

export async function getCustomers(query = &quot;&quot;) {
  const params = new URLSearchParams({
    limit: &quot;20&quot;,
    offset: &quot;0&quot;,
  });

  if (query.trim()) {
    params.set(&quot;q&quot;, query.trim());
  }

  const response = await fetch(
    `${API_BASE_URL}/api/customers/?${params}`
  );

  if (!response.ok) {
    throw new Error(&quot;Failed to load customers&quot;);
  }

  return response.json();
}

export async function createCustomer(customer) {
  const response = await fetch(
    `${API_BASE_URL}/api/customers/`,
    {
      method: &quot;POST&quot;,
      headers: {
        &quot;Content-Type&quot;: &quot;application/json&quot;,
      },
      body: JSON.stringify(customer),
    }
  );

  if (!response.ok) {
    const error = await response.json();
    throw new Error(
      error.detail || &quot;Failed to create customer&quot;
    );
  }

  return response.json();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The service module centralizes API communication and error handling.&lt;/p&gt;
&lt;p&gt;For a larger application, add functions for retrieving a single customer, updating records, deleting customers, and handling authentication.&lt;/p&gt;
&lt;p&gt;The API base URL should be configurable through environment variables. Remember that frontend environment variables are generally included in the built application, so they must never contain secrets.&lt;/p&gt;
&lt;h3 id=&quot;11-building-the-customer-list-component&quot;&gt;11. Building the Customer List Component&lt;/h3&gt;
&lt;p&gt;React components turn API data into an interactive interface.&lt;/p&gt;
&lt;p&gt;Create a basic customer list in &lt;code&gt;src/components/CustomerList.jsx&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import { useEffect, useState } from &quot;react&quot;;
import { getCustomers } from &quot;../services/customerService&quot;;

export default function CustomerList() {
  const [customers, setCustomers] = useState([]);
  const [query, setQuery] = useState(&quot;&quot;);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(&quot;&quot;);

  useEffect(() =&amp;gt; {
    let cancelled = false;

    async function loadCustomers() {
      setLoading(true);
      setError(&quot;&quot;);

      try {
        const data = await getCustomers(query);

        if (!cancelled) {
          setCustomers(data);
        }
      } catch {
        if (!cancelled) {
          setError(&quot;Unable to load customer records.&quot;);
        }
      } finally {
        if (!cancelled) {
          setLoading(false);
        }
      }
    }

    loadCustomers();

    return () =&amp;gt; {
      cancelled = true;
    };
  }, [query]);

  if (loading) {
    return &amp;lt;p&amp;gt;Loading customers...&amp;lt;/p&amp;gt;;
  }

  return (
    &amp;lt;section&amp;gt;
      &amp;lt;h1&amp;gt;Customers&amp;lt;/h1&amp;gt;

      &amp;lt;input
        value={query}
        onChange={(event) =&amp;gt; setQuery(event.target.value)}
        placeholder=&quot;Search customers&quot;
        aria-label=&quot;Search customers&quot;
      /&amp;gt;

      {error &amp;amp;&amp;amp; &amp;lt;p role=&quot;alert&quot;&amp;gt;{error}&amp;lt;/p&amp;gt;}

      {!error &amp;amp;&amp;amp; customers.length === 0 &amp;amp;&amp;amp; (
        &amp;lt;p&amp;gt;No customers found.&amp;lt;/p&amp;gt;
      )}

      &amp;lt;ul&amp;gt;
        {customers.map((customer) =&amp;gt; (
          &amp;lt;li key={customer.id}&amp;gt;
            &amp;lt;strong&amp;gt;{customer.full_name}&amp;lt;/strong&amp;gt;
            &amp;lt;span&amp;gt; — {customer.email}&amp;lt;/span&amp;gt;
            &amp;lt;span&amp;gt; ({customer.status})&amp;lt;/span&amp;gt;
          &amp;lt;/li&amp;gt;
        ))}
      &amp;lt;/ul&amp;gt;
    &amp;lt;/section&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This component demonstrates several essential React concepts.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;useState&lt;/code&gt; hook stores the customer list, search query, loading status, and error message. The &lt;code&gt;useEffect&lt;/code&gt; hook fetches data when the query changes. The &lt;code&gt;map&lt;/code&gt; method renders each customer using a stable key.&lt;/p&gt;
&lt;p&gt;The cancellation flag prevents a completed request from updating state after the effect has been cleaned up. In a larger application, &lt;code&gt;AbortController&lt;/code&gt; can cancel obsolete requests, especially when users type quickly into the search field.&lt;/p&gt;
&lt;p&gt;For production search, add a debounce delay to avoid sending a new request for every keystroke. You should also handle HTTP errors, network failures, and authentication expiration consistently.&lt;/p&gt;
&lt;h3 id=&quot;12-creating-a-customer-form&quot;&gt;12. Creating a Customer Form&lt;/h3&gt;
&lt;p&gt;A customer management application needs a form for creating and updating records.&lt;/p&gt;
&lt;p&gt;A simple form can use controlled React inputs, meaning React state holds the current field values.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import { useState } from &quot;react&quot;;
import { createCustomer } from &quot;../services/customerService&quot;;

const initialForm = {
  full_name: &quot;&quot;,
  email: &quot;&quot;,
  phone: &quot;&quot;,
  company: &quot;&quot;,
  status: &quot;lead&quot;,
};

export default function CustomerForm({ onCreated }) {
  const [form, setForm] = useState(initialForm);
  const [error, setError] = useState(&quot;&quot;);
  const [saving, setSaving] = useState(false);

  function updateField(event) {
    const { name, value } = event.target;

    setForm((current) =&amp;gt; ({
      ...current,
      [name]: value,
    }));
  }

  async function handleSubmit(event) {
    event.preventDefault();
    setError(&quot;&quot;);
    setSaving(true);

    try {
      const customer = await createCustomer({
        ...form,
        phone: form.phone || null,
        company: form.company || null,
      });

      setForm(initialForm);
      onCreated?.(customer);
    } catch (err) {
      setError(err.message || &quot;Unable to save customer.&quot;);
    } finally {
      setSaving(false);
    }
  }

  return (
    &amp;lt;form onSubmit={handleSubmit}&amp;gt;
      &amp;lt;h2&amp;gt;Add Customer&amp;lt;/h2&amp;gt;

      &amp;lt;label&amp;gt;
        Full name
        &amp;lt;input
          name=&quot;full_name&quot;
          value={form.full_name}
          onChange={updateField}
          required
          minLength={2}
          maxLength={150}
        /&amp;gt;
      &amp;lt;/label&amp;gt;

      &amp;lt;label&amp;gt;
        Email
        &amp;lt;input
          type=&quot;email&quot;
          name=&quot;email&quot;
          value={form.email}
          onChange={updateField}
          required
        /&amp;gt;
      &amp;lt;/label&amp;gt;

      &amp;lt;label&amp;gt;
        Phone
        &amp;lt;input
          name=&quot;phone&quot;
          value={form.phone}
          onChange={updateField}
        /&amp;gt;
      &amp;lt;/label&amp;gt;

      &amp;lt;label&amp;gt;
        Company
        &amp;lt;input
          name=&quot;company&quot;
          value={form.company}
          onChange={updateField}
        /&amp;gt;
      &amp;lt;/label&amp;gt;

      &amp;lt;label&amp;gt;
        Status
        &amp;lt;select
          name=&quot;status&quot;
          value={form.status}
          onChange={updateField}
        &amp;gt;
          &amp;lt;option value=&quot;lead&quot;&amp;gt;Lead&amp;lt;/option&amp;gt;
          &amp;lt;option value=&quot;active&quot;&amp;gt;Active&amp;lt;/option&amp;gt;
          &amp;lt;option value=&quot;inactive&quot;&amp;gt;Inactive&amp;lt;/option&amp;gt;
        &amp;lt;/select&amp;gt;
      &amp;lt;/label&amp;gt;

      {error &amp;amp;&amp;amp; &amp;lt;p role=&quot;alert&quot;&amp;gt;{error}&amp;lt;/p&amp;gt;}

      &amp;lt;button type=&quot;submit&quot; disabled={saving}&amp;gt;
        {saving ? &quot;Saving...&quot; : &quot;Save Customer&quot;}
      &amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The form uses native HTML validation for a better user experience, but frontend validation is not sufficient on its own. FastAPI and Pydantic must continue to validate every incoming request.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;onCreated&lt;/code&gt; callback allows a parent component to refresh the customer list after a successful submission.&lt;/p&gt;
&lt;p&gt;For editing, reuse the form with initial values loaded from an existing customer. Make sure the application distinguishes between creating a new record and updating an existing one.&lt;/p&gt;
&lt;h3 id=&quot;13-organizing-the-dashboard&quot;&gt;13. Organizing the Dashboard&lt;/h3&gt;
&lt;p&gt;A useful customer management dashboard should make important information easy to understand.&lt;/p&gt;
&lt;p&gt;Consider organizing the interface into the following sections:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Navigation sidebar:&lt;/strong&gt; Links to Dashboard, Customers, Activities, Reports, and Settings.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Summary cards:&lt;/strong&gt; Display the total number of customers, active customers, new leads, and inactive records.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Customer table:&lt;/strong&gt; Show names, email addresses, companies, statuses, and available actions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Search and filters:&lt;/strong&gt; Help users locate records quickly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Customer detail page:&lt;/strong&gt; Display contact information and a history of interactions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Create and edit dialogs:&lt;/strong&gt; Provide a focused interface for changing customer information.&lt;/p&gt;
&lt;p&gt;React supports this structure through reusable components. For example, a &lt;code&gt;StatCard&lt;/code&gt; component can display different metrics, while a &lt;code&gt;CustomerTable&lt;/code&gt; component handles the presentation of customer records.&lt;/p&gt;
&lt;p&gt;A consistent design system helps avoid duplicated styling and makes future improvements easier.&lt;/p&gt;
&lt;p&gt;Accessibility should be part of the interface from the beginning. Use meaningful labels, visible focus indicators, sufficient color contrast, keyboard-accessible controls, and clear validation messages. Do not rely on color alone to communicate a customer's status.&lt;/p&gt;
&lt;h3 id=&quot;14-implementing-search-filtering-and-pagination&quot;&gt;14. Implementing Search, Filtering, and Pagination&lt;/h3&gt;
&lt;p&gt;Search becomes increasingly important when a business manages hundreds or thousands of customers.&lt;/p&gt;
&lt;p&gt;The backend can accept query parameters such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/api/customers/?q=alex&amp;amp;limit=20&amp;amp;offset=0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The search term can match a customer's name or email address.&lt;/p&gt;
&lt;p&gt;Filtering by status can be added through another parameter:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/api/customers/?status=active&amp;amp;limit=20&amp;amp;offset=0
``
The backend should validate the status value and apply it to the database query.

Pagination allows the application to load records in manageable batches. Without pagination, a large response may increase database load, network transfer, and rendering time.
For a straightforward administrative application, limit-and-offset pagination is often sufficient. For very large or rapidly changing datasets, cursor-based pagination may offer more predictable performance.

Search and filtering should be performed on the backend when the dataset is large. Filtering only the currently loaded React array may incorrectly suggest that no matching records exist elsewhere in the database.

## 15. Adding Authentication and Authorization

A customer management system often contains personal information, so authentication and authorization must be designed carefully.

Authentication determines who the user is. Authorization determines what that user is allowed to do.

A practical production implementation should include:

- User registration or administrator-created accounts.
- Secure login and logout.
- Password hashing with a modern password-hashing algorithm.
- Access control for customer endpoints.
- Role-based permissions where required.
- Session expiration and revocation.
- Audit logging for sensitive changes.

FastAPI supports security dependencies and OAuth2-compatible authentication flows. Depending on the application, you may use short-lived access tokens, refresh-token rotation, or secure server-managed sessions.

If browser cookies are used for authentication, configure `HttpOnly`, `Secure`, and appropriate `SameSite` attributes, and implement appropriate CSRF protection for state-changing requests.

If bearer tokens are used, protect token storage and transmission. Avoid storing long-lived sensitive tokens in browser-accessible storage without carefully considering the risks.

Most importantly, every protected endpoint must enforce authorization on the server. Hiding a Delete button in React does not prevent an unauthorized user from calling the delete endpoint directly.

For a multi-company application, every customer query must also be scoped to the authenticated user's organization. Guessing another customer's numeric ID must never grant access to that record.

## 16. Securing Customer Data

Security should be treated as an ongoing engineering responsibility rather than a final checklist.

First, use HTTPS in production to protect data in transit.

Second, keep database credentials, signing keys, and other secrets outside source control.

Third, validate and normalize incoming data before storing it. Use parameterized database operations and avoid building SQL statements through string concatenation.

Fourth, apply the principle of least privilege. The application's database account should have only the permissions required for normal operations.

Fifth, limit the information returned by API endpoints. Internal notes, private account details, and administrative fields should not be exposed unless the requesting user has permission to access them.

Sixth, implement rate limiting and appropriate request-size limits to reduce abuse.

Seventh, maintain backups and periodically test the recovery process.

Finally, establish data retention and deletion policies that fit the business's legal and operational requirements. Logging should avoid exposing passwords, access tokens, or unnecessary personal information.

A secure application combines technical controls, appropriate operational procedures, and clear rules about who can access customer information.

## 17. Testing the Application

Testing helps ensure that customer records remain reliable as the code changes.

### Backend testing

Use `pytest` and FastAPI's `TestClient` to test API behavior.

Important test cases include:

- Creating a customer with valid data.
- Rejecting an invalid email address.
- Rejecting a duplicate email address.
- Returning a `404` response for a missing customer.
- Updating one field without changing others.
- Rejecting an invalid customer status.
- Preventing unauthorized access.
- Preventing users from accessing another organization's records.

Use a dedicated test database rather than running destructive tests against production data.

### Frontend testing

Use a React testing library to verify user interactions.

For example, test that the customer form displays validation feedback, disables the submit button while saving, and shows the newly created customer after a successful response.

Also test loading states, empty search results, server errors, and network failures.

End-to-end tests can verify the complete workflow, from logging in and creating a customer to editing the record and confirming that the change persists.

Automated tests should be integrated into a continuous integration pipeline so that regressions are detected before deployment.

## 18. Improving Performance and Scalability

An application that works well with twenty customers may struggle with fifty thousand. Performance decisions should be guided by measurements and expected usage.

Start with database indexes for common filters and sorting operations. Use query plans to identify expensive database operations instead of adding indexes without evidence.

Keep API responses focused. Return only the fields the frontend needs, and use pagination to control response sizes.

Avoid repeatedly fetching the same data when a safe caching strategy would work. However, cached customer information must respect authorization boundaries and must be invalidated when necessary.

React performance improvements may include component memoization, virtualization for very large lists, and reducing unnecessary state updates. These techniques should be introduced when profiling shows they are useful.

If the application needs to process large imports, send notifications, or generate complex reports, consider background workers and a task queue rather than making every HTTP request wait for a long-running job.

For multi-tenant systems, tenant isolation should be built into database access patterns and tested thoroughly.

As usage increases, monitor request latency, database connection utilization, error rates, and resource consumption. Good observability makes it easier to identify the actual cause of performance problems.

## 19. Deploying the Application

Deployment turns the development project into a service that real users can access.

A typical production environment includes:

- A hosting platform for the React frontend.
- A hosting platform or container environment for FastAPI.
- A managed PostgreSQL database.
- HTTPS and correctly configured DNS.
- Environment-based configuration.
- Automated backups and monitoring.

Build the React application for production:

```bash
npm run build
``
## 20. Future Features Worth Adding

Once the basic system is reliable, several features can make it more useful for real business operations.

**Customer activity timeline:** Record calls, emails, meetings, and important interactions.

**Notes and reminders:** Help staff remember follow-ups and customer requirements.

**Import and export:** Support controlled CSV imports and exports, including duplicate detection and validation reports.

**Advanced reporting:** Track new leads, customer retention, status changes, and team activity.

**Email integration:** Connect customer records to relevant email conversations, with appropriate permissions.

**Team collaboration:** Assign customers to staff members and track ownership changes.

**Audit trail:** Record who changed important fields and when.

**CRM integration:** Synchronize records with other business systems through documented APIs.

Avoid adding features simply because they sound impressive. Prioritize those that solve genuine problems for the intended users.

## 21. Common Mistakes to Avoid

Several mistakes can make a customer management system difficult to maintain.

**Mixing frontend and backend responsibilities:** React should not connect directly to the production database. Database access belongs in the backend.

**Ignoring validation:** Validate incoming data at the API boundary and enforce important constraints in the database.

**Returning every customer in one response:** Large datasets need pagination and carefully designed queries.

**Skipping authentication:** A public customer endpoint is inappropriate for private business information.

**Ignoring authorization:** A logged-in user should not automatically have permission to access every record.

**Handling errors inconsistently:** Use clear HTTP status codes and structured error responses.

**Hardcoding configuration:** Use environment-specific configuration instead of committing secrets or production URLs into application code.

**Skipping automated tests:** Basic tests can catch duplicate handling, data corruption, and permission regressions early.

**Overcomplicating the architecture:** Start with a clear modular design. Introduce additional services and infrastructure when the project's requirements justify them.

Avoiding these mistakes early will save time and reduce the cost of future maintenance.

For developers building a portfolio project, this application is also a valuable opportunity to demonstrate practical engineering skills. A well-documented repository, automated tests, a working deployment, and a clear explanation of architectural decisions can show potential employers or clients that you understand how to build more than a simple user interface.

With careful planning and iterative development, React and FastAPI can provide a strong foundation for a maintainable customer management application that grows alongside the business.
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31007/how-to-build-a-customer-management-system-with-react-and-fastapi</guid>
<pubDate>Wed, 14 Oct 2026 03:47:00 +0000</pubDate>
</item>
<item>
<title>Building a Smarter useIdle Hook in React: Solving the Short-Timeout Edge Case</title>
<link>https://coderlegion.com/30090/building-a-smarter-useidle-hook-in-react-solving-the-short-timeout-edge-case</link>
<description>&lt;p&gt;Tracking user inactivity in web applications seems straightforward at first. You listen to a few mouse and keyboard events, set a timeout, and toggle state when the timer expires. However, once you start optimizing for performance, things get tricky.&lt;/p&gt;
&lt;p&gt;To prevent your app from grinding to a halt during heavy mouse movement, you must throttle the event handlers. But what happens if a developer configures a very short idle timeout—say, for a rapid UI transition or testing? If your throttle interval is static, it can easily clash with the timeout length, causing missed events or inconsistent state transitions. &lt;/p&gt;
&lt;p&gt;In the latest release of &lt;code&gt;react-hook-lab&lt;/code&gt;, we solved this exact problem by introducing dynamic adaptive throttling to our &lt;code&gt;useIdle&lt;/code&gt; hook.&lt;/p&gt;
&lt;h4 id=&quot;the-problem-with-fixed-throttling&quot;&gt;The Problem with Fixed Throttling&lt;/h4&gt;
&lt;p&gt;Previously, &lt;code&gt;useIdle&lt;/code&gt; utilized a fixed throttle duration. If the user moved their mouse rapidly, events were ignored for a set period to save CPU cycles. However, if a developer set a short timeout duration close to or lower than the throttle limit, the hook could miss the final active event and incorrectly report the user as idle.&lt;/p&gt;
&lt;p&gt;Furthermore, initializing the tracking state at &lt;code&gt;0&lt;/code&gt; instead of backdating it meant the hook could experience a sluggish response immediately after mounting.&lt;/p&gt;
&lt;h4 id=&quot;the-solution-dynamic-adaptation&quot;&gt;The Solution: Dynamic Adaptation&lt;/h4&gt;
&lt;p&gt;To resolve this, we modified &lt;code&gt;useIdle&lt;/code&gt; to dynamically compute an &lt;code&gt;effectiveThrottle&lt;/code&gt;. It now ensures the throttle interval is never larger than half of the requested timeout duration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;const effectiveThrottle = Math.min(THROTTLE_MS, Math.floor(timeout / 2));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Additionally, we corrected the initial state of the active tracking reference. By setting it to &lt;code&gt;-THROTTLE_MS&lt;/code&gt; on mount, the very first user interaction triggers an immediate, unthrottled update.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;practical-code-examples&quot;&gt;Practical Code Examples&lt;/h4&gt;
&lt;p&gt;Here are two ways you can leverage the newly optimized &lt;code&gt;useIdle&lt;/code&gt; hook in your React applications.&lt;/p&gt;
&lt;h5 id=&quot;example-1-protecting-secure-sessions-standard-timeout-&quot;&gt;Example 1: Protecting Secure Sessions (Standard Timeout)&lt;/h5&gt;
&lt;p&gt;For security-sensitive interfaces, you want to warn users before their session expires. With a standard timeout, &lt;code&gt;useIdle&lt;/code&gt; works efficiently in the background without causing unnecessary re-renders during active scrolling or typing.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React from &quot;react&quot;;
import { useIdle } from &quot;react-hook-lab&quot;;

export function SessionTimeoutWarning() {
  // Trigger idle warning after 5 minutes of inactivity
  const isIdle = useIdle(300000);

  return (
    &amp;lt;div className=&quot;session-banner&quot;&amp;gt;
      {isIdle ? (
        &amp;lt;div className=&quot;warning&quot;&amp;gt;
          &amp;lt;p&amp;gt;You have been inactive for 5 minutes. Your session will lock soon.&amp;lt;/p&amp;gt;
          &amp;lt;button onClick={() =&amp;gt; window.location.reload()}&amp;gt;Extend Session&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
      ) : (
        &amp;lt;p className=&quot;status&quot;&amp;gt;Secure connection active.&amp;lt;/p&amp;gt;
      )}
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;example-2-interactive-video-player-hud-short-timeout-&quot;&gt;Example 2: Interactive Video Player HUD (Short Timeout)&lt;/h5&gt;
&lt;p&gt;When building a video streaming client, you want to hide the playback controls (HUD) quickly when the user stops moving their cursor. This requires a very short timeout of 2 seconds. Thanks to our dynamic adaptive throttling, this short window no longer glitches out.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React from &quot;react&quot;;
import { useIdle } from &quot;react-hook-lab&quot;;

export function VideoPlayerHUD() {
  // Hide controls after 2 seconds of inactivity
  const isControlsIdle = useIdle(2000);

  return (
    &amp;lt;div className=&quot;video-container&quot; style={{ position: &quot;relative&quot; }}&amp;gt;
      &amp;lt;video src=&quot;movie.mp4&quot; width=&quot;100%&quot; controls={false} /&amp;gt;
      
      &amp;lt;div 
        className=&quot;controls-overlay&quot;
        style={{
          opacity: isControlsIdle ? 0 : 1,
          transition: &quot;opacity 0.3s ease-in-out&quot;
        }}
      &amp;gt;
        &amp;lt;button&amp;gt;Play&amp;lt;/button&amp;gt;
        &amp;lt;button&amp;gt;Pause&amp;lt;/button&amp;gt;
        &amp;lt;span&amp;gt;01:23 / 05:40&amp;lt;/span&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;resources&quot;&gt;Resources&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Saurav-TB-Pandey/react-hook-lab&quot;&gt;react-hook-lab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NPM Package:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/react-hook-lab&quot;&gt;react-hook-lab on npm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Connect on LinkedIn:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/pandeysaurav/&quot;&gt;Saurav Pandey&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on my blog. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://sauravtbpandey.blogspot.com/2026/10/how-we-optimized-idle-detection-in.html&quot;&gt;read the alternative breakdown here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30090/building-a-smarter-useidle-hook-in-react-solving-the-short-timeout-edge-case</guid>
<pubDate>Wed, 07 Oct 2026 09:35:26 +0000</pubDate>
</item>
<item>
<title>From React to PostgreSQL: Understanding the Full Stack</title>
<link>https://coderlegion.com/30077/from-react-to-postgresql-understanding-the-full-stack</link>
<description>&lt;p&gt;Modern web applications are no longer built with a single technology. A production application usually involves multiple layers working together: a frontend that users interact with, a backend that processes requests, APIs that connect different components, and a database that stores information.&lt;/p&gt;
&lt;p&gt;One of the most popular combinations for building modern full stack applications is &lt;strong&gt;React  Node.js/Express + PostgreSQL&lt;/strong&gt;. React provides an interactive user interface, the backend handles business logic and communication, and PostgreSQL provides reliable and structured data storage.&lt;/p&gt;
&lt;p&gt;Understanding how these technologies communicate is much more valuable than simply learning each technology separately. A developer who understands the entire request lifecycle can build applications that are easier to maintain, debug, secure, and scale.&lt;/p&gt;
&lt;p&gt;This article explores the complete journey from React in the browser to PostgreSQL in the database and explains how all the pieces fit together.&lt;/p&gt;
&lt;h3 id=&quot;1-what-does-full-stack-actually-mean-&quot;&gt;1. What Does &quot;Full Stack&quot; Actually Mean?&lt;/h3&gt;
&lt;p&gt;The term &lt;strong&gt;full stack development&lt;/strong&gt; refers to working across multiple layers of a web application.&lt;/p&gt;
&lt;p&gt;A typical application can be divided into four major areas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;**Frontend What users see and interact with&lt;/li&gt;
&lt;li&gt;**Backend Server-side logic and application processing&lt;/li&gt;
&lt;li&gt;**API  Communication between frontend and backend&lt;/li&gt;
&lt;li&gt;**Database  Persistent storage of application data&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A simplified architecture looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;User
  ↓
React Frontend
  ↓
HTTP Request
  ↓
Backend / API
  ↓
Business Logic
  ↓
PostgreSQL Database
  ↓
Backend Response
  ↓
React UI Update
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For example, imagine a task management application.&lt;/p&gt;
&lt;p&gt;A user opens the application and sees a list of tasks. When they click &lt;strong&gt;&quot;Add Task&quot;&lt;/strong&gt;, React collects the task information and sends it to the backend.&lt;/p&gt;
&lt;p&gt;The backend validates the information, processes the request, and sends an SQL query to PostgreSQL.&lt;/p&gt;
&lt;p&gt;PostgreSQL stores the task and returns the result.&lt;/p&gt;
&lt;p&gt;The backend then sends a response to React, and React updates the interface.&lt;/p&gt;
&lt;p&gt;The entire process may happen in milliseconds.&lt;/p&gt;
&lt;p&gt;Understanding this complete flow is the foundation of full-stack development.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;2-react-the-frontend-layer&quot;&gt;2. React: The Frontend Layer&lt;/h2&gt;
&lt;p&gt;React is a JavaScript library for building user interfaces.&lt;/p&gt;
&lt;p&gt;Instead of creating one large HTML page, React allows developers to divide an application into reusable components.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;function TaskCard({ task }) {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h3&amp;gt;{task.title}&amp;lt;/h3&amp;gt;
      &amp;lt;p&amp;gt;{task.description}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A larger application might contain components such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;App
├── Navbar
├── Sidebar
├── Dashboard
├── TaskList
│   └── TaskCard
└── Footer
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This component-based architecture makes large applications easier to manage.&lt;/p&gt;
&lt;h3 id=&quot;react-state&quot;&gt;React State&lt;/h3&gt;
&lt;p&gt;React applications frequently need to manage changing information.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;const [tasks, setTasks] = useState([]);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;tasks&lt;/code&gt; variable contains the current data, while &lt;code&gt;setTasks()&lt;/code&gt; updates it.&lt;/p&gt;
&lt;p&gt;When the state changes, React re-renders the relevant part of the interface.&lt;/p&gt;
&lt;p&gt;This makes React particularly useful for interactive applications.&lt;/p&gt;
&lt;h3 id=&quot;fetching-backend-data&quot;&gt;Fetching Backend Data&lt;/h3&gt;
&lt;p&gt;React usually communicates with a backend through HTTP requests.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;fetch(&quot;https://example.com/api/tasks&quot;)
  .then(response =&amp;gt; response.json())
  .then(data =&amp;gt; setTasks(data));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Modern applications may also use libraries such as Axios or tools provided by frameworks.&lt;/p&gt;
&lt;p&gt;The important concept is that React itself normally does not communicate directly with PostgreSQL.&lt;/p&gt;
&lt;p&gt;Instead, it communicates with the backend.&lt;/p&gt;
&lt;p&gt;That separation is extremely important.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;3-why-react-should-not-connect-directly-to-postgresql&quot;&gt;3. Why React Should Not Connect Directly to PostgreSQL&lt;/h2&gt;
&lt;p&gt;A beginner might wonder:&lt;/p&gt;
&lt;p&gt;&quot;If React can make network requests, why not connect React directly to PostgreSQL?&quot;&lt;/p&gt;
&lt;p&gt;The answer is security and architecture.&lt;/p&gt;
&lt;p&gt;A browser application runs on the user's device. If database credentials were placed inside React code, anyone could potentially inspect them.&lt;/p&gt;
&lt;p&gt;For example, exposing something like this would be extremely dangerous:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const databasePassword = &quot;my-secret-password&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Frontend code is not a secure location for database credentials.&lt;/p&gt;
&lt;p&gt;Instead, the architecture should look like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;React
   ↓
Backend API
   ↓
PostgreSQL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The backend protects the database credentials and controls what users are allowed to access.&lt;/p&gt;
&lt;p&gt;This also allows the backend to perform validation, authentication, authorization, business logic, and database operations.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;4-the-backend-where-the-application-thinks&quot;&gt;4. The Backend: Where the Application Thinks&lt;/h2&gt;
&lt;p&gt;The backend is responsible for processing requests from the frontend.&lt;/p&gt;
&lt;p&gt;A popular JavaScript backend combination is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Node.js&lt;/li&gt;
&lt;li&gt;Express.js&lt;/li&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Node.js allows JavaScript to run outside the browser.&lt;/p&gt;
&lt;p&gt;Express provides tools for building APIs and handling HTTP requests.&lt;/p&gt;
&lt;p&gt;A simple Express route might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;app.get(&quot;/api/tasks&quot;, async (req, res) =&amp;gt; {
  const result = await db.query(&quot;SELECT * FROM tasks&quot;);

  res.json(result.rows);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When React requests:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;GET /api/tasks
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;the backend receives the request.&lt;/p&gt;
&lt;p&gt;It queries PostgreSQL and returns the database results as JSON.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;5-understanding-apis&quot;&gt;5. Understanding APIs&lt;/h2&gt;
&lt;p&gt;An API acts as a communication layer between different parts of an application.&lt;/p&gt;
&lt;p&gt;For a task application, we might have:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;GET    /api/tasks
GET    /api/tasks/:id
POST   /api/tasks
PUT    /api/tasks/:id
DELETE /api/tasks/:id
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These endpoints represent different operations.&lt;/p&gt;
&lt;h4 id=&quot;get&quot;&gt;GET&lt;/h4&gt;
&lt;p&gt;Used to retrieve information.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;GET /api/tasks
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;post&quot;&gt;POST&lt;/h4&gt;
&lt;p&gt;Used to create new information.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;POST /api/tasks
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;put-or-patch&quot;&gt;PUT or PATCH&lt;/h4&gt;
&lt;p&gt;Used to update information.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;PATCH /api/tasks/25
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;delete&quot;&gt;DELETE&lt;/h4&gt;
&lt;p&gt;Used to remove information.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;DELETE /api/tasks/25
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This pattern is commonly associated with REST APIs.&lt;/p&gt;
&lt;p&gt;A well-designed API gives the frontend a predictable way to communicate with the backend.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;6-postgresql-the-data-layer&quot;&gt;6. PostgreSQL: The Data Layer&lt;/h2&gt;
&lt;p&gt;PostgreSQL is a powerful open-source relational database system.&lt;/p&gt;
&lt;p&gt;It stores information in tables containing rows and columns.&lt;/p&gt;
&lt;p&gt;For example, a &lt;code&gt;tasks&lt;/code&gt; table could look like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;tasks
------------------------------------------------
id | title          | completed | created_at
------------------------------------------------
1  | Learn React    | false     | ...
2  | Build API      | true      | ...
3  | Learn PostgreSQL| false    | ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each row represents a record.&lt;/p&gt;
&lt;p&gt;Each column represents a property of that record.&lt;/p&gt;
&lt;p&gt;A PostgreSQL table might be created using:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE tasks (
    id SERIAL PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    description TEXT,
    completed BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PostgreSQL provides powerful features for data integrity, relationships, transactions, indexing, querying, and more.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;7-sql-how-the-backend-talks-to-postgresql&quot;&gt;7. SQL: How the Backend Talks to PostgreSQL&lt;/h2&gt;
&lt;p&gt;SQL stands for Structured Query Language.&lt;/p&gt;
&lt;p&gt;Backend applications use SQL to interact with relational databases.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT * FROM tasks;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;returns tasks.&lt;/p&gt;
&lt;p&gt;To create a task:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;INSERT INTO tasks (title, description)
VALUES ('Learn Full Stack Development', 'Study React, APIs and PostgreSQL');
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To update a task:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;UPDATE tasks
SET completed = TRUE
WHERE id = 1;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To delete a task:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;DELETE FROM tasks
WHERE id = 1;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These operations are the foundation of database-driven applications.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;8-the-complete-request-lifecycle&quot;&gt;8. The Complete Request Lifecycle&lt;/h2&gt;
&lt;p&gt;Let's follow a real request from React to PostgreSQL.&lt;/p&gt;
&lt;p&gt;Imagine a user submits this form:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Title: Learn PostgreSQL
Description: Understand relational databases
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;step-1-react-collects-the-data&quot;&gt;Step 1: React Collects the Data&lt;/h4&gt;
&lt;p&gt;React stores the form values in state.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const [title, setTitle] = useState(&quot;&quot;);
const [description, setDescription] = useState(&quot;&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the user submits the form, React creates an HTTP request.&lt;/p&gt;
&lt;h4 id=&quot;step-2-react-sends-the-request&quot;&gt;Step 2: React Sends the Request&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;fetch(&quot;/api/tasks&quot;, {
  method: &quot;POST&quot;,
  headers: {
    &quot;Content-Type&quot;: &quot;application/json&quot;
  },
  body: JSON.stringify({
    title,
    description
  })
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The browser sends the request to the backend.&lt;/p&gt;
&lt;h4 id=&quot;step-3-express-receives-it&quot;&gt;Step 3: Express Receives It&lt;/h4&gt;
&lt;p&gt;The backend route receives the request:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;app.post(&quot;/api/tasks&quot;, async (req, res) =&amp;gt; {
  const { title, description } = req.body;

  // validation and database logic
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;step-4-backend-validates-the-data&quot;&gt;Step 4: Backend Validates the Data&lt;/h4&gt;
&lt;p&gt;The backend should never blindly trust frontend input.&lt;/p&gt;
&lt;p&gt;It might check:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;if (!title) {
  return res.status(400).json({
    error: &quot;Title is required&quot;
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;step-5-backend-queries-postgresql&quot;&gt;Step 5: Backend Queries PostgreSQL&lt;/h4&gt;
&lt;p&gt;The server sends an SQL query:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;INSERT INTO tasks (title, description)
VALUES ($1, $2)
RETURNING *;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;$1&lt;/code&gt; and &lt;code&gt;$2&lt;/code&gt; parameters are supplied separately.&lt;/p&gt;
&lt;p&gt;This is important because parameterized queries help protect against SQL injection.&lt;/p&gt;
&lt;h4 id=&quot;step-6-postgresql-processes-the-query&quot;&gt;Step 6: PostgreSQL Processes the Query&lt;/h4&gt;
&lt;p&gt;PostgreSQL validates the query, modifies the appropriate table, and returns the result.&lt;/p&gt;
&lt;h4 id=&quot;step-7-backend-sends-a-response&quot;&gt;Step 7: Backend Sends a Response&lt;/h4&gt;
&lt;p&gt;The backend may return:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;id&quot;: 15,
  &quot;title&quot;: &quot;Learn PostgreSQL&quot;,
  &quot;description&quot;: &quot;Understand relational databases&quot;,
  &quot;completed&quot;: false
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;step-8-react-updates-the-ui&quot;&gt;Step 8: React Updates the UI&lt;/h4&gt;
&lt;p&gt;React receives the response and updates its state.&lt;/p&gt;
&lt;p&gt;The new task then appears on the screen.&lt;/p&gt;
&lt;p&gt;This is the complete full-stack cycle.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;9-authentication-connecting-users-to-data&quot;&gt;9. Authentication: Connecting Users to Data&lt;/h2&gt;
&lt;p&gt;Most real applications need authentication.&lt;/p&gt;
&lt;p&gt;Users might register with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Name
Email
Password
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The backend stores account information in PostgreSQL.&lt;/p&gt;
&lt;p&gt;Passwords should &lt;strong&gt;never&lt;/strong&gt; be stored as plain text.&lt;/p&gt;
&lt;p&gt;Instead, applications typically use secure password hashing algorithms such as bcrypt or Argon2.&lt;/p&gt;
&lt;p&gt;After login, the application can issue an authentication token or session.&lt;/p&gt;
&lt;p&gt;A common architecture looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;User
 ↓
React Login Form
 ↓
POST /api/login
 ↓
Backend
 ↓
Verify Password
 ↓
PostgreSQL
 ↓
Create Session/Token
 ↓
React
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For protected requests, the frontend sends authentication information with the request.&lt;/p&gt;
&lt;p&gt;The backend verifies it before allowing access to protected resources.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;10-database-relationships&quot;&gt;10. Database Relationships&lt;/h2&gt;
&lt;p&gt;Real-world applications rarely have only one table.&lt;/p&gt;
&lt;p&gt;For example, a project management application might contain:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;users
projects
tasks
comments
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A user can have many projects.&lt;/p&gt;
&lt;p&gt;A project can have many tasks.&lt;/p&gt;
&lt;p&gt;A task can have many comments.&lt;/p&gt;
&lt;p&gt;This creates relationships between tables.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    email VARCHAR(255) UNIQUE NOT NULL
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Projects can reference users:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE projects (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    user_id INTEGER REFERENCES users(id)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;user_id&lt;/code&gt; creates a relationship between the project and its owner.&lt;/p&gt;
&lt;p&gt;This is one of the most important concepts when moving from simple applications to production systems.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;11-join-queries&quot;&gt;11. JOIN Queries&lt;/h2&gt;
&lt;p&gt;Relationships become powerful when combined with SQL joins.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT
    projects.name,
    users.name AS owner
FROM projects
JOIN users
ON projects.user_id = users.id;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This can return:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Project             Owner
--------------------------------
Marketing Website   John
Mobile Application  Sarah
CRM Dashboard       David
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of storing everything in one giant table, relational databases allow information to be organized into connected structures.&lt;/p&gt;
&lt;p&gt;This improves data consistency and maintainability.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;12-validation-must-happen-on-the-backend&quot;&gt;12. Validation Must Happen on the Backend&lt;/h2&gt;
&lt;p&gt;Frontend validation is useful for user experience.&lt;/p&gt;
&lt;p&gt;For example, React can tell a user:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Email is required.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But frontend validation alone is not enough.&lt;/p&gt;
&lt;p&gt;A malicious user can bypass the React interface and send requests directly to the API.&lt;/p&gt;
&lt;p&gt;Therefore, the backend must validate requests independently.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;if (!email || !email.includes(&quot;@&quot;)) {
  return res.status(400).json({
    error: &quot;Invalid email&quot;
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Production applications often use validation libraries to create structured schemas.&lt;/p&gt;
&lt;p&gt;The rule is simple:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Never trust client-side input.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;13-error-handling&quot;&gt;13. Error Handling&lt;/h2&gt;
&lt;p&gt;Full-stack applications must also handle errors gracefully.&lt;/p&gt;
&lt;p&gt;A database connection might fail.&lt;/p&gt;
&lt;p&gt;A user might request a resource that doesn't exist.&lt;/p&gt;
&lt;p&gt;A SQL query might fail.&lt;/p&gt;
&lt;p&gt;A server might experience an unexpected error.&lt;/p&gt;
&lt;p&gt;Instead of exposing internal errors to users, the backend should return appropriate HTTP status codes.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;200 OK
201 Created
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
500 Internal Server Error
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;React can then display an appropriate message.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;if (!response.ok) {
  setError(&quot;Unable to load tasks.&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Good error handling improves both user experience and debugging.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;14-environment-variables&quot;&gt;14. Environment Variables&lt;/h2&gt;
&lt;p&gt;Database credentials should not be hard-coded into application source code.&lt;/p&gt;
&lt;p&gt;Instead, applications commonly use environment variables.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;DATABASE_URL=postgresql://user:password@localhost:5432/myapp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The backend reads the value from the environment.&lt;/p&gt;
&lt;p&gt;This allows different configurations for:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Development
Testing
Staging
Production
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It also helps prevent sensitive credentials from being accidentally committed to Git repositories.&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;.env&lt;/code&gt; file should generally be excluded from version control when it contains secrets.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;15-orm-vs-raw-sql&quot;&gt;15. ORM vs Raw SQL&lt;/h2&gt;
&lt;p&gt;Developers can communicate with PostgreSQL using raw SQL or an Object-Relational Mapping tool.&lt;/p&gt;
&lt;p&gt;Raw SQL:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT * FROM users WHERE id = $1;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ORMs provide a higher-level programming interface.&lt;/p&gt;
&lt;p&gt;Popular choices in the JavaScript ecosystem include tools such as Prisma, Drizzle, and Sequelize.&lt;/p&gt;
&lt;p&gt;An ORM can make common database operations easier to organize.&lt;/p&gt;
&lt;p&gt;However, understanding SQL remains important.&lt;/p&gt;
&lt;p&gt;Using an ORM without understanding databases can create problems when queries become complex or performance issues appear.&lt;/p&gt;
&lt;p&gt;A strong full-stack developer should understand both the abstraction and the underlying database.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;16-database-indexing&quot;&gt;16. Database Indexing&lt;/h2&gt;
&lt;p&gt;As an application grows, database performance becomes increasingly important.&lt;/p&gt;
&lt;p&gt;Suppose a users table contains millions of records.&lt;/p&gt;
&lt;p&gt;A query like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT *
FROM users
WHERE email = '*Emails are not allowed*';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;could become expensive without appropriate indexing.&lt;/p&gt;
&lt;p&gt;An index can improve lookup performance:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE INDEX idx_users_email
ON users(email);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Indexes are powerful, but they also have a cost.&lt;/p&gt;
&lt;p&gt;They consume storage and can increase the cost of insert and update operations.&lt;/p&gt;
&lt;p&gt;Therefore, indexes should be designed around actual query patterns.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;17-transactions&quot;&gt;17. Transactions&lt;/h2&gt;
&lt;p&gt;Some operations require multiple database changes to succeed together.&lt;/p&gt;
&lt;p&gt;Imagine transferring money between two accounts.&lt;/p&gt;
&lt;p&gt;You might need to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Subtract money from Account A
Add money to Account B
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the first operation succeeds but the second fails, the database could become inconsistent.&lt;/p&gt;
&lt;p&gt;Transactions solve this problem.&lt;/p&gt;
&lt;p&gt;Conceptually:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;BEGIN;

UPDATE accounts
SET balance = balance - 100
WHERE id = 1;

UPDATE accounts
SET balance = balance + 100
WHERE id = 2;

COMMIT;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If something goes wrong, the transaction can be rolled back.&lt;/p&gt;
&lt;p&gt;Transactions are an important part of reliable backend systems.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;18-performance-across-the-full-stack&quot;&gt;18. Performance Across the Full Stack&lt;/h2&gt;
&lt;p&gt;Full-stack performance isn't just about writing fast React components.&lt;/p&gt;
&lt;p&gt;Every layer can introduce delays.&lt;/p&gt;
&lt;p&gt;Consider:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;React rendering
       ↓
Network request
       ↓
Backend processing
       ↓
Database query
       ↓
Network response
       ↓
React rendering
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the database query takes 2 seconds, optimizing React alone won't solve the problem.&lt;/p&gt;
&lt;p&gt;Similarly, if the API returns a massive JSON response, the frontend may still feel slow.&lt;/p&gt;
&lt;p&gt;Performance should therefore be considered across the entire architecture.&lt;/p&gt;
&lt;p&gt;Possible optimizations include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;React code splitting&lt;/li&gt;
&lt;li&gt;Caching&lt;/li&gt;
&lt;li&gt;API pagination&lt;/li&gt;
&lt;li&gt;Database indexing&lt;/li&gt;
&lt;li&gt;Efficient SQL queries&lt;/li&gt;
&lt;li&gt;Connection pooling&lt;/li&gt;
&lt;li&gt;Compression&lt;/li&gt;
&lt;li&gt;CDN usage&lt;/li&gt;
&lt;li&gt;Lazy loading&lt;/li&gt;
&lt;li&gt;Server-side caching&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;19-pagination&quot;&gt;19. Pagination&lt;/h2&gt;
&lt;p&gt;Imagine an application has 100,000 products.&lt;/p&gt;
&lt;p&gt;Returning every product to React at once would be inefficient.&lt;/p&gt;
&lt;p&gt;Instead, the API can use pagination.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;GET /api/products?page=1&amp;amp;limit=20
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The backend retrieves only the required records.&lt;/p&gt;
&lt;p&gt;SQL might use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT *
FROM products
ORDER BY id
LIMIT 20 OFFSET 0;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pagination reduces network traffic and improves application responsiveness.&lt;/p&gt;
&lt;p&gt;For very large datasets, cursor-based pagination can provide better performance than large offsets.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;20-caching&quot;&gt;20. Caching&lt;/h2&gt;
&lt;p&gt;Not every request needs to hit PostgreSQL.&lt;/p&gt;
&lt;p&gt;Suppose thousands of users request the same popular product.&lt;/p&gt;
&lt;p&gt;Repeatedly querying the database can create unnecessary load.&lt;/p&gt;
&lt;p&gt;Caching can store frequently requested information temporarily.&lt;/p&gt;
&lt;p&gt;The architecture might become:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;React
 ↓
API
 ↓
Cache
 ↓
PostgreSQL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the data exists in the cache, the backend can return it immediately.&lt;/p&gt;
&lt;p&gt;Caching strategies need careful consideration because stale data can create incorrect results.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;21-security-in-a-full-stack-application&quot;&gt;21. Security in a Full-Stack Application&lt;/h2&gt;
&lt;p&gt;Security must exist at every layer.&lt;/p&gt;
&lt;h4 id=&quot;frontend&quot;&gt;Frontend&lt;/h4&gt;
&lt;p&gt;Avoid exposing sensitive information.&lt;/p&gt;
&lt;h4 id=&quot;backend&quot;&gt;Backend&lt;/h4&gt;
&lt;p&gt;Validate input and authenticate users.&lt;/p&gt;
&lt;h4 id=&quot;api&quot;&gt;API&lt;/h4&gt;
&lt;p&gt;Implement authorization and rate limiting where appropriate.&lt;/p&gt;
&lt;h4 id=&quot;database&quot;&gt;Database&lt;/h4&gt;
&lt;p&gt;Use restricted database accounts and secure credentials.&lt;/p&gt;
&lt;h4 id=&quot;sql&quot;&gt;SQL&lt;/h4&gt;
&lt;p&gt;Use parameterized queries.&lt;/p&gt;
&lt;h4 id=&quot;authentication&quot;&gt;Authentication&lt;/h4&gt;
&lt;p&gt;Hash passwords securely and protect sessions or tokens.&lt;/p&gt;
&lt;h4 id=&quot;deployment&quot;&gt;Deployment&lt;/h4&gt;
&lt;p&gt;Use HTTPS and secure environment variables.&lt;/p&gt;
&lt;p&gt;Security isn't one feature that can simply be added at the end.&lt;/p&gt;
&lt;p&gt;It is an architectural responsibility.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;22-development-workflow&quot;&gt;22. Development Workflow&lt;/h2&gt;
&lt;p&gt;A practical development workflow might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. Design database schema
2. Build backend API
3. Test API
4. Build React interface
5. Connect frontend to API
6. Add authentication
7. Add validation
8. Add error handling
9. Optimize database queries
10. Test complete application
11. Deploy
12. Monitor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Developers can use Git to track changes throughout the process.&lt;/p&gt;
&lt;p&gt;A typical repository might look like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;my-fullstack-app/
│
├── frontend/
│   ├── src/
│   ├── components/
│   └── pages/
│
├── backend/
│   ├── routes/
│   ├── controllers/
│   ├── services/
│   └── middleware/
│
├── database/
│   └── migrations/
│
└── README.md
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The exact structure varies between projects, but separating responsibilities helps maintainability.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;23-testing-the-full-stack&quot;&gt;23. Testing the Full Stack&lt;/h2&gt;
&lt;p&gt;Testing should happen at different levels.&lt;/p&gt;
&lt;h4 id=&quot;frontend-testing&quot;&gt;Frontend Testing&lt;/h4&gt;
&lt;p&gt;Test components and user interactions.&lt;/p&gt;
&lt;h4 id=&quot;api-testing&quot;&gt;API Testing&lt;/h4&gt;
&lt;p&gt;Test endpoints independently.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;POST /api/login
GET /api/users
POST /api/tasks
DELETE /api/tasks/:id
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;database-testing&quot;&gt;Database Testing&lt;/h4&gt;
&lt;p&gt;Verify that queries produce the expected results.&lt;/p&gt;
&lt;h4 id=&quot;integration-testing-1&quot;&gt;Integration Testing&lt;/h4&gt;
&lt;p&gt;Test whether multiple components work together.&lt;/p&gt;
&lt;h4 id=&quot;end-to-end-testing&quot;&gt;End-to-End Testing&lt;/h4&gt;
&lt;p&gt;Simulate the experience of a real user.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Open application
↓
Register
↓
Login
↓
Create task
↓
Edit task
↓
Complete task
↓
Logout
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The goal isn't simply to test individual functions.&lt;/p&gt;
&lt;p&gt;It's to verify that the complete system behaves correctly.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;24-deployment&quot;&gt;24. Deployment&lt;/h2&gt;
&lt;p&gt;Once the application works locally, it needs to be deployed.&lt;/p&gt;
&lt;p&gt;A typical production architecture might look like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;User
 ↓
Frontend Hosting / CDN
 ↓
React Application
 ↓
Backend API
 ↓
PostgreSQL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The frontend and backend can be deployed separately or together depending on the architecture.&lt;/p&gt;
&lt;p&gt;The production database should have appropriate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Backups&lt;/li&gt;
&lt;li&gt;Access controls&lt;/li&gt;
&lt;li&gt;Monitoring&lt;/li&gt;
&lt;li&gt;Connection limits&lt;/li&gt;
&lt;li&gt;Environment configuration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Deployment is not the end of development.&lt;/p&gt;
&lt;p&gt;Production systems require monitoring and maintenance.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;25-logging-and-monitoring&quot;&gt;25. Logging and Monitoring&lt;/h2&gt;
&lt;p&gt;When something goes wrong in production, developers need information about what happened.&lt;/p&gt;
&lt;p&gt;Backend logging can record events such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Request received
User authenticated
Database query executed
Response returned
Error occurred
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Monitoring tools can help identify:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Slow API responses&lt;/li&gt;
&lt;li&gt;High database usage&lt;/li&gt;
&lt;li&gt;Server errors&lt;/li&gt;
&lt;li&gt;Failed requests&lt;/li&gt;
&lt;li&gt;Memory problems&lt;/li&gt;
&lt;li&gt;Traffic spikes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A production application without observability can be extremely difficult to maintain.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;26-the-most-important-skill-understanding-the-boundaries&quot;&gt;26. The Most Important Skill: Understanding the Boundaries&lt;/h2&gt;
&lt;p&gt;Learning React, Node.js, Express, and PostgreSQL individually is useful.&lt;/p&gt;
&lt;p&gt;But the most important full-stack skill is understanding &lt;strong&gt;where responsibility belongs&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;React should focus primarily on presentation and user interaction.&lt;/p&gt;
&lt;p&gt;The backend should handle business rules, authentication, authorization, and API behavior.&lt;/p&gt;
&lt;p&gt;PostgreSQL should handle persistent data and relational operations.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;React:
&quot;What did the user click?&quot;

Backend:
&quot;Is this action allowed?&quot;

Database:
&quot;What information should be stored?&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When responsibilities are clearly separated, applications become easier to understand and maintain.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;27-what-happens-when-you-click-a-button-&quot;&gt;27. What Happens When You Click a Button?&lt;/h2&gt;
&lt;p&gt;Let's summarize the entire process using one simple example.&lt;/p&gt;
&lt;p&gt;A user clicks:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&quot;Complete Task&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;React detects the click.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;fetch(&quot;/api/tasks/15&quot;, {
  method: &quot;PATCH&quot;,
  body: JSON.stringify({
    completed: true
  })
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The request travels to the backend.&lt;/p&gt;
&lt;p&gt;Express identifies the route.&lt;/p&gt;
&lt;p&gt;The backend verifies the user's authentication.&lt;/p&gt;
&lt;p&gt;The backend validates the input.&lt;/p&gt;
&lt;p&gt;The server sends a parameterized SQL query:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;UPDATE tasks
SET completed = $1
WHERE id = $2
RETURNING *;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PostgreSQL executes the query.&lt;/p&gt;
&lt;p&gt;The database returns the updated task.&lt;/p&gt;
&lt;p&gt;The backend converts the result into JSON.&lt;/p&gt;
&lt;p&gt;React receives the response.&lt;/p&gt;
&lt;p&gt;React updates its state.&lt;/p&gt;
&lt;p&gt;The interface changes from:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;☐ Learn PostgreSQL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;☑ Learn PostgreSQL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The user sees the result almost instantly.&lt;/p&gt;
&lt;p&gt;That tiny interaction represents the entire full stack architecture working together.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;28-what-should-a-beginner-learn-first-&quot;&gt;28. What Should a Beginner Learn First?&lt;/h2&gt;
&lt;p&gt;If you're starting full-stack development, don't try to master everything simultaneously.&lt;/p&gt;
&lt;p&gt;A practical learning path is:&lt;/p&gt;
&lt;h4 id=&quot;step-1-html-and-css&quot;&gt;Step 1: HTML and CSS&lt;/h4&gt;
&lt;p&gt;Understand the structure and styling of websites.&lt;/p&gt;
&lt;h4 id=&quot;step-2-javascript&quot;&gt;Step 2: JavaScript&lt;/h4&gt;
&lt;p&gt;Learn variables, functions, arrays, objects, asynchronous programming, modules, and modern JavaScript.&lt;/p&gt;
&lt;h4 id=&quot;step-3-react&quot;&gt;Step 3: React&lt;/h4&gt;
&lt;p&gt;Learn:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Components&lt;/li&gt;
&lt;li&gt;Props&lt;/li&gt;
&lt;li&gt;State&lt;/li&gt;
&lt;li&gt;Hooks&lt;/li&gt;
&lt;li&gt;Forms&lt;/li&gt;
&lt;li&gt;Routing&lt;/li&gt;
&lt;li&gt;API requests&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;step-4-node-js-and-express&quot;&gt;Step 4: Node.js and Express&lt;/h4&gt;
&lt;p&gt;Learn:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HTTP&lt;/li&gt;
&lt;li&gt;REST APIs&lt;/li&gt;
&lt;li&gt;Middleware&lt;/li&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;Validation&lt;/li&gt;
&lt;li&gt;Error handling&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;step-5-postgresql&quot;&gt;Step 5: PostgreSQL&lt;/h4&gt;
&lt;p&gt;Learn:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tables&lt;/li&gt;
&lt;li&gt;Primary keys&lt;/li&gt;
&lt;li&gt;Foreign keys&lt;/li&gt;
&lt;li&gt;CRUD&lt;/li&gt;
&lt;li&gt;JOINs&lt;/li&gt;
&lt;li&gt;Indexes&lt;/li&gt;
&lt;li&gt;Transactions&lt;/li&gt;
&lt;li&gt;Database design&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;step-6-full-stack-projects&quot;&gt;Step 6: Full-Stack Projects&lt;/h4&gt;
&lt;p&gt;Build applications that connect everything together.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Task manager&lt;/li&gt;
&lt;li&gt;Blog platform&lt;/li&gt;
&lt;li&gt;Ecommerce application&lt;/li&gt;
&lt;li&gt;CRM&lt;/li&gt;
&lt;li&gt;Job board&lt;/li&gt;
&lt;li&gt;Inventory system&lt;/li&gt;
&lt;li&gt;Project management platform&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Building projects is where the concepts become real skills.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;29-the-future-of-full-stack-development&quot;&gt;29. The Future of Full-Stack Development&lt;/h2&gt;
&lt;p&gt;Modern full-stack development continues to evolve.&lt;/p&gt;
&lt;p&gt;Developers now have access to powerful frameworks, managed databases, cloud platforms, serverless infrastructure, AI coding assistants, and automated deployment systems.&lt;/p&gt;
&lt;p&gt;However, the fundamental architecture remains remarkably consistent:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;User
 ↓
Interface
 ↓
API
 ↓
Business Logic
 ↓
Database
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tools will change.&lt;/p&gt;
&lt;p&gt;Frameworks will change.&lt;/p&gt;
&lt;p&gt;Programming languages will evolve.&lt;/p&gt;
&lt;p&gt;But understanding how data moves through a software system will remain valuable.&lt;/p&gt;
&lt;p&gt;A developer who understands the complete lifecycle of a request can adapt to new technologies much more easily.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;conclusion-5&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Going from React to PostgreSQL is not simply about learning four different technologies.&lt;/p&gt;
&lt;p&gt;It's about understanding how an entire software system works.&lt;/p&gt;
&lt;p&gt;React creates the interface.&lt;/p&gt;
&lt;p&gt;The browser sends requests.&lt;/p&gt;
&lt;p&gt;The backend receives those requests.&lt;/p&gt;
&lt;p&gt;Business logic determines what should happen.&lt;/p&gt;
&lt;p&gt;The API communicates with the database.&lt;/p&gt;
&lt;p&gt;PostgreSQL stores and retrieves information.&lt;/p&gt;
&lt;p&gt;The backend returns a response.&lt;/p&gt;
&lt;p&gt;React updates the interface.&lt;/p&gt;
&lt;p&gt;This cycle happens repeatedly throughout almost every modern data-driven web application.&lt;/p&gt;
&lt;p&gt;The real power of full stack development comes from understanding the connections between these layers.&lt;/p&gt;
&lt;p&gt;You don't need to become an expert in everything immediately. Start with the fundamentals, build small applications, understand how each layer communicates with the next, and gradually introduce more advanced concepts such as authentication, database relationships, caching, indexing, transactions, testing, and deployment.&lt;/p&gt;
&lt;p&gt;Once you understand the journey from a user's click in React to a SQL query in PostgreSQL and back again you stop thinking about technologies as isolated tools.&lt;/p&gt;
&lt;p&gt;You start thinking like a full stack developer.&lt;/p&gt;
&lt;p&gt;And that shift in perspective is one of the most valuable skills you can develop in modern software engineering.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30077/from-react-to-postgresql-understanding-the-full-stack</guid>
<pubDate>Wed, 07 Oct 2026 05:22:39 +0000</pubDate>
</item>
<item>
<title>Securing the MyZubster Metaverse: From Stateless JWTs to Revocable Sessions</title>
<link>https://coderlegion.com/28926/securing-the-myzubster-metaverse-from-stateless-jwts-to-revocable-sessions</link>
<description>&lt;p&gt;Securing the MyZubster Metaverse: From Stateless JWTs to Revocable Sessions&lt;br&gt;
We have completed another major security milestone for the MyZubster Metaverse.&lt;br&gt;
The original authentication system depended mainly on browser-stored JWT access tokens. That solution was simple, but it could not provide the session lifecycle controls required by a platform with persistent identities, virtual characters, private rooms, realtime interactions and future Marketplace or payment operations.&lt;br&gt;
Under MYZ-71 — Authentication, Sessions &amp;amp; Account Security, we have now implemented:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;persistent server-side sessions;&lt;/li&gt;
&lt;li&gt;session-bound JWT access tokens;&lt;/li&gt;
&lt;li&gt;real logout;&lt;/li&gt;
&lt;li&gt;active-device management;&lt;/li&gt;
&lt;li&gt;remote session revocation;&lt;/li&gt;
&lt;li&gt;rotating opaque refresh tokens;&lt;/li&gt;
&lt;li&gt;refresh-token replay detection;&lt;/li&gt;
&lt;li&gt;automatic access-token renewal for the Metaverse;&lt;/li&gt;
&lt;li&gt;concurrent refresh deduplication;&lt;/li&gt;
&lt;li&gt;origin-based CSRF protection;&lt;/li&gt;
&lt;li&gt;structured authentication errors.&lt;br&gt;
The implementation is currently available in a Draft pull request:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/danieldirimini-myzubster/myzubster/pull/10&quot;&gt;https://github.com/danieldirimini-myzubster/myzubster/pull/10&lt;/a&gt;&lt;br&gt;
It has not been merged or declared production-ready yet.&lt;br&gt;
Why stateless JWTs were no longer enough&lt;br&gt;
A server can validate a JWT without loading a database session, but a completely stateless design has significant limitations.&lt;br&gt;
A token may remain valid even after:&lt;/li&gt;
&lt;li&gt;the user logs out;&lt;/li&gt;
&lt;li&gt;the device is no longer trusted;&lt;/li&gt;
&lt;li&gt;the password changes;&lt;/li&gt;
&lt;li&gt;the account owner wants to disconnect another device;&lt;/li&gt;
&lt;li&gt;a security event requires immediate revocation.&lt;br&gt;
Long-lived tokens also increase the impact of credential theft.&lt;br&gt;
If they are stored in localStorage, they are accessible to frontend JavaScript. This makes an XSS vulnerability significantly more dangerous.&lt;br&gt;
We moved toward a hybrid architecture:&lt;br&gt;
User authentication&lt;pre&gt;&lt;code&gt;  ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Persistent server-side session&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Short-lived JWT access token&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rotating opaque refresh token&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Automatic renewal&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  ↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replay detection and revocation&lt;br&gt;
The JWT is still useful, but the persistent session is now the final source of authority.&lt;br&gt;
Persistent server-side sessions&lt;br&gt;
Each authenticated device receives its own MongoDB AuthSession.&lt;br&gt;
A session records:&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;the session identifier;&lt;/li&gt;
&lt;li&gt;the owning user;&lt;/li&gt;
&lt;li&gt;creation and expiration timestamps;&lt;/li&gt;
&lt;li&gt;last activity;&lt;/li&gt;
&lt;li&gt;limited device metadata;&lt;/li&gt;
&lt;li&gt;a privacy-preserving network digest;&lt;/li&gt;
&lt;li&gt;revocation time and reason;&lt;/li&gt;
&lt;li&gt;the current refresh-token hash;&lt;/li&gt;
&lt;li&gt;previously consumed refresh-token hashes;&lt;/li&gt;
&lt;li&gt;refresh-token rotation metadata.&lt;br&gt;
MongoDB TTL indexing is used to remove expired sessions automatically.&lt;br&gt;
A valid JWT is rejected when its corresponding session is missing, expired or revoked.&lt;br&gt;
Binding access tokens to sessions&lt;br&gt;
New access tokens include a server-generated sid claim.&lt;br&gt;
A simplified token looks like this:&lt;br&gt;
{&lt;br&gt;
&quot;sub&quot;: &quot;user-id&quot;,&lt;br&gt;
&quot;sid&quot;: &quot;session-uuid&quot;,&lt;br&gt;
&quot;jti&quot;: &quot;access-token-uuid&quot;,&lt;br&gt;
&quot;iat&quot;: 1790540000,&lt;br&gt;
&quot;exp&quot;: 1790540900&lt;br&gt;
}&lt;br&gt;
Request authentication now consists of two steps:&lt;br&gt;
Verify the JWT&lt;pre&gt;&lt;code&gt;↓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Validate the server-side session&lt;br&gt;
This provides both efficient request authentication and immediate revocation.&lt;br&gt;
If a device is removed from the account-security interface, its JWT stops working even if it has not reached its expiration time.&lt;br&gt;
Real logout and active-device management&lt;br&gt;
The React frontend now includes:&lt;br&gt;
/account/security&lt;br&gt;
Users can inspect:&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;active sessions;&lt;/li&gt;
&lt;li&gt;the current device;&lt;/li&gt;
&lt;li&gt;creation time;&lt;/li&gt;
&lt;li&gt;last activity;&lt;/li&gt;
&lt;li&gt;expiration time;&lt;/li&gt;
&lt;li&gt;limited device information.&lt;br&gt;
They can disconnect another device or terminate their current session.&lt;br&gt;
The supporting endpoints are:&lt;br&gt;
POST /api/auth/logout&lt;br&gt;
GET /api/auth/me&lt;br&gt;
GET /api/auth/me/sessions&lt;br&gt;
DELETE /api/auth/me/sessions/:sessionId&lt;br&gt;
Logout is idempotent.&lt;br&gt;
Even when the access token is expired or malformed, the backend clears the authentication cookies. When possible, the refresh token is used to identify and revoke the persistent session.&lt;br&gt;
Opaque refresh tokens&lt;br&gt;
The refresh token is deliberately not a JWT.&lt;br&gt;
Its conceptual format is:&lt;br&gt;
myzr.&amp;lt;session-id&amp;gt;.&amp;lt;cryptographically-random-secret&amp;gt;&lt;br&gt;
The raw token is never stored in MongoDB.&lt;br&gt;
The backend calculates:&lt;br&gt;
HMAC-SHA256(server secret, raw refresh token)&lt;br&gt;
Only the resulting digest is persisted.&lt;br&gt;
If the session database is exposed, an attacker cannot directly use the stored digest to renew a session.&lt;br&gt;
Atomic refresh-token rotation&lt;br&gt;
Refresh tokens are single-use.&lt;br&gt;
When the browser calls:&lt;br&gt;
POST /api/auth/refresh&lt;br&gt;
the server:&lt;ol&gt;
&lt;li&gt;reads the HttpOnly refresh cookie;&lt;/li&gt;
&lt;li&gt;extracts the session identifier;&lt;/li&gt;
&lt;li&gt;hashes the submitted token;&lt;/li&gt;
&lt;li&gt;creates a new random refresh token;&lt;/li&gt;
&lt;li&gt;atomically compares the submitted hash with the current stored hash;&lt;/li&gt;
&lt;li&gt;replaces the current hash;&lt;/li&gt;
&lt;li&gt;stores the consumed hash in a bounded history;&lt;/li&gt;
&lt;li&gt;issues a new access token;&lt;/li&gt;
&lt;li&gt;sends new authentication cookies.&lt;br&gt;
Only one request can successfully consume the current token.&lt;br&gt;
This prevents ordinary concurrent requests from rotating the same credential more than once.&lt;br&gt;
Refresh-token replay detection&lt;br&gt;
The server maintains a bounded history of consumed refresh-token hashes.&lt;br&gt;
If a previously consumed token appears again, the session is treated as potentially compromised:&lt;br&gt;
Old refresh token submitted&lt;br&gt;
   ↓&lt;br&gt;
Replay detected&lt;br&gt;
   ↓&lt;br&gt;
Entire session revoked&lt;br&gt;
   ↓&lt;br&gt;
Cookies cleared&lt;br&gt;
   ↓&lt;br&gt;
New login required&lt;br&gt;
The revocation reason is recorded as:&lt;br&gt;
refresh-token-replay&lt;br&gt;
The history is limited so the MongoDB session document cannot grow indefinitely.&lt;br&gt;
Automatic renewal in the Metaverse&lt;br&gt;
We introduced a reusable frontend client:&lt;br&gt;
authenticatedFetch(input, options)&lt;br&gt;
The client handles the access-token lifecycle:&lt;br&gt;
Protected request&lt;br&gt;
   ↓&lt;br&gt;
401 access-token response&lt;br&gt;
   ↓&lt;br&gt;
POST /api/auth/refresh&lt;br&gt;
   ↓&lt;br&gt;
Receive rotated access token&lt;br&gt;
   ↓&lt;br&gt;
Retry original request once&lt;br&gt;
The complete Metaverse API module now uses this client.&lt;br&gt;
It covers operations involving:&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;profiles;&lt;/li&gt;
&lt;li&gt;rooms;&lt;/li&gt;
&lt;li&gt;invitations;&lt;/li&gt;
&lt;li&gt;sessions;&lt;/li&gt;
&lt;li&gt;participants;&lt;/li&gt;
&lt;li&gt;stage access;&lt;/li&gt;
&lt;li&gt;messages;&lt;/li&gt;
&lt;li&gt;moderation;&lt;/li&gt;
&lt;li&gt;reports;&lt;/li&gt;
&lt;li&gt;landmarks;&lt;/li&gt;
&lt;li&gt;realtime world actions.&lt;br&gt;
The client retries only recognized access-token errors and never enters an infinite retry loop.&lt;br&gt;
Deduplicating concurrent refresh operations&lt;br&gt;
A Metaverse page can perform several API requests simultaneously.&lt;br&gt;
When an access token expires, multiple requests may receive a 401 at approximately the same time.&lt;br&gt;
Without coordination, every request could call /api/auth/refresh using the same single-use refresh token. This could produce an accidental replay event.&lt;br&gt;
The client therefore shares one in-flight refresh operation:&lt;br&gt;
Request A → 401 ┐&lt;br&gt;
Request B → 401 ├─→ One refresh&lt;br&gt;
Request C → 401 ┘&lt;pre&gt;&lt;code&gt;               ↓
         New access token
               ↓
        Retry A, B and C
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tests confirm that two simultaneous protected requests produce exactly one refresh operation and two successful retries.&lt;br&gt;
Handling terminal authentication failures&lt;br&gt;
The client does not silently renew an explicitly revoked session.&lt;br&gt;
For example:&lt;br&gt;
AUTH_SESSION_REVOKED&lt;br&gt;
is returned directly to the application.&lt;br&gt;
If the refresh token is no longer valid, the client:&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;clears the temporary browser credentials;&lt;/li&gt;
&lt;li&gt;emits:&lt;br&gt;
myzubster:auth-expired&lt;br&gt;
The UI can use this event to return the user to authentication without every page implementing its own renewal logic.&lt;br&gt;
CSRF protection&lt;br&gt;
Cookie authentication introduces a different security risk: browsers attach cookies automatically.&lt;br&gt;
State-changing requests must therefore be protected against Cross-Site Request Forgery.&lt;br&gt;
We added an origin-based CSRF guard for unsafe cookie-authenticated methods:&lt;br&gt;
POST&lt;br&gt;
PUT&lt;br&gt;
PATCH&lt;br&gt;
DELETE&lt;br&gt;
The guard evaluates:&lt;/li&gt;
&lt;li&gt;Origin;&lt;/li&gt;
&lt;li&gt;Referer;&lt;/li&gt;
&lt;li&gt;Sec-Fetch-Site;&lt;/li&gt;
&lt;li&gt;configured application origins;&lt;/li&gt;
&lt;li&gt;the actual request target.&lt;br&gt;
A browser request marked as cross-site is rejected before the backend performs a session lookup:&lt;br&gt;
Sec-Fetch-Site: cross-site&lt;br&gt;
The API returns a structured response:&lt;br&gt;
{&lt;br&gt;
&quot;success&quot;: false,&lt;br&gt;
&quot;request_id&quot;: &quot;request-uuid&quot;,&lt;br&gt;
&quot;error&quot;: {&lt;br&gt;
  &quot;code&quot;: &quot;AUTH_CSRF_REJECTED&quot;,&lt;br&gt;
  &quot;message&quot;: &quot;Origine della richiesta non autorizzata&quot;&lt;br&gt;
}&lt;br&gt;
}&lt;br&gt;
Which routes are protected?&lt;br&gt;
The trusted-origin policy is applied to:&lt;/li&gt;
&lt;li&gt;password registration;&lt;/li&gt;
&lt;li&gt;password login;&lt;/li&gt;
&lt;li&gt;OAuth ticket verification;&lt;/li&gt;
&lt;li&gt;social-login ticket exchange;&lt;/li&gt;
&lt;li&gt;refresh-token rotation;&lt;/li&gt;
&lt;li&gt;logout;&lt;/li&gt;
&lt;li&gt;Gmail verification exchange;&lt;/li&gt;
&lt;li&gt;protected mutations authenticated through cookies.&lt;br&gt;
Bearer-token clients remain compatible during the migration because they do not depend on automatically attached cookies.&lt;br&gt;
CLI and server-to-server clients that do not send browser origin metadata also remain supported.&lt;br&gt;
Configuring trusted origins&lt;br&gt;
Production and preview origins can be configured with:&lt;br&gt;
AUTH_TRUSTED_ORIGINS=&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.myzubster.com,https://preview.myzubster.com&quot;&gt;https://www.myzubster.com,https://preview.myzubster.com&lt;/a&gt;&lt;br&gt;
The middleware also recognizes:&lt;br&gt;
FRONTEND_URL&lt;br&gt;
PUBLIC_APP_URL&lt;br&gt;
GATEWAY_PUBLIC_URL&lt;br&gt;
Local development accepts:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;http://localhost:3000&quot;&gt;http://localhost:3000&lt;/a&gt;&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;http://127.0.0.1:3000&quot;&gt;http://127.0.0.1:3000&lt;/a&gt;&lt;br&gt;
These values still need to be verified in the real deployment before the platform switches to cookie-only authentication.&lt;br&gt;
Cookie protections&lt;br&gt;
The authentication cookies use:&lt;br&gt;
HttpOnly&lt;br&gt;
SameSite=Lax&lt;br&gt;
Secure in production&lt;br&gt;
The refresh cookie is restricted to:&lt;br&gt;
Path=/api/auth&lt;br&gt;
This prevents it from being attached to unrelated application requests.&lt;br&gt;
SameSite=Lax provides another security layer, but it is not treated as the only CSRF defence.&lt;br&gt;
Privacy considerations&lt;br&gt;
Raw client IP addresses are not stored.&lt;br&gt;
The server stores an HMAC-derived digest instead. This allows limited correlation for security analysis without retaining the original address as ordinary application data.&lt;br&gt;
Device metadata is also intentionally limited.&lt;br&gt;
A session-security feature should help users recognize their devices without becoming an invasive tracking mechanism.&lt;br&gt;
Validation results&lt;br&gt;
The focused backend security scope passes:&lt;br&gt;
30/30 tests&lt;br&gt;
The focused frontend and Metaverse scope passes:&lt;br&gt;
19/19 tests&lt;br&gt;
The test coverage includes:&lt;/li&gt;
&lt;li&gt;persistent session creation;&lt;/li&gt;
&lt;li&gt;session-bound JWT validation;&lt;/li&gt;
&lt;li&gt;real logout;&lt;/li&gt;
&lt;li&gt;remote revocation;&lt;/li&gt;
&lt;li&gt;refresh-token hashing;&lt;/li&gt;
&lt;li&gt;atomic token rotation;&lt;/li&gt;
&lt;li&gt;refresh replay detection;&lt;/li&gt;
&lt;li&gt;replay-driven session revocation;&lt;/li&gt;
&lt;li&gt;automatic access-token renewal;&lt;/li&gt;
&lt;li&gt;exactly-once retry;&lt;/li&gt;
&lt;li&gt;concurrent refresh deduplication;&lt;/li&gt;
&lt;li&gt;structured authentication failures;&lt;/li&gt;
&lt;li&gt;same-origin CSRF acceptance;&lt;/li&gt;
&lt;li&gt;configured frontend-origin acceptance;&lt;/li&gt;
&lt;li&gt;cross-site CSRF rejection;&lt;/li&gt;
&lt;li&gt;rejection before database lookup;&lt;/li&gt;
&lt;li&gt;bearer-token compatibility;&lt;/li&gt;
&lt;li&gt;non-browser compatibility.&lt;br&gt;
The React production build also completes successfully.&lt;br&gt;
GitHub Actions status&lt;br&gt;
The affected backend suites are green:&lt;br&gt;
tests/authSessionService.test.js&lt;br&gt;
tests/authSessionController.test.js&lt;br&gt;
tests/authMiddlewareSessions.test.js&lt;br&gt;
tests/socialIdentityService.test.js&lt;br&gt;
tests/csrfProtection.test.js&lt;br&gt;
The complete repository currently reports:&lt;br&gt;
142 test suites passed&lt;br&gt;
42 test suites failed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;746 tests passed&lt;br&gt;
44 tests failed&lt;br&gt;
The remaining failures belong to existing repository-wide problems involving Zorgax, Marketplace, seller policies and legacy Metaverse expectations.&lt;br&gt;
The new authentication implementation is green in its affected scope, but the pull request remains a Draft because the repository as a whole is not yet CI-clean.&lt;br&gt;
What is completed?&lt;br&gt;
Persistent server-side sessions           DONE&lt;br&gt;
Session-bound access tokens                DONE&lt;br&gt;
Real logout                                DONE&lt;br&gt;
Active-device management                   DONE&lt;br&gt;
Remote session revocation                  DONE&lt;br&gt;
Rotating opaque refresh tokens             DONE&lt;br&gt;
Refresh-token replay detection             DONE&lt;br&gt;
Password and social authentication         DONE&lt;br&gt;
Account-security interface                 DONE&lt;br&gt;
Metaverse automatic renewal                DONE&lt;br&gt;
Concurrent refresh deduplication           DONE&lt;br&gt;
Origin-based CSRF protection               DONE&lt;br&gt;
Structured authentication errors           DONE&lt;br&gt;
Focused automated testing                  DONE&lt;br&gt;
What is still missing?&lt;br&gt;
Renewal outside the Metaverse&lt;br&gt;
Some Marketplace, Zorgax, profile and administrative pages still use direct fetch calls and manually constructed bearer headers.&lt;br&gt;
They need to be migrated to authenticatedFetch.&lt;br&gt;
Removal of localStorage authentication&lt;br&gt;
Browser storage remains as a temporary migration bridge.&lt;br&gt;
The final architecture should avoid exposing reusable credentials to frontend JavaScript.&lt;br&gt;
Before removing it, we must migrate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;all authenticated API clients;&lt;/li&gt;
&lt;li&gt;protected React pages;&lt;/li&gt;
&lt;li&gt;realtime connections;&lt;/li&gt;
&lt;li&gt;embedded or mobile clients;&lt;/li&gt;
&lt;li&gt;legacy user sessions.&lt;br&gt;
Production trusted-origin validation&lt;br&gt;
The public deployment must verify:&lt;/li&gt;
&lt;li&gt;exact frontend origins;&lt;/li&gt;
&lt;li&gt;preview domains;&lt;/li&gt;
&lt;li&gt;reverse-proxy host and protocol forwarding;&lt;/li&gt;
&lt;li&gt;cookie paths;&lt;/li&gt;
&lt;li&gt;cross-site rejection;&lt;/li&gt;
&lt;li&gt;same-origin refresh;&lt;/li&gt;
&lt;li&gt;session revocation;&lt;/li&gt;
&lt;li&gt;deliberate replay detection.&lt;br&gt;
Strict session enforcement&lt;br&gt;
After legacy tokens have expired, we can enable:&lt;br&gt;
REQUIRE_SERVER_SESSION=true&lt;br&gt;
JWTs without a valid persistent session will then be rejected.&lt;br&gt;
Passkeys and magic links&lt;br&gt;
MYZ-71 still includes passwordless authentication.&lt;br&gt;
Passkeys require complete WebAuthn support for:&lt;/li&gt;
&lt;li&gt;challenge generation;&lt;/li&gt;
&lt;li&gt;relying-party configuration;&lt;/li&gt;
&lt;li&gt;allowed origins;&lt;/li&gt;
&lt;li&gt;credential counters;&lt;/li&gt;
&lt;li&gt;credential revocation;&lt;/li&gt;
&lt;li&gt;recovery.&lt;br&gt;
Magic links require short-lived, single-use verification tokens.&lt;br&gt;
Step-up authentication&lt;br&gt;
Sensitive operations should require recent stronger authentication.&lt;br&gt;
Possible examples include:&lt;/li&gt;
&lt;li&gt;changing an email address;&lt;/li&gt;
&lt;li&gt;changing a password;&lt;/li&gt;
&lt;li&gt;registering or deleting passkeys;&lt;/li&gt;
&lt;li&gt;deleting an account;&lt;/li&gt;
&lt;li&gt;wallet operations;&lt;/li&gt;
&lt;li&gt;payment approval;&lt;/li&gt;
&lt;li&gt;sensitive Marketplace changes.&lt;br&gt;
Production OAuth and database validation&lt;br&gt;
Google, GitHub and Facebook authentication must still be tested using the production callback URLs and secrets.&lt;br&gt;
MongoDB Atlas must be checked for:&lt;/li&gt;
&lt;li&gt;TTL-index creation;&lt;/li&gt;
&lt;li&gt;refresh-token indexes;&lt;/li&gt;
&lt;li&gt;document growth;&lt;/li&gt;
&lt;li&gt;expired-session cleanup;&lt;/li&gt;
&lt;li&gt;concurrent rotation;&lt;/li&gt;
&lt;li&gt;rolling-deployment compatibility.&lt;br&gt;
Recommended next steps&lt;ol&gt;
&lt;li&gt;Review the Draft pull request&lt;/li&gt;
&lt;li&gt;Configure production trusted origins&lt;/li&gt;
&lt;li&gt;Deploy with migration compatibility enabled&lt;/li&gt;
&lt;li&gt;Verify cookies and database indexes&lt;/li&gt;
&lt;li&gt;Test automatic Metaverse renewal&lt;/li&gt;
&lt;li&gt;Test concurrent refresh requests&lt;/li&gt;
&lt;li&gt;Test cross-site rejection&lt;/li&gt;
&lt;li&gt;Test deliberate refresh-token replay&lt;/li&gt;
&lt;li&gt;Migrate the remaining frontend API clients&lt;/li&gt;
&lt;li&gt;Allow legacy tokens to expire&lt;/li&gt;
&lt;li&gt;Remove tokens from localStorage&lt;/li&gt;
&lt;li&gt;Enable strict session enforcement&lt;/li&gt;
&lt;li&gt;Remove bearer migration support&lt;/li&gt;
&lt;li&gt;Add passkeys, magic links and step-up authentication&lt;br&gt;
Final thoughts&lt;br&gt;
Authentication security is not just about issuing a JWT.&lt;br&gt;
A complete system must manage the entire lifecycle:&lt;br&gt;
Authenticate&lt;br&gt;
Create&lt;br&gt;
Validate&lt;br&gt;
Renew&lt;br&gt;
Observe&lt;br&gt;
Revoke&lt;br&gt;
Recover&lt;br&gt;
Monitor&lt;br&gt;
MyZubster now has the foundations for secure multi-device sessions, refresh-token rotation, replay detection, automatic Metaverse renewal and browser-origin protection.&lt;br&gt;
The next challenge is deployment and migration: validate the public environment, move the remaining clients to the shared authentication layer and eliminate browser-accessible credentials.&lt;br&gt;
Draft pull request:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/danieldirimini-myzubster/myzubster/pull/10&quot;&gt;https://github.com/danieldirimini-myzubster/myzubster/pull/10&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;myzubster-metaverse-cybersecurity-authentication-nodejs-reactjs-mongodb-jwt-csrf-websecurity-softwarearchitecture-opensource&quot;&gt;MyZubster #Metaverse #CyberSecurity #Authentication #NodeJS #ReactJS #MongoDB #JWT #CSRF #WebSecurity #SoftwareArchitecture #OpenSource&lt;/h2&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28926/securing-the-myzubster-metaverse-from-stateless-jwts-to-revocable-sessions</guid>
<pubDate>Mon, 28 Sep 2026 12:31:55 +0000</pubDate>
</item>
<item>
<title>From Stateless JWTs to Revocable Sessions: Securing the MyZubster Metaverse</title>
<link>https://coderlegion.com/28825/from-stateless-jwts-to-revocable-sessions-securing-the-myzubster-metaverse</link>
<description>&lt;p&gt;From Stateless JWTs to Revocable Sessions: Securing the MyZubster Metaverse&lt;br&gt;
MyZubster is developing a persistent open-source digital ecosystem that connects verified identities, metaverse characters, community missions, knowledge, marketplaces, and contributor activity.&lt;br&gt;
As the platform grows, authentication can no longer mean simply issuing a JSON Web Token and trusting it until it expires.&lt;br&gt;
A persistent metaverse requires a stronger security model:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;real server-side logout;&lt;/li&gt;
&lt;li&gt;session and device management;&lt;/li&gt;
&lt;li&gt;remote revocation;&lt;/li&gt;
&lt;li&gt;privacy-preserving activity metadata;&lt;/li&gt;
&lt;li&gt;secure browser cookies;&lt;/li&gt;
&lt;li&gt;structured authentication errors;&lt;/li&gt;
&lt;li&gt;compatibility with existing clients;&lt;/li&gt;
&lt;li&gt;a path toward passkeys and rotating refresh tokens.&lt;br&gt;
We have now implemented the first major security slice of MYZ-71 — Authentication, sessions and account security.&lt;br&gt;
The implementation is available in the following draft pull request:&lt;br&gt;
MYZ-71: Add revocable sessions and device security controls&lt;br&gt;
The pull request is still a draft. It has not been merged or deployed to production.&lt;br&gt;
Why stateless JWTs were no longer enough&lt;br&gt;
The previous authentication architecture issued a signed JWT containing information such as:&lt;br&gt;
{&lt;br&gt;
&quot;userId&quot;: &quot;USER_ID&quot;,&lt;br&gt;
&quot;username&quot;: &quot;daniel&quot;,&lt;br&gt;
&quot;role&quot;: &quot;user&quot;,&lt;br&gt;
&quot;iat&quot;: 1789970000,&lt;br&gt;
&quot;exp&quot;: 1790574800&lt;br&gt;
}&lt;br&gt;
The server could validate the signature and expiration time, but it could not immediately answer several important security questions:&lt;/li&gt;
&lt;li&gt;Did the user log out?&lt;/li&gt;
&lt;li&gt;Was the device remotely revoked?&lt;/li&gt;
&lt;li&gt;Is the token associated with a known active login?&lt;/li&gt;
&lt;li&gt;Was the session disabled after the token was created?&lt;/li&gt;
&lt;li&gt;Which browser created the session?&lt;/li&gt;
&lt;li&gt;Should this token be rejected before its cryptographic expiration?&lt;br&gt;
A purely stateless JWT remains valid until it expires. Logging out in the browser may remove a local token, but it does not necessarily invalidate a copied token.&lt;br&gt;
That limitation becomes especially important when an account controls:&lt;/li&gt;
&lt;li&gt;a persistent metaverse character;&lt;/li&gt;
&lt;li&gt;private rooms;&lt;/li&gt;
&lt;li&gt;creator permissions;&lt;/li&gt;
&lt;li&gt;marketplace activity;&lt;/li&gt;
&lt;li&gt;verified social identities;&lt;/li&gt;
&lt;li&gt;community moderation;&lt;/li&gt;
&lt;li&gt;contributor or reward-related actions.&lt;br&gt;
The new hybrid architecture&lt;br&gt;
We retained JWTs, but every newly issued JWT is now connected to a persistent server-side session.&lt;br&gt;
The new token includes two identifiers:&lt;br&gt;
{&lt;br&gt;
&quot;userId&quot;: &quot;USER_ID&quot;,&lt;br&gt;
&quot;username&quot;: &quot;daniel&quot;,&lt;br&gt;
&quot;role&quot;: &quot;user&quot;,&lt;br&gt;
&quot;sid&quot;: &quot;4cb9c3a1-8d39-4f2d-8a91-82c9556abc91&quot;,&lt;br&gt;
&quot;jti&quot;: &quot;48ab2bd5-b4fa-426a-af42-3e24758e0434&quot;,&lt;br&gt;
&quot;iat&quot;: 1789970000,&lt;br&gt;
&quot;exp&quot;: 1790574800&lt;br&gt;
}&lt;br&gt;
The sid claim identifies the persistent session. The jti uniquely identifies the issued JWT.&lt;br&gt;
Authentication now follows this sequence:&lt;br&gt;
Request&lt;br&gt;
↓&lt;br&gt;
Extract token from secure cookie or Authorization header&lt;br&gt;
↓&lt;br&gt;
Verify JWT signature and expiration&lt;br&gt;
↓&lt;br&gt;
Read the sid claim&lt;br&gt;
↓&lt;br&gt;
Find the corresponding server-side session&lt;br&gt;
↓&lt;br&gt;
Verify ownership, expiration and revocation state&lt;br&gt;
↓&lt;br&gt;
Allow or reject the request&lt;br&gt;
A valid JWT is no longer sufficient when its server-side session has been revoked or expired.&lt;br&gt;
Persistent AuthSession records&lt;br&gt;
A new MongoDB model stores the authentication state.&lt;br&gt;
Conceptually, the model contains:&lt;br&gt;
{&lt;br&gt;
sessionId: String,&lt;br&gt;
userId: ObjectId,&lt;br&gt;
userAgent: String,&lt;br&gt;
ipHash: String,&lt;br&gt;
createdAt: Date,&lt;br&gt;
lastSeenAt: Date,&lt;br&gt;
expiresAt: Date,&lt;br&gt;
revokedAt: Date,&lt;br&gt;
revokedReason: String&lt;br&gt;
}&lt;br&gt;
Session creation&lt;br&gt;
Every password or social login creates a cryptographically random session identifier:&lt;br&gt;
const sessionId = crypto.randomUUID();&lt;br&gt;
A JWT is then signed with that identifier:&lt;br&gt;
const token = jwt.sign(&lt;br&gt;
{&lt;br&gt;
  userId: String(user._id),&lt;br&gt;
  username: user.username,&lt;br&gt;
  role: user.role,&lt;br&gt;
  sid: sessionId&lt;br&gt;
},&lt;br&gt;
jwtSecret,&lt;br&gt;
{&lt;br&gt;
  expiresIn: sessionTtl,&lt;br&gt;
  jwtid: crypto.randomUUID()&lt;br&gt;
}&lt;br&gt;
);&lt;br&gt;
The session document and token are created as part of the same authentication flow.&lt;br&gt;
Expiration enforcement&lt;br&gt;
The session contains an explicit expiration timestamp and a MongoDB TTL index.&lt;br&gt;
However, MongoDB TTL cleanup is asynchronous. An expired document may remain in the collection temporarily.&lt;br&gt;
For this reason, authentication never relies only on physical deletion. The validation query requires:&lt;br&gt;
{&lt;br&gt;
sessionId: decoded.sid,&lt;br&gt;
userId: decoded.userId,&lt;br&gt;
revokedAt: null,&lt;br&gt;
expiresAt: { $gt: new Date() }&lt;br&gt;
}&lt;br&gt;
The session becomes unusable immediately after expiration, even if MongoDB has not deleted it yet.&lt;br&gt;
Revocation&lt;br&gt;
Revocation does not delete the session immediately. Instead, the server records when and why it was revoked:&lt;br&gt;
{&lt;br&gt;
revokedAt: new Date(),&lt;br&gt;
revokedReason: &quot;user-revoked&quot;&lt;br&gt;
}&lt;br&gt;
Keeping this state is useful for security analysis and makes the revocation operation auditable.&lt;br&gt;
Privacy-preserving IP metadata&lt;br&gt;
The server does not store raw IP addresses.&lt;br&gt;
When an address is available, it is converted into an HMAC-SHA256 digest:&lt;br&gt;
const ipHash = crypto&lt;br&gt;
.createHmac(&quot;sha256&quot;, SESSION_IP_HASH_SECRET)&lt;br&gt;
.update(ipAddress)&lt;br&gt;
.digest(&quot;hex&quot;);&lt;br&gt;
This allows the application to identify meaningful changes between login contexts without creating a database of clear-text IP addresses.&lt;br&gt;
The HMAC secret prevents the value from behaving like a simple unsalted hash that could be easily precomputed.&lt;br&gt;
Last-activity tracking without excessive writes&lt;br&gt;
The session records lastSeenAt, but it is not updated on every request.&lt;br&gt;
Updating the database for every authenticated API call would create unnecessary write volume.&lt;br&gt;
Instead, the timestamp is refreshed only when the previous update is older than five minutes:&lt;br&gt;
if (Date.now() - lastSeenAt &amp;gt; FIVE_MINUTES) {&lt;br&gt;
await AuthSession.updateOne(&lt;br&gt;
  {&lt;pre&gt;&lt;code&gt;sessionId: decoded.sid,
revokedAt: null
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  },&lt;br&gt;
  {&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$set: {
  lastSeenAt: new Date()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  }&lt;br&gt;
);&lt;br&gt;
}&lt;br&gt;
This provides useful account activity information while reducing database pressure.&lt;br&gt;
Secure cookies and compatibility mode&lt;br&gt;
The authentication token can now be received from either:&lt;br&gt;
Authorization: Bearer TOKEN&lt;br&gt;
or the following browser cookie:&lt;br&gt;
myzubster_session&lt;br&gt;
The cookie uses these properties:&lt;br&gt;
{&lt;br&gt;
httpOnly: true,&lt;br&gt;
secure: production,&lt;br&gt;
sameSite: &quot;lax&quot;,&lt;br&gt;
path: &quot;/&quot;,&lt;br&gt;
maxAge: sessionTtl&lt;br&gt;
}&lt;br&gt;
Why use HttpOnly?&lt;br&gt;
Browser JavaScript cannot directly read an HttpOnly cookie.&lt;br&gt;
This limits simple token extraction through code such as:&lt;br&gt;
document.cookie&lt;br&gt;
It does not solve every XSS scenario, but it substantially reduces the exposure of authentication secrets.&lt;br&gt;
Why bearer tokens are still supported&lt;br&gt;
MyZubster already has clients that use tokens stored by older authentication flows.&lt;br&gt;
Removing bearer support immediately would invalidate existing sessions and potentially break multiple parts of the frontend.&lt;br&gt;
The system therefore supports a migration period:&lt;br&gt;
Legacy client&lt;br&gt;
→ Authorization: Bearer &lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;New browser session&lt;br&gt;
  → HttpOnly myzubster_session cookie&lt;br&gt;
Older JWTs may also lack an sid claim. They remain temporarily compatible unless strict mode is enabled:&lt;br&gt;
REQUIRE_SERVER_SESSION=true&lt;br&gt;
Once existing tokens have expired, strict mode can reject every JWT that is not connected to a server session.&lt;br&gt;
New authentication endpoints&lt;br&gt;
Four important endpoints were added.&lt;br&gt;
Current account&lt;br&gt;
GET /api/auth/me&lt;br&gt;
Returns the authenticated user and associated public character information.&lt;br&gt;
Active devices and sessions&lt;br&gt;
GET /api/auth/me/sessions&lt;br&gt;
Example response:&lt;br&gt;
{&lt;br&gt;
  &quot;success&quot;: true,&lt;br&gt;
  &quot;request_id&quot;: &quot;bf7a957e-1669-4432-a9a4-e686cb70cc07&quot;,&lt;br&gt;
  &quot;sessions&quot;: [&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;id&quot;: &quot;4cb9c3a1-8d39-4f2d-8a91-82c9556abc91&quot;,
  &quot;current&quot;: true,
  &quot;device&quot;: &quot;Mozilla/5.0 ...&quot;,
  &quot;createdAt&quot;: &quot;2026-09-27T08:00:00.000Z&quot;,
  &quot;lastSeenAt&quot;: &quot;2026-09-27T09:00:00.000Z&quot;,
  &quot;expiresAt&quot;: &quot;2026-10-04T08:00:00.000Z&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  ]&lt;br&gt;
}&lt;br&gt;
Only sessions belonging to the authenticated user are returned.&lt;br&gt;
Revoke a session&lt;br&gt;
DELETE /api/auth/me/sessions/:sessionId&lt;br&gt;
The update is scoped by both the user and session identifier:&lt;br&gt;
{&lt;br&gt;
  userId: authenticatedUserId,&lt;br&gt;
  sessionId,&lt;br&gt;
  revokedAt: null&lt;br&gt;
}&lt;br&gt;
This prevents one account from revoking another user’s session.&lt;br&gt;
Logout&lt;br&gt;
POST /api/auth/logout&lt;br&gt;
Logout is deliberately idempotent.&lt;br&gt;
The cookie is cleared even when the submitted token is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;expired;&lt;/li&gt;
&lt;li&gt;malformed;&lt;/li&gt;
&lt;li&gt;already revoked;&lt;/li&gt;
&lt;li&gt;disconnected from a server session.&lt;br&gt;
The logout route is intentionally not protected by the standard authentication middleware. Otherwise, an expired token could be rejected before the handler had an opportunity to clear the invalid cookie.&lt;br&gt;
When a valid session is available, logout also revokes it server-side.&lt;br&gt;
Structured authentication errors&lt;br&gt;
Authentication errors now use stable public codes instead of exposing raw JWT library messages.&lt;br&gt;
Example:&lt;br&gt;
{&lt;br&gt;
&quot;success&quot;: false,&lt;br&gt;
&quot;request_id&quot;: &quot;5fe11522-c804-4907-a4d1-0f459f94ab2a&quot;,&lt;br&gt;
&quot;error&quot;: {&lt;br&gt;
  &quot;code&quot;: &quot;AUTH_SESSION_REVOKED&quot;,&lt;br&gt;
  &quot;message&quot;: &quot;Session expired or revoked&quot;&lt;br&gt;
}&lt;br&gt;
}&lt;br&gt;
Current error categories include:&lt;br&gt;
AUTH_TOKEN_MISSING&lt;br&gt;
AUTH_TOKEN_INVALID&lt;br&gt;
AUTH_TOKEN_EXPIRED&lt;br&gt;
AUTH_SESSION_REVOKED&lt;br&gt;
Each response contains a request ID that can be correlated with server logs.&lt;br&gt;
This is useful for debugging while avoiding the disclosure of stack traces, signing details, or internal JWT parsing errors.&lt;br&gt;
Integration with social authentication&lt;br&gt;
Persistent sessions are now created for:&lt;/li&gt;
&lt;li&gt;registration;&lt;/li&gt;
&lt;li&gt;password login;&lt;/li&gt;
&lt;li&gt;Google login;&lt;/li&gt;
&lt;li&gt;GitHub login;&lt;/li&gt;
&lt;li&gt;Facebook login.&lt;br&gt;
The social OAuth ticket exchange now creates the same server-side session used by password authentication and sets the secure cookie.&lt;br&gt;
During integration testing, we also corrected two existing issues.&lt;br&gt;
GitHub profile persistence&lt;br&gt;
An undefined nested GitHub public-profile snapshot could cause a Mongoose casting error.&lt;br&gt;
The new implementation updates nested data explicitly and writes the public snapshot only when a defined value is available.&lt;br&gt;
Facebook accounts without email&lt;br&gt;
Not every Facebook account exposes an email address.&lt;br&gt;
MyZubster already supported a deterministic private relay identity for this situation. The integration tests were aligned with that intended behavior instead of incorrectly requiring every account to provide an email.&lt;br&gt;
The relay value is treated as an internal identifier, not as a verified personal email.&lt;br&gt;
The account-security interface&lt;br&gt;
A new React page is available at:&lt;br&gt;
/account/security&lt;br&gt;
It displays:&lt;/li&gt;
&lt;li&gt;the authenticated account;&lt;/li&gt;
&lt;li&gt;all active devices;&lt;/li&gt;
&lt;li&gt;the current device;&lt;/li&gt;
&lt;li&gt;session creation time;&lt;/li&gt;
&lt;li&gt;last activity;&lt;/li&gt;
&lt;li&gt;session expiration;&lt;/li&gt;
&lt;li&gt;controls for remote revocation;&lt;/li&gt;
&lt;li&gt;current-session termination;&lt;/li&gt;
&lt;li&gt;logout.&lt;br&gt;
The page is linked from:&lt;/li&gt;
&lt;li&gt;the authenticated account panel on the MyZubster home interface;&lt;/li&gt;
&lt;li&gt;the Neon Plaza metaverse top bar.&lt;br&gt;
Frontend API module&lt;br&gt;
The React application uses a dedicated session API:&lt;br&gt;
getCurrentAccount();&lt;br&gt;
getAuthSessions();&lt;br&gt;
revokeAuthSession(sessionId);&lt;br&gt;
logoutCurrentSession();&lt;br&gt;
clearBrowserAuth();&lt;br&gt;
Requests include same-origin cookies:&lt;br&gt;
fetch(path, {&lt;br&gt;
credentials: &quot;same-origin&quot;,&lt;br&gt;
headers: {&lt;br&gt;
  ...bearerMigrationFallback&lt;br&gt;
}&lt;br&gt;
});&lt;br&gt;
Structured server errors are preserved so the UI can display a support request ID when an operation fails.&lt;br&gt;
Remote device revocation&lt;br&gt;
When the user revokes another device:&lt;ol&gt;
&lt;li&gt;the UI requests confirmation;&lt;/li&gt;
&lt;li&gt;a session-specific DELETE request is sent;&lt;/li&gt;
&lt;li&gt;the server verifies session ownership;&lt;/li&gt;
&lt;li&gt;the session receives a revocation timestamp;&lt;/li&gt;
&lt;li&gt;the frontend removes it from the active list;&lt;/li&gt;
&lt;li&gt;the revoked device is rejected on its next request.&lt;br&gt;
Current-device termination&lt;br&gt;
Terminating the current session:&lt;/li&gt;
&lt;li&gt;revokes the server-side session;&lt;/li&gt;
&lt;li&gt;clears the secure cookie;&lt;/li&gt;
&lt;li&gt;removes legacy browser credentials;&lt;/li&gt;
&lt;li&gt;redirects the user through authentication again.&lt;br&gt;
Legacy browser keys removed during logout include:&lt;br&gt;
myzubster-token&lt;br&gt;
token&lt;br&gt;
accessToken&lt;br&gt;
myzubster-user&lt;br&gt;
myzubster-identity-provider&lt;br&gt;
myzubster-metaverse-character-id&lt;br&gt;
Protected production routing&lt;br&gt;
The new account page is configured as an explicit Vercel SPA route.&lt;br&gt;
It includes:&lt;br&gt;
X-Robots-Tag: noindex, nofollow&lt;br&gt;
Account-security pages should not be indexed, cached as search snippets, or treated as public content.&lt;br&gt;
Both the root and standalone frontend Vercel configurations were updated and validated as JSON.&lt;br&gt;
Testing and verification&lt;br&gt;
Backend&lt;br&gt;
Five focused backend suites pass:&lt;br&gt;
authSessionService&lt;br&gt;
authMiddlewareSessions&lt;br&gt;
authSessionController&lt;br&gt;
socialAuthCallback&lt;br&gt;
socialIdentityService&lt;br&gt;
Results:&lt;br&gt;
5 suites passed&lt;br&gt;
24 tests passed&lt;br&gt;
Coverage includes:&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;persistent session creation;&lt;/li&gt;
&lt;li&gt;bearer and cookie extraction;&lt;/li&gt;
&lt;li&gt;active-session validation;&lt;/li&gt;
&lt;li&gt;revoked-session rejection;&lt;/li&gt;
&lt;li&gt;token-expiration handling;&lt;/li&gt;
&lt;li&gt;structured errors;&lt;/li&gt;
&lt;li&gt;session listing;&lt;/li&gt;
&lt;li&gt;remote revocation;&lt;/li&gt;
&lt;li&gt;valid logout;&lt;/li&gt;
&lt;li&gt;malformed-token logout;&lt;/li&gt;
&lt;li&gt;Google authentication;&lt;/li&gt;
&lt;li&gt;GitHub authentication;&lt;/li&gt;
&lt;li&gt;Facebook authentication;&lt;/li&gt;
&lt;li&gt;persistent metaverse characters.&lt;br&gt;
The social identity tests use MongoDB Memory Server so the persistence behavior is exercised rather than completely mocked.&lt;br&gt;
Frontend&lt;br&gt;
Three focused frontend suites pass:&lt;br&gt;
authSessions API&lt;br&gt;
AccountSecurityPage&lt;br&gt;
MetaversePage&lt;br&gt;
Results:&lt;br&gt;
3 suites passed&lt;br&gt;
11 tests passed&lt;br&gt;
The tests cover:&lt;/li&gt;
&lt;li&gt;same-origin credentials;&lt;/li&gt;
&lt;li&gt;bearer-token migration;&lt;/li&gt;
&lt;li&gt;safe session-ID encoding;&lt;/li&gt;
&lt;li&gt;structured server errors;&lt;/li&gt;
&lt;li&gt;local credential cleanup;&lt;/li&gt;
&lt;li&gt;account and session rendering;&lt;/li&gt;
&lt;li&gt;current-device labeling;&lt;/li&gt;
&lt;li&gt;remote revocation;&lt;/li&gt;
&lt;li&gt;removal of revoked devices;&lt;/li&gt;
&lt;li&gt;safe date formatting;&lt;/li&gt;
&lt;li&gt;existing Neon Plaza mission-state behavior.&lt;br&gt;
The React production bundle also compiles successfully.&lt;br&gt;
Repository-wide CI status&lt;br&gt;
The pull request remains a draft because repository-wide CI still contains failures outside this authentication change.&lt;br&gt;
A previous complete run reported:&lt;br&gt;
149 test suites passed&lt;br&gt;
34 test suites failed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;774 tests passed&lt;br&gt;
44 tests failed&lt;br&gt;
The sampled failures were primarily related to existing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Zorgax payment code;&lt;/li&gt;
&lt;li&gt;cultural modules;&lt;/li&gt;
&lt;li&gt;Kefir workflows;&lt;/li&gt;
&lt;li&gt;Marketplace contract assertions;&lt;/li&gt;
&lt;li&gt;realtime behavior;&lt;/li&gt;
&lt;li&gt;observability source assertions.&lt;br&gt;
The dependency audit also reports vulnerabilities from the unchanged lockfile. This authentication work does not modify package.json or package-lock.json.&lt;br&gt;
These failures should remain visible, but unrelated systems should not be rewritten inside an authentication PR without appropriate scope and review.&lt;br&gt;
What remains to be implemented&lt;br&gt;
This pull request does not complete MYZ-71.&lt;br&gt;
Rotating refresh tokens&lt;br&gt;
The final architecture should use:&lt;br&gt;
Short-lived access token&lt;pre&gt;&lt;code&gt;  +
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rotating refresh token&lt;br&gt;
The refresh token should:&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;be cryptographically random;&lt;/li&gt;
&lt;li&gt;be stored only as a hash;&lt;/li&gt;
&lt;li&gt;belong to a token family;&lt;/li&gt;
&lt;li&gt;be consumed exactly once;&lt;/li&gt;
&lt;li&gt;rotate after every refresh;&lt;/li&gt;
&lt;li&gt;detect reuse;&lt;/li&gt;
&lt;li&gt;revoke the family after suspected replay.&lt;br&gt;
A conditional database operation will be necessary to prevent two simultaneous requests from consuming the same token successfully.&lt;br&gt;
Passkeys and WebAuthn&lt;br&gt;
The planned API includes:&lt;br&gt;
POST /api/auth/start&lt;br&gt;
POST /api/auth/verify&lt;br&gt;
Passkey verification must validate:&lt;/li&gt;
&lt;li&gt;the challenge;&lt;/li&gt;
&lt;li&gt;origin;&lt;/li&gt;
&lt;li&gt;relying-party ID;&lt;/li&gt;
&lt;li&gt;credential ID;&lt;/li&gt;
&lt;li&gt;public-key signature;&lt;/li&gt;
&lt;li&gt;authenticator data;&lt;/li&gt;
&lt;li&gt;user presence;&lt;/li&gt;
&lt;li&gt;user verification;&lt;/li&gt;
&lt;li&gt;authenticator signature counters.&lt;br&gt;
The account UI will also need credential listing, naming and revocation.&lt;br&gt;
Magic-link authentication&lt;br&gt;
Magic links require:&lt;/li&gt;
&lt;li&gt;single-use random challenges;&lt;/li&gt;
&lt;li&gt;hashed challenge storage;&lt;/li&gt;
&lt;li&gt;short expiration;&lt;/li&gt;
&lt;li&gt;rate limiting;&lt;/li&gt;
&lt;li&gt;account-enumeration resistance;&lt;/li&gt;
&lt;li&gt;safe redirect validation;&lt;/li&gt;
&lt;li&gt;protection against email link scanners;&lt;/li&gt;
&lt;li&gt;invalidation after first use.&lt;br&gt;
Step-up authentication&lt;br&gt;
Sensitive operations should require recent stronger authentication.&lt;br&gt;
Examples include:&lt;/li&gt;
&lt;li&gt;changing an email address;&lt;/li&gt;
&lt;li&gt;changing payment information;&lt;/li&gt;
&lt;li&gt;adding or removing passkeys;&lt;/li&gt;
&lt;li&gt;exporting sensitive data;&lt;/li&gt;
&lt;li&gt;changing privileges;&lt;/li&gt;
&lt;li&gt;deleting an account.&lt;br&gt;
The server will need to distinguish between a valid session and a recently re-verified session.&lt;br&gt;
CSRF protection&lt;br&gt;
SameSite=Lax is a useful baseline, but it is not the complete strategy for a cookie-authenticated application.&lt;br&gt;
Before the system becomes cookie-only, state-changing requests should add explicit protection through techniques such as:&lt;/li&gt;
&lt;li&gt;origin validation;&lt;/li&gt;
&lt;li&gt;synchronizer tokens;&lt;/li&gt;
&lt;li&gt;signed double-submit cookies;&lt;/li&gt;
&lt;li&gt;required custom headers.&lt;br&gt;
Removing browser-readable tokens&lt;br&gt;
Bearer compatibility and local-storage cleanup exist for migration purposes.&lt;br&gt;
The intended final browser architecture is:&lt;br&gt;
Credentials:&lt;br&gt;
HttpOnly secure cookies&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Frontend JavaScript:&lt;br&gt;
  no direct access to reusable authentication secrets&lt;br&gt;
Once the migration period ends, MyZubster should stop returning long-lived authentication tokens to frontend JavaScript.&lt;br&gt;
Production validation&lt;br&gt;
The following checks remain:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;secure cookie verification on the public HTTPS domain;&lt;/li&gt;
&lt;li&gt;remote revocation between two physical devices;&lt;/li&gt;
&lt;li&gt;GitHub OAuth callback verification in production;&lt;/li&gt;
&lt;li&gt;Google and Facebook regression checks;&lt;/li&gt;
&lt;li&gt;MongoDB TTL-index verification;&lt;/li&gt;
&lt;li&gt;investigation of intermittent Atlas ReplicaSetNoPrimary events;&lt;/li&gt;
&lt;li&gt;strict-session migration testing;&lt;/li&gt;
&lt;li&gt;request-ID propagation through production logs and proxies.&lt;br&gt;
Why this is important for the metaverse&lt;br&gt;
Authentication is not separate from the metaverse. It is the foundation of persistent identity.&lt;br&gt;
The MyZubster identity chain is becoming:&lt;br&gt;
MyZubster account&lt;br&gt;
↓&lt;br&gt;
Revocable server session&lt;br&gt;
↓&lt;br&gt;
Verified character&lt;br&gt;
↓&lt;br&gt;
Neon Plaza participation&lt;br&gt;
↓&lt;br&gt;
Missions and contributions&lt;br&gt;
↓&lt;br&gt;
Knowledge, marketplace and community activity&lt;br&gt;
If a token is stolen, revocation must also protect the connected metaverse character and its permissions.&lt;br&gt;
This session infrastructure prepares the platform for:&lt;/li&gt;
&lt;li&gt;private metaverse rooms;&lt;/li&gt;
&lt;li&gt;verified contributors;&lt;/li&gt;
&lt;li&gt;creator permissions;&lt;/li&gt;
&lt;li&gt;moderation roles;&lt;/li&gt;
&lt;li&gt;university pilots;&lt;/li&gt;
&lt;li&gt;community governance;&lt;/li&gt;
&lt;li&gt;marketplace actions;&lt;/li&gt;
&lt;li&gt;development requests;&lt;/li&gt;
&lt;li&gt;contribution evidence.&lt;br&gt;
Current status&lt;br&gt;
Implemented:&lt;/li&gt;
&lt;li&gt;persistent authentication sessions;&lt;/li&gt;
&lt;li&gt;JWT-to-session binding;&lt;/li&gt;
&lt;li&gt;secure cookies;&lt;/li&gt;
&lt;li&gt;bearer-token migration;&lt;/li&gt;
&lt;li&gt;session expiration;&lt;/li&gt;
&lt;li&gt;device listing;&lt;/li&gt;
&lt;li&gt;remote revocation;&lt;/li&gt;
&lt;li&gt;server-side logout;&lt;/li&gt;
&lt;li&gt;privacy-preserving IP hashing;&lt;/li&gt;
&lt;li&gt;structured errors and request IDs;&lt;/li&gt;
&lt;li&gt;social-login integration;&lt;/li&gt;
&lt;li&gt;account-security UI;&lt;/li&gt;
&lt;li&gt;Neon Plaza integration;&lt;/li&gt;
&lt;li&gt;protected Vercel routing;&lt;/li&gt;
&lt;li&gt;focused backend and frontend tests.&lt;br&gt;
Still required:&lt;/li&gt;
&lt;li&gt;refresh-token rotation;&lt;/li&gt;
&lt;li&gt;replay detection;&lt;/li&gt;
&lt;li&gt;passkeys;&lt;/li&gt;
&lt;li&gt;magic links;&lt;/li&gt;
&lt;li&gt;step-up authentication;&lt;/li&gt;
&lt;li&gt;CSRF hardening;&lt;/li&gt;
&lt;li&gt;removal of browser-readable credentials;&lt;/li&gt;
&lt;li&gt;production OAuth verification;&lt;/li&gt;
&lt;li&gt;Atlas stability verification;&lt;/li&gt;
&lt;li&gt;repository-wide CI cleanup.&lt;br&gt;
This is an important security milestone, but it is not a declaration that authentication is finished.&lt;br&gt;
The goal is to make every security claim testable, observable and reversible during rollout.&lt;br&gt;
In MyZubster, trust should not depend on promises. It should be built through verifiable behavior.&lt;br&gt;
Suggested Coder Legion tags:&lt;br&gt;
Cybersecurity&lt;br&gt;
Node.js&lt;br&gt;
React&lt;br&gt;
Web Development&lt;br&gt;
Authentication&lt;br&gt;
Metaverse&lt;br&gt;
Suggested excerpt:&lt;br&gt;
How MyZubster is moving from stateless JWT authentication to persistent, revocable sessions with secure cookies, device management, structured errors, social-login integration and a React account-security dashboard.&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28825/from-stateless-jwts-to-revocable-sessions-securing-the-myzubster-metaverse</guid>
<pubDate>Sun, 27 Sep 2026 18:29:37 +0000</pubDate>
</item>
<item>
<title>One Design System, Two Runtimes: Sharing Contracts Between React and React Native</title>
<link>https://coderlegion.com/28700/one-design-system-two-runtimes-sharing-contracts-between-react-and-react-native</link>
<description>&lt;p&gt;A cross-platform design system sounds simple until the first real component forces the platforms apart.&lt;/p&gt;
&lt;p&gt;React and React Native can expose the same product concept — a button, checkbox, input, modal — while relying on very different runtime primitives. The web has DOM attributes, anchors, CSS, hover and keyboard behavior. React Native has &lt;code&gt;Pressable&lt;/code&gt;, native accessibility props, gesture events, and a completely different styling model.&lt;/p&gt;
&lt;p&gt;Trying to hide all of those differences behind one implementation usually makes the API worse. In Vellira, I have been taking a different approach: &lt;strong&gt;share the semantic contract, not the runtime implementation&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;That distinction has become one of the most useful architectural rules in the project.&lt;/p&gt;
&lt;h3 id=&quot;the-real-goal-is-consistency-not-identical-code&quot;&gt;The real goal is consistency, not identical code&lt;/h3&gt;
&lt;p&gt;When I started building Vellira, it was tempting to think about cross-platform components mainly as a code-sharing problem.&lt;/p&gt;
&lt;p&gt;If a web button and a native button look similar, why not make them use as much of the same component code as possible?&lt;/p&gt;
&lt;p&gt;Because the important thing users consume is not the internal component tree. It is the behavior and mental model of the public API.&lt;/p&gt;
&lt;p&gt;For a button, developers should be able to learn concepts such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;color&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;appearance&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;size&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shape&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;loading&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;disabled&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fullWidth&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;iconOnly&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;once and carry that knowledge between platforms.&lt;/p&gt;
&lt;p&gt;The implementation underneath those concepts does not need to be the same.&lt;/p&gt;
&lt;p&gt;This leads to a more useful definition of parity:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Cross-platform parity means shared semantics where the platforms agree, plus explicit platform-specific capabilities where they do not.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That is much stronger than forcing two different runtimes through the same abstraction.&lt;/p&gt;
&lt;h3 id=&quot;put-shared-semantics-in-a-platform-neutral-contract&quot;&gt;Put shared semantics in a platform-neutral contract&lt;/h3&gt;
&lt;p&gt;Vellira keeps the common Button vocabulary in &lt;code&gt;@vellira-ui/types&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A simplified view of the shared contract looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export type ButtonSize = 'sm' | 'md' | 'lg';

export type ButtonColor =
  | 'primary'
  | 'neutral'
  | 'success'
  | 'warning'
  | 'danger';

export type ButtonAppearance =
  | 'solid'
  | 'outline'
  | 'ghost'
  | 'soft'
  | 'link';

export interface BaseButtonProps {
  color?: ButtonColor;
  appearance?: ButtonAppearance;
  size?: ButtonSize;
  shape?: 'square' | 'rounded' | 'pill';
  fullWidth?: boolean;
  loading?: boolean;
  loadingText?: string;
  disabled?: boolean;
  iconOnly?: boolean;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is nothing web-specific or native-specific in that interface. It describes what a Vellira button &lt;em&gt;means&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;That shared layer gives both implementations a stable semantic center. If &lt;code&gt;appearance=&quot;soft&quot;&lt;/code&gt; exists on both platforms, it should represent the same design intent. If &lt;code&gt;loading&lt;/code&gt; disables interaction, that rule should be understood consistently too.&lt;/p&gt;
&lt;p&gt;This is where I want reuse to be strongest.&lt;/p&gt;
&lt;h3 id=&quot;let-each-platform-extend-the-contract-honestly&quot;&gt;Let each platform extend the contract honestly&lt;/h3&gt;
&lt;p&gt;Once the common semantics are defined, the platform packages can extend them instead of pretending their runtimes are identical.&lt;/p&gt;
&lt;p&gt;On the web, &lt;code&gt;ButtonProps&lt;/code&gt; extends the shared contract and normal HTML button behavior. It can also expose anchor-related properties such as &lt;code&gt;href&lt;/code&gt;, &lt;code&gt;target&lt;/code&gt;, and &lt;code&gt;rel&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export interface ButtonProps
  extends
    BaseButtonProps,
    Omit&amp;lt;ButtonHTMLAttributes&amp;lt;HTMLButtonElement&amp;gt;, 'color'&amp;gt;,
    Pick&amp;lt;
      AnchorHTMLAttributes&amp;lt;HTMLAnchorElement&amp;gt;,
      'href' | 'target' | 'rel' | 'download'
    &amp;gt; {
  children?: ReactNode;
  iconStart?: ReactNode;
  iconEnd?: ReactNode;
  tooltip?: string;
  badge?: ReactNode;
  shortcut?: ReactNode;
  asChild?: boolean;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;React Native starts from the same &lt;code&gt;BaseButtonProps&lt;/code&gt;, but the runtime-specific surface is different. It builds on &lt;code&gt;PressableProps&lt;/code&gt;, exposes &lt;code&gt;onPress&lt;/code&gt;, native styles, and accessibility properties that belong to the native platform.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export interface ButtonProps
  extends BaseButtonProps, Omit&amp;lt;PressableProps, /* platform exclusions */&amp;gt; {
  children?: ReactNode;
  iconStart?: ButtonIconElement;
  iconEnd?: ButtonIconElement;
  iconSize?: number;
  onPress?: (event: GestureResponderEvent) =&amp;gt; void;
  style?: StyleProp&amp;lt;ViewStyle&amp;gt;;
  textStyle?: StyleProp&amp;lt;TextStyle&amp;gt;;
  accessibilityLabel?: string;
  testID?: string;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The two APIs are clearly related, but they are not artificially identical.&lt;/p&gt;
&lt;p&gt;That is intentional.&lt;/p&gt;
&lt;p&gt;A web developer still gets normal web capabilities. A React Native developer still gets normal native capabilities. The design system owns the common semantics without becoming an abstraction tax on either platform.&lt;/p&gt;
&lt;h3 id=&quot;healthy-divergence-is-part-of-a-good-cross-platform-api&quot;&gt;Healthy divergence is part of a good cross-platform API&lt;/h3&gt;
&lt;p&gt;Some differences should not be normalized away.&lt;/p&gt;
&lt;p&gt;For example, a web button can naturally compose with an anchor or another DOM element. That is where properties such as &lt;code&gt;href&lt;/code&gt; or Vellira's &lt;code&gt;asChild&lt;/code&gt; capability make sense.&lt;/p&gt;
&lt;p&gt;React Native does not have an HTML anchor element. Inventing a fake &lt;code&gt;href&lt;/code&gt; prop just to make the type signatures look more symmetrical would create parity on paper while making the native API less natural.&lt;/p&gt;
&lt;p&gt;The same rule applies in the opposite direction. React Native has concepts such as &lt;code&gt;StyleProp&amp;lt;ViewStyle&amp;gt;&lt;/code&gt;, &lt;code&gt;testID&lt;/code&gt;, and native press events. Wrapping every one of those in invented cross-platform names would make the system harder to understand and harder to debug.&lt;/p&gt;
&lt;p&gt;So I use a simple test when deciding whether something belongs in the shared contract:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Does this property describe product-level component behavior, or does it describe the runtime that implements it?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Product-level behavior is a strong candidate for the shared layer. Runtime behavior usually stays with the platform package.&lt;/p&gt;
&lt;p&gt;That distinction keeps the common API small enough to remain meaningful.&lt;/p&gt;
&lt;h3 id=&quot;share-design-decisions-separately-from-component-implementations&quot;&gt;Share design decisions separately from component implementations&lt;/h3&gt;
&lt;p&gt;Component props are only one layer of cross-platform consistency.&lt;/p&gt;
&lt;p&gt;Visual decisions such as spacing, radii, typography scales, colors, and semantic states also need a common source of truth. Those decisions can be represented as tokens and consumed differently by CSS on the web and native style objects in React Native.&lt;/p&gt;
&lt;p&gt;The important architectural point is the same: &lt;strong&gt;share the decision, adapt the execution&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;A radius token does not require the web and native styling engines to work the same way. It only requires both implementations to resolve the same design intent.&lt;/p&gt;
&lt;p&gt;This is a useful pattern because it prevents &quot;cross-platform&quot; from turning into &quot;lowest common denominator&quot;. Shared semantics stay centralized while each renderer remains free to use the platform correctly.&lt;/p&gt;
&lt;h3 id=&quot;test-parity-at-the-contract-boundary&quot;&gt;Test parity at the contract boundary&lt;/h3&gt;
&lt;p&gt;Once implementations are separate, drift becomes the next problem.&lt;/p&gt;
&lt;p&gt;It is easy for the web Button to gain a new appearance or loading rule while the React Native Button silently falls behind. Separate implementations therefore need explicit validation around the things that are supposed to stay aligned.&lt;/p&gt;
&lt;p&gt;In Vellira, both Button implementations have their own tests and Storybook coverage. The shared types define a common vocabulary, while platform tests verify actual runtime behavior.&lt;/p&gt;
&lt;p&gt;That gives two different kinds of confidence:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Contract confidence&lt;/strong&gt; — the shared semantic API is defined in one place.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Runtime confidence&lt;/strong&gt; — each implementation proves that the contract behaves correctly on its own platform.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I prefer this over a large shared implementation with a growing number of platform conditionals. A shared file can look beautifully DRY while still hiding platform-specific bugs.&lt;/p&gt;
&lt;p&gt;Separate runtime tests make those differences visible.&lt;/p&gt;
&lt;h3 id=&quot;a-practical-three-layer-model&quot;&gt;A practical three-layer model&lt;/h3&gt;
&lt;p&gt;The pattern I am converging on in Vellira can be summarized as three layers.&lt;/p&gt;
&lt;h4 id=&quot;1-shared-semantics&quot;&gt;1. Shared semantics&lt;/h4&gt;
&lt;p&gt;Define the concepts developers should learn once:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;size
appearance
color
loading
disabled
shape
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These belong in platform-neutral types and design contracts.&lt;/p&gt;
&lt;h4 id=&quot;2-platform-api&quot;&gt;2. Platform API&lt;/h4&gt;
&lt;p&gt;Extend those concepts with capabilities developers expect from the runtime:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Web:          href, target, DOM attributes, asChild
React Native: onPress, accessibilityLabel, style, testID
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do not disguise real platform differences.&lt;/p&gt;
&lt;h4 id=&quot;3-platform-implementation&quot;&gt;3. Platform implementation&lt;/h4&gt;
&lt;p&gt;Use the native tools of each runtime:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Web:          DOM + CSS
React Native: Pressable + native styles
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The implementation can diverge significantly as long as the shared semantics remain trustworthy.&lt;/p&gt;
&lt;h3 id=&quot;what-this-changes-when-designing-new-components&quot;&gt;What this changes when designing new components&lt;/h3&gt;
&lt;p&gt;This architecture also changes how I think about adding components.&lt;/p&gt;
&lt;p&gt;Instead of beginning with &quot;How can I reuse the most code?&quot;, I begin with questions like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What behavior is actually common to both platforms?&lt;/li&gt;
&lt;li&gt;Which names should mean the same thing everywhere?&lt;/li&gt;
&lt;li&gt;Which states must have equivalent semantics?&lt;/li&gt;
&lt;li&gt;Which capabilities are genuinely platform-specific?&lt;/li&gt;
&lt;li&gt;What should be validated so parity cannot drift accidentally?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Only after those questions are answered does implementation reuse become interesting.&lt;/p&gt;
&lt;p&gt;Sometimes there will be useful shared code below the component layer. Sometimes the correct answer is two mostly independent implementations. Both are acceptable if the public contract stays coherent.&lt;/p&gt;
&lt;p&gt;This makes the design system easier to evolve because platform differences are treated as architecture inputs, not inconvenient exceptions.&lt;/p&gt;
&lt;h3 id=&quot;the-rule-i-keep-coming-back-to&quot;&gt;The rule I keep coming back to&lt;/h3&gt;
&lt;p&gt;The most useful lesson so far is that cross-platform design systems do not need maximum code sharing. They need &lt;strong&gt;maximum semantic clarity&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Developers should feel that the React and React Native packages belong to the same system. They should recognize the vocabulary, states, design decisions, and interaction intent. But they should not have to give up the strengths of their platform to get that consistency.&lt;/p&gt;
&lt;p&gt;For Vellira, that means sharing contracts aggressively and implementations selectively.&lt;/p&gt;
&lt;p&gt;It is a smaller abstraction, but a much more durable one.&lt;/p&gt;
&lt;p&gt;You can explore the project in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.vellira.dev&quot;&gt;Vellira documentation&lt;/a&gt; or browse the implementation on &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/vellira-dev/vellira&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28700/one-design-system-two-runtimes-sharing-contracts-between-react-and-react-native</guid>
<pubDate>Sat, 26 Sep 2026 09:42:11 +0000</pubDate>
</item>
<item>
<title>Stop Wasting Days on Dev Setup: How We Automated Our Microservices Environment for Every Developer</title>
<link>https://coderlegion.com/28399/stop-wasting-days-dev-setup-how-automated-our-microservices-environment-every-developer</link>
<description>&lt;h2 id=&quot;stop-wasting-days-on-dev-setup-how-we-automated-our-microservices-environment-for-every-developer&quot;&gt;Stop Wasting Days on Dev Setup: How We Automated Our Microservices Environment for Every Developer&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;How we reduced new developer onboarding from 3 days to 15 minutes and eliminated configuration drift across 12 microservices.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-problem-that-made-us-snap&quot;&gt;The Problem That Made Us Snap&lt;/h3&gt;
&lt;p&gt;Picture this: A new senior developer joins your team. They're excited, ready to ship code on day one. Then reality hits.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Day 1:&lt;/strong&gt; They spend 6 hours installing Node, Docker, configuring ESLint to match the team's style, figuring out which environment variables go where, and asking Slack questions like &lt;em&gt;&quot;which version of NestJS are we on again?&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Day 2:&lt;/strong&gt; They finally get &lt;code&gt;user-service&lt;/code&gt; running, but &lt;code&gt;auth-service&lt;/code&gt; throws a CORS error because their local config is slightly different from everyone else's. They spend the afternoon comparing &lt;code&gt;.env&lt;/code&gt; files with a teammate.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Day 3:&lt;/strong&gt; They submit their first PR. The CI pipeline fails because their local Prettier config formats code differently from the shared config that was updated last month — but nobody told them.&lt;/p&gt;
&lt;p&gt;Sound familiar?&lt;/p&gt;
&lt;p&gt;We had &lt;strong&gt;12 microservices&lt;/strong&gt;, a team that was growing fast, and a configuration mess that was eating our productivity alive. Every service had slightly different ESLint rules, Docker setups, folder structures, and VS Code settings. Senior developers were spending hours every week answering setup questions instead of building features.&lt;/p&gt;
&lt;p&gt;So we built a system that fixed all of it. Here's exactly how.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-solution-one-repo-to-rule-them-all&quot;&gt;The Solution: One Repo to Rule Them All&lt;/h3&gt;
&lt;p&gt;We created a &lt;strong&gt;central configuration repository&lt;/strong&gt; called &lt;code&gt;dev-environment-config&lt;/code&gt; that acts as the single source of truth for every development environment across our entire microservices ecosystem.&lt;/p&gt;
&lt;p&gt;The idea is simple:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;One repository controls the setup, structure, linting, Docker config, VS Code settings, and shared code for every service. When a senior developer updates it, every developer's machine automatically syncs the next morning.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;the-architecture-at-a-glance&quot;&gt;The Architecture at a Glance&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;dev-environment-config (Central Repo)
├── templates/          → Project scaffolding (NestJS, React)
├── shared/             → ESLint, Prettier, Git configs
├── vscode/             → Extensions &amp;amp; settings
├── docker/             → Base Docker configs
├── scripts/            → Automation (setup, sync, update)
├── common-lib/         → Shared NPM package
└── manifest.json       → Version tracking

         │  syncs to
         ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ user-svc │ │ auth-svc │ │ pay-svc  │  ... 12 services
│ .eslint  │ │ .eslint  │ │ .eslint  │
│ .vscode  │ │ .vscode  │ │ .vscode  │
│ Docker   │ │ Docker   │ │ Docker   │
└──────────┘ └──────────┘ └──────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;implementation-step-by-step&quot;&gt;Implementation: Step by Step&lt;/h3&gt;
&lt;h4 id=&quot;step-1-the-central-config-repository&quot;&gt;Step 1: The Central Config Repository&lt;/h4&gt;
&lt;p&gt;We created a new repository with this structure:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dev-environment-config/
├── templates/
│   ├── nestjs-service/    # Complete NestJS starter
│   └── reactjs-service/   # Complete React starter
├── shared/
│   ├── .eslintrc.js
│   ├── .prettierrc
│   ├── .editorconfig
│   ├── .gitignore
│   └── commitlint.config.js
├── vscode/
│   ├── extensions.json
│   ├── settings.json
│   └── launch.json
├── scripts/
│   ├── setup-new-developer.sh
│   ├── sync-config.sh
│   ├── auto-update.sh
│   └── create-new-service.sh
├── common-lib/            # Shared NPM package
└── manifest.json          # Version tracker
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;manifest.json&lt;/code&gt; is the heartbeat of the system:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;version&quot;: &quot;1.2.0&quot;,
  &quot;lastUpdated&quot;: &quot;2024-01-15T10:00:00Z&quot;,
  &quot;updatedBy&quot;: &quot;senior-dev&quot;,
  &quot;configs&quot;: {
    &quot;eslint&quot;: &quot;3.2.0&quot;,
    &quot;prettier&quot;: &quot;2.1.0&quot;,
    &quot;docker&quot;: &quot;1.5.0&quot;
  },
  &quot;services&quot;: [
    &quot;user-service&quot;,
    &quot;auth-service&quot;,
    &quot;payment-service&quot;
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every time a config changes, we bump the version. This is how the auto-update system knows something changed.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-2-the-one-command-onboarding-script&quot;&gt;Step 2: The One-Command Onboarding Script&lt;/h4&gt;
&lt;p&gt;This was the game-changer. We wrote a bash script that a new developer runs &lt;strong&gt;once&lt;/strong&gt;, and their entire environment is ready.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The new developer's experience:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install VS Code ✅&lt;/li&gt;
&lt;li&gt;Run one command:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -sSL https://raw.githubusercontent.com/company/\
dev-environment-config/main/scripts/\
setup-new-developer.sh | bash
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Wait ~10 minutes. Done.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;What the script does under the hood:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
set -e

WORKSPACE_DIR=&quot;$HOME/company-workspace&quot;
CONFIG_REPO=&quot;https://github.com/company/dev-environment-config.git&quot;

echo &quot;🚀 Setting up your development environment...&quot;

# 1. Check &amp;amp; install prerequisites
check_and_install &quot;git&quot; &quot;Git&quot;
check_and_install &quot;node&quot; &quot;Node.js&quot; &quot;nvm install 20&quot;
check_and_install &quot;docker&quot; &quot;Docker&quot;

# 2. Create workspace &amp;amp; clone everything
mkdir -p &quot;$WORKSPACE_DIR&quot; &amp;amp;&amp;amp; cd &quot;$WORKSPACE_DIR&quot;
git clone &quot;$CONFIG_REPO&quot;

# 3. Clone all service repos from manifest
SERVICES=$(cat dev-environment-config/manifest.json | \
  python3 -c &quot;import sys,json; \
  [print(s) for s in json.load(sys.stdin)['services']]&quot;)

for service in $SERVICES; do
  git clone &quot;https://github.com/company/${service}.git&quot;
done

# 4. Install VS Code extensions
EXTENSIONS=$(cat dev-environment-config/vscode/extensions.json | \
  python3 -c &quot;import sys,json; \
  [print(e) for e in json.load(sys.stdin)['recommendations']]&quot;)

for ext in $EXTENSIONS; do
  code --install-extension &quot;$ext&quot; --force
done

# 5. Sync shared configs to all services
bash dev-environment-config/scripts/sync-config.sh

# 6. Install dependencies everywhere
for service in $SERVICES; do
  cd &quot;$service&quot; &amp;amp;&amp;amp; npm install &amp;amp;&amp;amp; cp .env.example .env &amp;amp;&amp;amp; cd ..
done

# 7. Set up daily auto-update cron (8 AM weekdays)
CRON=&quot;0 8 * * 1-5 cd $WORKSPACE_DIR &amp;amp;&amp;amp; \
  bash dev-environment-config/scripts/auto-update.sh&quot;
(crontab -l 2&amp;gt;/dev/null; echo &quot;$CRON&quot;) | crontab -

echo &quot;✅ Setup complete! Open VS Code: code $WORKSPACE_DIR&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's it. The developer opens VS Code, and everything is configured — extensions, linting, formatting, Docker, environment variables, the works.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-3-the-config-sync-script&quot;&gt;Step 3: The Config Sync Script&lt;/h4&gt;
&lt;p&gt;This script copies the shared configs from the central repo into every service:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
# sync-config.sh

CONFIG_ROOT=&quot;$(dirname &quot;$SCRIPT_DIR&quot;)&quot;
WORKSPACE_DIR=&quot;$(dirname &quot;$CONFIG_ROOT&quot;)&quot;

for service in $SERVICES; do
  SERVICE_DIR=&quot;$WORKSPACE_DIR/$service&quot;

  # Copy shared configs
  cp -f &quot;$CONFIG_ROOT/shared/.eslintrc.js&quot; &quot;$SERVICE_DIR/&quot;
  cp -f &quot;$CONFIG_ROOT/shared/.prettierrc&quot; &quot;$SERVICE_DIR/&quot;
  cp -f &quot;$CONFIG_ROOT/shared/.editorconfig&quot; &quot;$SERVICE_DIR/&quot;
  cp -f &quot;$CONFIG_ROOT/shared/.gitignore&quot; &quot;$SERVICE_DIR/&quot;

  # Copy VS Code settings
  mkdir -p &quot;$SERVICE_DIR/.vscode&quot;
  cp -f &quot;$CONFIG_ROOT/vscode/settings.json&quot; &quot;$SERVICE_DIR/.vscode/&quot;
  cp -f &quot;$CONFIG_ROOT/vscode/extensions.json&quot; &quot;$SERVICE_DIR/.vscode/&quot;

  echo &quot;✅ $service synced&quot;
done
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-4-the-daily-auto-update-the-magic-&quot;&gt;Step 4: The Daily Auto-Update (The Magic)&lt;/h4&gt;
&lt;p&gt;This is the part that keeps everything in sync without anyone thinking about it.&lt;/p&gt;
&lt;p&gt;Every morning at 8 AM (weekdays), a cron job runs &lt;code&gt;auto-update.sh&lt;/code&gt; on every developer's machine:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
# auto-update.sh — Runs daily at 8 AM

cd &quot;$CONFIG_ROOT&quot;

# 1. Check for config updates
OLD_VERSION=$(cat manifest.json | jq -r '.version')
git pull origin main
NEW_VERSION=$(cat manifest.json | jq -r '.version')

if [ &quot;$OLD_VERSION&quot; != &quot;$NEW_VERSION&quot; ]; then
  echo &quot;🆕 Config updated: $OLD_VERSION → $NEW_VERSION&quot;
  bash &quot;$SCRIPT_DIR/sync-config.sh&quot;
fi

# 2. Pull latest code for all services
for service in $SERVICES; do
  cd &quot;$WORKSPACE_DIR/$service&quot;

  # Stash local changes safely
  git stash push -m &quot;auto-update-$(date +%Y%m%d)&quot; 2&amp;gt;/dev/null

  # Pull latest
  git pull origin $(git branch --show-current) --rebase

  # Update dependencies if package.json changed
  if git diff HEAD~1 --name-only | grep -q &quot;package.json&quot;; then
    npm install
  fi

  # Update shared library
  npm update @company/common-lib
done

# 3. Notify the developer
notify-send &quot;Dev Environment&quot; &quot;✅ Updated to config v$NEW_VERSION&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;The flow:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Senior Dev pushes config change
         │
         ▼
   GitHub Actions
         │
    ┌────┴────┐
    │ Slack   │ Notify team
    │ Publish │ common-lib to NPM
    └────┬────┘
         │
    Next 8 AM
         │
         ▼
  Every developer's cron runs
         │
    ┌────┴────────────────┐
    │ Pull config repo    │
    │ Sync shared configs │
    │ Pull service repos  │
    │ Update dependencies │
    │ Desktop notification│
    └─────────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-5-the-shared-common-library&quot;&gt;Step 5: The Shared Common Library&lt;/h4&gt;
&lt;p&gt;Instead of copying and pasting guards, interceptors, and DTOs across 12 services, we created &lt;code&gt;@company/common-lib&lt;/code&gt; — a private NPM package:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// In any service, just import and use:
import {
  AuthGuard,
  RolesGuard,
  ResponseInterceptor,
  LoggingInterceptor,
  HttpExceptionFilter,
  PaginationDto,
  McpService,        // Qwen MCP integration
} from '@company/common-lib';

@Controller('users')
@UseGuards(AuthGuard, RolesGuard)
@UseInterceptors(ResponseInterceptor)
@UseFilters(HttpExceptionFilter)
export class UserController {
  constructor(private mcpService: McpService) {}

  @Get()
  findAll(@Query() pagination: PaginationDto) {
    return this.userService.findAll(pagination);
  }

  @Post('analyze')
  async analyze(@Body() dto: AnalyzeDto) {
    return this.mcpService.callTool('text-analysis', {
      text: dto.text,
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When we update the common library (add a new guard, fix a bug in the exception filter), the auto-update system bumps the version in every service automatically.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-6-service-scaffolding&quot;&gt;Step 6: Service Scaffolding&lt;/h4&gt;
&lt;p&gt;Creating a new microservice used to take a day of copy-pasting and tweaking. Now it takes 30 seconds:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bash scripts/create-new-service.sh

# Prompts:
# Service name: order-service
# Description: Handles order processing
# Port: 3002
# Type: nestjs

# ✅ Done! Complete service with:
# - src/common/ (filters, guards, interceptors, DTOs)
# - Docker setup (Dockerfile + docker-compose)
# - VS Code settings
# - ESLint + Prettier
# - Health check endpoint
# - Swagger docs
# - MCP integration
# - Auto-update scripts
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-benefits-with-real-numbers-&quot;&gt;The Benefits (With Real Numbers)&lt;/h3&gt;
&lt;h4 id=&quot;1-onboarding-3-days-15-minutes&quot;&gt;1. Onboarding: 3 Days → 15 Minutes&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Before &lt;/th&gt;&lt;th&gt;After&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Manual setup following a 40-step Confluence page &lt;/td&gt;&lt;td&gt;One &lt;code&gt;curl&lt;/code&gt; command&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3 days to first productive commit &lt;/td&gt;&lt;td&gt;15 minutes to running all services&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&quot;Hey, can you help me with my env vars?&quot; (daily Slack noise) &lt;/td&gt;&lt;td&gt;Zero setup questions&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&quot;2-configuration-drift-eliminated&quot;&gt;2. Configuration Drift: Eliminated&lt;/h4&gt;
&lt;p&gt;Before, every service slowly diverged. &lt;code&gt;user-service&lt;/code&gt; had ESLint v8, &lt;code&gt;auth-service&lt;/code&gt; had v7. Prettier configs had subtle differences. Docker setups were copy-pasted and never updated.&lt;/p&gt;
&lt;p&gt;Now, &lt;strong&gt;every service has identical configs&lt;/strong&gt;, and they stay identical because the sync script overwrites them daily.&lt;/p&gt;
&lt;h4 id=&quot;3-senior-developer-time-saved-5-hours-week&quot;&gt;3. Senior Developer Time: Saved ~5 Hours/Week&lt;/h4&gt;
&lt;p&gt;Senior devs were spending roughly an hour a day helping juniors with setup issues, reviewing PRs that failed CI due to formatting, and manually updating configs across services.&lt;/p&gt;
&lt;p&gt;That time is now spent building features.&lt;/p&gt;
&lt;h4 id=&quot;4-consistent-code-quality&quot;&gt;4. Consistent Code Quality&lt;/h4&gt;
&lt;p&gt;Every developer has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The same ESLint rules (auto-fixed on save)&lt;/li&gt;
&lt;li&gt;The same Prettier config (format on save)&lt;/li&gt;
&lt;li&gt;The same commit message format (enforced by Husky)&lt;/li&gt;
&lt;li&gt;The same VS Code extensions (auto-installed)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;PR reviews went from &lt;em&gt;&quot;please fix your formatting&quot;&lt;/em&gt; to &lt;em&gt;&quot;let's discuss the architecture.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;h4 id=&quot;5-zero-effort-config-updates&quot;&gt;5. Zero-Effort Config Updates&lt;/h4&gt;
&lt;p&gt;When a senior developer wants to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add a new ESLint rule → Push to config repo → Everyone gets it tomorrow&lt;/li&gt;
&lt;li&gt;Update Docker base image → Push → Auto-synced&lt;/li&gt;
&lt;li&gt;Add a VS Code extension → Push → Auto-installed&lt;/li&gt;
&lt;li&gt;Fix a bug in the exception filter → Publish common-lib → Auto-updated&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No Slack announcements needed. No &quot;please run &lt;code&gt;npm install&lt;/code&gt;&quot; messages. It just happens.&lt;/p&gt;
&lt;h4 id=&quot;6-standardized-project-structure&quot;&gt;6. Standardized Project Structure&lt;/h4&gt;
&lt;p&gt;Every service looks the same. A developer can jump from &lt;code&gt;user-service&lt;/code&gt; to &lt;code&gt;payment-service&lt;/code&gt; and immediately know where everything is. The &lt;code&gt;src/common/&lt;/code&gt; folder has the same patterns. The Docker setup is identical. The health check endpoint is at the same path.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-we-learned&quot;&gt;What We Learned&lt;/h3&gt;
&lt;h4 id=&quot;start-small&quot;&gt;Start Small&lt;/h4&gt;
&lt;p&gt;We didn't build this all at once. We started with just syncing ESLint and Prettier configs. Once the team saw the value, we added Docker, VS Code settings, the common library, and the auto-update system incrementally.&lt;/p&gt;
&lt;h4 id=&quot;don-t-force-everything&quot;&gt;Don't Force Everything&lt;/h4&gt;
&lt;p&gt;We allow service-specific overrides. If &lt;code&gt;payment-service&lt;/code&gt; needs a special ESLint rule, they can add a &lt;code&gt;.eslintrc.local.js&lt;/code&gt; that extends the shared one. The shared config is the baseline, not a straitjacket.&lt;/p&gt;
&lt;h4 id=&quot;notifications-matter&quot;&gt;Notifications Matter&lt;/h4&gt;
&lt;p&gt;The desktop notification (&quot;✅ Updated to config v1.2.0&quot;) was a small touch, but it made developers &lt;em&gt;aware&lt;/em&gt; of the system. Before that, updates happened silently and people were confused when things changed.&lt;/p&gt;
&lt;h4 id=&quot;version-everything&quot;&gt;Version Everything&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;manifest.json&lt;/code&gt; version tracking was critical. Without it, the auto-update script had no way to know if anything changed, and it would waste time syncing configs that were already up to date.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-complete-stack&quot;&gt;The Complete Stack&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Layer &lt;/th&gt;&lt;th&gt;Technology&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Backend &lt;/td&gt;&lt;td&gt;NestJS (TypeScript)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Frontend &lt;/td&gt;&lt;td&gt;ReactJS (TypeScript)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;AI &lt;/td&gt;&lt;td&gt;Qwen MCP&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Containers &lt;/td&gt;&lt;td&gt;Docker, Docker Compose&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Runtime &lt;/td&gt;&lt;td&gt;Node.js 20 LTS&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Database &lt;/td&gt;&lt;td&gt;PostgreSQL 16&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Cache &lt;/td&gt;&lt;td&gt;Redis 7&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;CI/CD &lt;/td&gt;&lt;td&gt;GitHub Actions&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Editor &lt;/td&gt;&lt;td&gt;VS Code&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Config Sync &lt;/td&gt;&lt;td&gt;Bash scripts + Cron&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;tl-dr&quot;&gt;TL;DR&lt;/h3&gt;
&lt;p&gt;If your team has more than 3 microservices and more than 5 developers, you need a centralized dev environment config. Here's the recipe:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Create a config repo&lt;/strong&gt; with templates, shared configs, and scripts&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Write a setup script&lt;/strong&gt; that onboards new devs in one command&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Write a sync script&lt;/strong&gt; that copies shared configs to all services&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Set up a cron job&lt;/strong&gt; that auto-updates every morning&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Create a shared NPM library&lt;/strong&gt; for reusable code&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build a service generator&lt;/strong&gt; for scaffolding new microservices&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The result: developers spend their time writing code, not configuring environments.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Have you solved this problem differently at your company? I'd love to hear about it in the comments.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;If you found this useful, give it a clap 👏 and follow for more microservices architecture deep-dives.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/28399/stop-wasting-days-dev-setup-how-automated-our-microservices-environment-every-developer</guid>
<pubDate>Thu, 24 Sep 2026 01:52:08 +0000</pubDate>
</item>
<item>
<title>React Compiler in 2026: Do You Still Need useMemo and useCallback?</title>
<link>https://coderlegion.com/26933/react-compiler-in-2026-do-you-still-need-usememo-and-usecallback</link>
<description>&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;If you've been following React news this year, you've probably seen React Compiler come up constantly. Since its stable release, adoption has picked up fast, and it's changing a conversation that's been going on in the React community for years: do developers actually need to think about &lt;code&gt;useMemo&lt;/code&gt;, &lt;code&gt;useCallback&lt;/code&gt;, and &lt;code&gt;React.memo&lt;/code&gt; anymore?&lt;/p&gt;
&lt;p&gt;Short answer: less than before, but not zero. Let's break down what's actually changed.&lt;/p&gt;
&lt;h2 id=&quot;what-react-compiler-actually-does&quot;&gt;What React Compiler Actually Does&lt;/h2&gt;
&lt;p&gt;React Compiler is a build-time tool that automatically analyzes your components and inserts memoization for you — the same kind of optimization you'd previously write by hand with &lt;code&gt;useMemo&lt;/code&gt; and &lt;code&gt;useCallback&lt;/code&gt;, except the compiler figures out where it's actually needed and applies it during your build step.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// What you write
function ProductList({ products, searchTerm }) {
  const filtered = products.filter(p =&amp;gt; p.name.includes(searchTerm));
  return &amp;lt;ul&amp;gt;{filtered.map(p =&amp;gt; &amp;lt;li key={p.id}&amp;gt;{p.name}&amp;lt;/li&amp;gt;)}&amp;lt;/ul&amp;gt;;
}

// What the compiler effectively produces (simplified)
function ProductList({ products, searchTerm }) {
  const filtered = useMemo(
    () =&amp;gt; products.filter(p =&amp;gt; p.name.includes(searchTerm)),
    [products, searchTerm]
  );
  return &amp;lt;ul&amp;gt;{filtered.map(p =&amp;gt; &amp;lt;li key={p.id}&amp;gt;{p.name}&amp;lt;/li&amp;gt;)}&amp;lt;/ul&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You write the simple version. The compiler handles the optimization. This is a genuinely big shift in how React code gets written day to day.&lt;/p&gt;
&lt;h2 id=&quot;why-this-matters&quot;&gt;Why This Matters&lt;/h2&gt;
&lt;p&gt;For years, a big part of &quot;writing performant React&quot; meant knowing exactly when to reach for &lt;code&gt;useMemo&lt;/code&gt;, &lt;code&gt;useCallback&lt;/code&gt;, and &lt;code&gt;React.memo&lt;/code&gt; — and just as importantly, when not to, since overusing them adds overhead without benefit (something worth understanding deeply, not just memorizing rules for). React Compiler removes a huge chunk of that decision-making from your daily workflow. You write straightforward component code, and the compiler figures out the optimization boundaries for you, generally more consistently than most developers would by hand.&lt;/p&gt;
&lt;p&gt;This lowers the barrier significantly for newer developers — you can focus on what the component actually does, rather than also having to reason about referential stability and shallow prop comparisons on every component you write.&lt;/p&gt;
&lt;h2 id=&quot;so-is-manual-memoization-dead-&quot;&gt;So Is Manual Memoization Dead?&lt;/h2&gt;
&lt;p&gt;Not entirely, and here's why it's still worth understanding:&lt;/p&gt;
&lt;p&gt;The compiler doesn't cover every case yet. Custom hooks with complex external dependencies, some third-party library integrations, and edge cases the compiler's static analysis can't confidently reason about may still need manual attention.&lt;/p&gt;
&lt;p&gt;Understanding why memoization helps makes debugging easier. When something doesn't optimize the way you expect, knowing the underlying mechanics — what actually triggers a re-render, how shallow comparison works — lets you diagnose the issue instead of just guessing.&lt;/p&gt;
&lt;p&gt;Not every codebase has adopted it yet. Plenty of production apps are still running without React Compiler, especially larger, older codebases where adopting a new build-time tool takes deliberate migration effort. If you're working on one of these, the manual APIs are still exactly as relevant as before.&lt;/p&gt;
&lt;h2 id=&quot;adopting-it-in-an-existing-project&quot;&gt;Adopting It in an Existing Project&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;npm install babel-plugin-react-compiler
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// babel.config.js
module.exports = {
  plugins: [&quot;babel-plugin-react-compiler&quot;],
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The compiler is designed to be adopted incrementally — you don't need to rewrite your whole codebase at once. It analyzes your existing components and applies optimizations where it's confident doing so is safe, without requiring you to change how you write components.&lt;/p&gt;
&lt;h2 id=&quot;what-this-means-for-learning-react-in-2026&quot;&gt;What This Means for Learning React in 2026&lt;/h2&gt;
&lt;p&gt;If you're newer to React, this is genuinely good news: you can focus your energy on understanding component structure, hooks, and data flow, without needing to simultaneously master manual performance optimization from day one. That said, I'd still recommend understanding what the compiler is doing under the hood — reading about how &lt;code&gt;useMemo&lt;/code&gt; and &lt;code&gt;React.memo&lt;/code&gt; work conceptually will make you a stronger developer even if you rarely write them by hand going forward, the same way understanding how a car engine works makes you a better driver even if you never plan to fix one yourself.&lt;/p&gt;
&lt;h2 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;React Compiler is one of the more meaningful shifts in how React gets written in years — less boilerplate, fewer performance footguns, and a lower barrier to entry for people learning React right now. It doesn't make performance knowledge irrelevant, but it does change where that knowledge matters most: less in your daily component code, more in the rare cases where the compiler needs a hand, or when you're maintaining a codebase that hasn't adopted it yet.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Muhammad Farhan&lt;/strong&gt; is a Frontend Developer specializing in React.js and Tailwind CSS, based in Dera Ismail Khan, Pakistan. Portfolio: &lt;strong&gt;&lt;em&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://muhammadfarhandev.netlify.app&quot;&gt;Muhammad Farhan&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26933/react-compiler-in-2026-do-you-still-need-usememo-and-usecallback</guid>
<pubDate>Sat, 12 Sep 2026 05:57:58 +0000</pubDate>
</item>
<item>
<title>How to Build Your First React Component (Step-by-Step)</title>
<link>https://coderlegion.com/23000/how-to-build-your-first-react-component-step-by-step</link>
<description>&lt;h2 id=&quot;introduction-1&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Your first React component doesn't need to be complicated. In fact, the biggest mistake beginners make is trying to build something too ambitious right away and getting stuck on concepts they haven't learned yet.&lt;/p&gt;
&lt;p&gt;We're building something practical and genuinely useful: a reusable Testimonial Card component — the kind of thing that shows up in real portfolios and client projects constantly.&lt;/p&gt;
&lt;h2 id=&quot;step-1-understand-what-a-component-actually-is&quot;&gt;Step 1: Understand What a Component Actually Is&lt;/h2&gt;
&lt;p&gt;A React component is just a JavaScript function that returns some JSX (HTML-like syntax). That's the whole concept.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function Greeting() {
  return &amp;lt;h1&amp;gt;Hello, world!&amp;lt;/h1&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You use it like an HTML tag:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Greeting /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;step-2-build-the-static-version-first&quot;&gt;Step 2: Build the Static Version First&lt;/h2&gt;
&lt;p&gt;Before adding any dynamic data, get the layout right with hardcoded content:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function TestimonialCard() {
  return (
    &amp;lt;div className=&quot;bg-slate-800 rounded-2xl p-6 max-w-sm shadow-lg&quot;&amp;gt;
      &amp;lt;p className=&quot;text-slate-300 mb-4&quot;&amp;gt;
        &quot;This developer delivered exactly what we needed, on time and with great communication.&quot;
      &amp;lt;/p&amp;gt;
      &amp;lt;div className=&quot;flex items-center gap-3&quot;&amp;gt;
        &amp;lt;div className=&quot;w-10 h-10 rounded-full bg-blue-500&quot;&amp;gt;&amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
          &amp;lt;p className=&quot;text-white font-semibold&quot;&amp;gt;Sarah Ahmed&amp;lt;/p&amp;gt;
          &amp;lt;p className=&quot;text-slate-400 text-sm&quot;&amp;gt;Product Manager&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice: no state, no props, nothing dynamic. Just structure and styling. This is genuinely how you should approach every new component — get the static layout right first.&lt;/p&gt;
&lt;h2 id=&quot;step-3-make-it-reusable-with-props&quot;&gt;Step 3: Make It Reusable with Props&lt;/h2&gt;
&lt;p&gt;Right now this card only shows one testimonial. Props let you reuse the same component with different data:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function TestimonialCard({ quote, name, role }) {
  return (
    &amp;lt;div className=&quot;bg-slate-800 rounded-2xl p-6 max-w-sm shadow-lg&quot;&amp;gt;
      &amp;lt;p className=&quot;text-slate-300 mb-4&quot;&amp;gt;&quot;{quote}&quot;&amp;lt;/p&amp;gt;
      &amp;lt;div className=&quot;flex items-center gap-3&quot;&amp;gt;
        &amp;lt;div className=&quot;w-10 h-10 rounded-full bg-blue-500&quot;&amp;gt;&amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
          &amp;lt;p className=&quot;text-white font-semibold&quot;&amp;gt;{name}&amp;lt;/p&amp;gt;
          &amp;lt;p className=&quot;text-slate-400 text-sm&quot;&amp;gt;{role}&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you use it like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;TestimonialCard
  quote=&quot;This developer delivered exactly what we needed, on time.&quot;
  name=&quot;Sarah Ahmed&quot;
  role=&quot;Product Manager&quot;
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;{ quote, name, role }&lt;/strong&gt; is destructuring the props object — same as writing &lt;strong&gt;props.quote, props.name, props.role&lt;/strong&gt;, just cleaner.&lt;/p&gt;
&lt;h2 id=&quot;step-4-render-a-list-of-them&quot;&gt;Step 4: Render a List of Them&lt;/h2&gt;
&lt;p&gt;In a real project, you'd map over an array of testimonial data:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const testimonials = [
  { quote: &quot;Great work, fast turnaround.&quot;, name: &quot;Ali Raza&quot;, role: &quot;Founder&quot; },
  { quote: &quot;Clean code and clear communication.&quot;, name: &quot;Sarah Ahmed&quot;, role: &quot;PM&quot; },
  { quote: &quot;Would hire again without hesitation.&quot;, name: &quot;James Lee&quot;, role: &quot;CTO&quot; },
];

function TestimonialGrid() {
  return (
    &amp;lt;div className=&quot;grid md:grid-cols-3 gap-6 p-6&quot;&amp;gt;
      {testimonials.map((item, index) =&amp;gt; (
        &amp;lt;TestimonialCard key={index} {...item} /&amp;gt;
      ))}
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;{...item}&lt;/strong&gt; spreads the object properties as individual props automatically — a common React pattern once you're comfortable with the basics.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; the key &lt;strong&gt;prop&lt;/strong&gt; is required whenever you render a list. React uses it to track which items changed. In a real project, use a unique ID from your data rather than the array index if possible.&lt;/p&gt;
&lt;h2 id=&quot;step-5-add-a-bit-of-interactivity-optional-&quot;&gt;Step 5: Add a Bit of Interactivity (Optional)&lt;/h2&gt;
&lt;p&gt;Want to show a &quot;Read more&quot; toggle for long quotes? That's where &lt;strong&gt;useState&lt;/strong&gt; comes in — covered in detail in Article 5, but here's the quick version:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function TestimonialCard({ quote, name, role }) {
  const [expanded, setExpanded] = useState(false);

  return (
    &amp;lt;div className=&quot;bg-slate-800 rounded-2xl p-6 max-w-sm shadow-lg&quot;&amp;gt;
      &amp;lt;p className={`text-slate-300 mb-4 ${!expanded &amp;amp;&amp;amp; &quot;line-clamp-2&quot;}`}&amp;gt;
        &quot;{quote}&quot;
      &amp;lt;/p&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setExpanded(!expanded)} className=&quot;text-blue-400 text-sm mb-4&quot;&amp;gt;
        {expanded ? &quot;Show less&quot; : &quot;Read more&quot;}
      &amp;lt;/button&amp;gt;
      {/* rest of the card */}
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;common-beginner-mistakes&quot;&gt;Common Beginner Mistakes&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Trying to add state and interactivity before the static layout is right. Get the structure and styling working first — add logic after.&lt;/li&gt;
&lt;li&gt;Forgetting the &lt;strong&gt;key&lt;/strong&gt; prop when rendering lists. React will warn you in the console — don't ignore that warning.&lt;/li&gt;
&lt;li&gt;Not destructuring props. Writing &lt;strong&gt;props.quote&lt;/strong&gt; everywhere instead of destructuring is more typing and harder to read.&lt;/li&gt;
&lt;li&gt;Building one giant component instead of small, focused ones. If a component is doing three different jobs, split it into three components.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;final-thoughts-1&quot;&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;Building your first real component is less about React &quot;magic&quot; and more about breaking a UI down into small, reusable pieces — static first, then props, then interactivity if needed. Rebuild this same pattern (static → props → list → interactivity) for your next few components and it'll start feeling automatic.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Muhammad Farhan is a Frontend Developer specializing in React.js and Tailwind CSS, based in Dera Ismail Khan, Pakistan.&lt;br&gt;
&lt;strong&gt;Portfolio:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://muhammadfarhandev.netlify.app&quot;&gt;Muhammad Farhan&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/23000/how-to-build-your-first-react-component-step-by-step</guid>
<pubDate>Fri, 07 Aug 2026 05:00:00 +0000</pubDate>
</item>
<item>
<title>Understanding React Hooks: useState and useEffect Explained Simply</title>
<link>https://coderlegion.com/22994/understanding-react-hooks-usestate-and-useeffect-explained-simply</link>
<description>&lt;h2 id=&quot;introduction-2&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Hooks confused me for longer than I'd like to admit. Not the syntax — the syntax is simple. What confused me was when to use useEffect versus just writing normal logic in my component.&lt;/p&gt;
&lt;p&gt;If you're at that stage right now, this article is for you. We're keeping this practical — real examples, real mistakes, no theory-heavy explanations of React's internal fiber architecture.&lt;/p&gt;
&lt;h3 id=&quot;usestate-giving-your-component-memory&quot;&gt;useState: Giving Your Component Memory&lt;/h3&gt;
&lt;p&gt;Without state, a component renders once and forgets everything. &lt;strong&gt;useState&lt;/strong&gt; lets a component remember values between renders — and re-render automatically when that value changes.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;Count: {count}&amp;lt;/p&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCount(count + 1)}&amp;gt;Increase&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;count&lt;/strong&gt; is the current value. &lt;strong&gt;setCount&lt;/strong&gt; is the function you call to update it. Calling &lt;strong&gt;setCount&lt;/strong&gt; tells React &quot;re-render this component with the new value&quot; — you never update &lt;strong&gt;count&lt;/strong&gt; directly.&lt;/p&gt;
&lt;p&gt;Common Mistake: Updating State Based on Old State&lt;/p&gt;
&lt;p&gt;This looks fine but can cause bugs:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;setCount(count + 1);
setCount(count + 1); // still only adds 1, not 2!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fix it with the function form:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;setCount(prev =&amp;gt; prev + 1);
setCount(prev =&amp;gt; prev + 1); // now correctly adds 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This matters more than it seems — always use the function form when your new state depends on the previous state.&lt;/p&gt;
&lt;h3 id=&quot;useeffect-running-code-at-the-right-time&quot;&gt;useEffect: Running Code at the Right Time&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;useEffect&lt;/strong&gt; lets you run code in response to a component rendering, or a specific value changing — things like fetching data, updating the page title, or setting up an event listener.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() =&amp;gt; {
    fetch(`https://api.example.com/users/${userId}`)
      .then(res =&amp;gt; res.json())
      .then(data =&amp;gt; setUser(data));
  }, [userId]);

  if (!user) return &amp;lt;p&amp;gt;Loading...&amp;lt;/p&amp;gt;;
  return &amp;lt;h2&amp;gt;{user.name}&amp;lt;/h2&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The array at the end — &lt;strong&gt;[userId]&lt;/strong&gt; — is the dependency array. It tells React &quot;only re-run this effect when &lt;strong&gt;userId&lt;/strong&gt; changes.&quot;&lt;/p&gt;
&lt;p&gt;The Three Dependency Array Patterns&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;useEffect(() =&amp;gt; { /* code */ });          // runs after EVERY render
useEffect(() =&amp;gt; { /* code */ }, []);       // runs ONCE, after the first render
useEffect(() =&amp;gt; { /* code */ }, [userId]); // runs when userId changes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Beginners almost always want one of the last two. An empty array &lt;strong&gt;[]&lt;/strong&gt; is for one-time setup (like fetching initial data). A populated array is for &quot;re-run this when X changes.&quot;&lt;/p&gt;
&lt;h3 id=&quot;cleaning-up-after-effects&quot;&gt;Cleaning Up After Effects&lt;/h3&gt;
&lt;p&gt;Some effects need cleanup — like removing an event listener when the component unmounts:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;useEffect(() =&amp;gt; {
  const handleResize = () =&amp;gt; console.log(window.innerWidth);
  window.addEventListener('resize', handleResize);

  return () =&amp;gt; {
    window.removeEventListener('resize', handleResize);
  };
}, []);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The function you &lt;strong&gt;return&lt;/strong&gt; inside &lt;strong&gt;useEffect&lt;/strong&gt; is the cleanup function. React calls it automatically before the component unmounts, or before the effect runs again.&lt;/p&gt;
&lt;h3 id=&quot;common-beginner-mistakes-1&quot;&gt;Common Beginner Mistakes&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Forgetting the dependency array entirely. This causes the effect to run after every single render, often triggering infinite fetch loops.&lt;/li&gt;
&lt;li&gt;Missing a dependency. If your effect uses a variable from outside it, add it to the array — ESLint's react-hooks/exhaustive-deps rule will warn you about this.&lt;/li&gt;
&lt;li&gt;Putting everything in useEffect. Not all logic needs to live in an effect. If you're just calculating a value from props or state, do it directly in the component body — no hook needed.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;final-thoughts-2&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;useState&lt;/strong&gt; gives your component memory. &lt;strong&gt;useEffect&lt;/strong&gt; lets it react to changes and the outside world (APIs, timers, event listeners). Once you separate those two jobs clearly in your head, hooks stop feeling mysterious.&lt;/p&gt;
&lt;p&gt;Try rebuilding a small feature — a search box with an API call, for example — using exactly these two hooks. That's usually the exercise that makes it click.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Muhammad Farhan&lt;/strong&gt; is a Frontend Developer specializing in React.js and Tailwind CSS, based in Dera Ismail Khan, Pakistan.&lt;br&gt;
&lt;strong&gt;Portfolio:&lt;/strong&gt; &lt;em&gt;muhammad-farhan-dev.github.io/muhammadfarhan.dev&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/22994/understanding-react-hooks-usestate-and-useeffect-explained-simply</guid>
<pubDate>Tue, 28 Jul 2026 05:00:00 +0000</pubDate>
</item>
<item>
<title>Hire Me: Muhammad Farhan — Frontend Developer Available for Remote Work</title>
<link>https://coderlegion.com/23328/hire-me-muhammad-farhan-frontend-developer-available-for-remote-work</link>
<description>&lt;h2 id=&quot;hi-i-m-muhammad-farhan&quot;&gt;Hi, I'm Muhammad Farhan&lt;/h2&gt;
&lt;p&gt;I'm a frontend developer based in Dera Ismail Khan, Pakistan, and I build clean, modern, fast-loading websites using React.js, Tailwind CSS, and WordPress. If you need a website built, a design turned into working code, or an existing site improved, I'd genuinely like to help — and I'm available to start right away.&lt;/p&gt;
&lt;h2 id=&quot;what-i-actually-do&quot;&gt;What I Actually Do&lt;/h2&gt;
&lt;p&gt;I specialize in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;React.js&lt;/strong&gt; — component-based, interactive web apps built to scale&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tailwind CSS&lt;/strong&gt; — clean, modern, fully responsive UI, my go-to styling approach&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTML5 &amp;amp; CSS3&lt;/strong&gt; — solid, semantic, accessible markup underneath every project&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JavaScript (ES6+)&lt;/strong&gt; — smooth interactivity, no clunky or broken UI&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bootstrap&lt;/strong&gt; — for projects that need it, including maintaining older sites&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;WordPress&lt;/strong&gt; — theme customization, blog setup, and full client websites, including custom frontend features layered on top&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;real-work-not-just-promises&quot;&gt;Real Work, Not Just Promises&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;UX Gazette&lt;/strong&gt; — a professional blog website I built for a real client on WordPress, live at uxgazette.com&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Personal Portfolio&lt;/strong&gt; — my own site, showcasing my design and development work&lt;/li&gt;
&lt;li&gt;Multiple additional projects across business landing pages, animated websites, and UI design work&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I don't just talk about being able to build things — every skill listed above has a real, live, shipped project behind it that you can actually visit and click through.&lt;/p&gt;
&lt;h2 id=&quot;why-work-with-me&quot;&gt;Why Work With Me&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;I communicate clearly and respond fast.&lt;/strong&gt; No chasing me for updates — I keep you informed without being asked.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I care about clean, maintainable code&lt;/strong&gt;, not just something that looks right on the surface and breaks the moment it needs a change.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I'm affordable for the quality I deliver&lt;/strong&gt;, especially compared to larger agencies, while still meeting professional standards.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I'm available now&lt;/strong&gt; — for a single project, an ongoing freelance arrangement, or a part-time/remote role.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;what-i-can-help-you-with&quot;&gt;What I Can Help You With&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Building a new website from scratch (business site, portfolio, landing page)&lt;/li&gt;
&lt;li&gt;Converting a Figma or Adobe XD design into a fully working, responsive website&lt;/li&gt;
&lt;li&gt;Fixing bugs or improving performance on an existing site&lt;/li&gt;
&lt;li&gt;Customizing or maintaining a WordPress site&lt;/li&gt;
&lt;li&gt;Building custom tools, dashboards, or interactive features with React&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;let-s-talk-about-your-project&quot;&gt;Let's Talk About Your Project&lt;/h2&gt;
&lt;p&gt;If you have a project in mind — big or small — I'd love to hear about it. Reach out directly using any of the details below, and I'll get back to you quickly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Email:&lt;/strong&gt; &lt;strong&gt;&lt;strong&gt;Emails are not allowed&lt;/strong&gt;&lt;/strong&gt; &lt;br&gt;
&lt;strong&gt;Phone / WhatsApp:&lt;/strong&gt; &lt;strong&gt;&lt;em&gt;+92 335-4051272&lt;/em&gt;&lt;/strong&gt; &lt;br&gt;
&lt;strong&gt;Portfolio:&lt;/strong&gt; &lt;strong&gt;&lt;em&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://muhammadfarhandev.netlify.app&quot;&gt;Click Here&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt; &lt;br&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;strong&gt;&lt;em&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;http://%20https://github.com/muhammad-farhan-dev&quot;&gt;Click Here&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Behance:&lt;/strong&gt; &lt;strong&gt;&lt;em&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;http://behance.net/muhammadfarhan-dev&quot;&gt;Click Here&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Instagram:&lt;/strong&gt; &lt;strong&gt;&lt;em&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;http://instagram.com/muhammadfarhan.dev&quot;&gt;Click Here&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt; &lt;br&gt;
&lt;strong&gt;X (Twitter):&lt;/strong&gt; &lt;strong&gt;&lt;em&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;http://x.com/m_farhandev&quot;&gt;Click Here&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt; &lt;br&gt;
&lt;strong&gt;WhatsApp Channel:&lt;/strong&gt; &lt;strong&gt;&lt;em&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;http://whatsapp.com/channel/0029Vb8GtTZ6mYPLeNwrgI3h&quot;&gt;Click Here&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Feel free to message me directly on WhatsApp for the fastest response — I usually reply within a few hours.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Muhammad Farhan&lt;/strong&gt; — Frontend Developer specializing in React.js and Tailwind CSS, based in Dera Ismail Khan, Pakistan.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/23328/hire-me-muhammad-farhan-frontend-developer-available-for-remote-work</guid>
<pubDate>Thu, 23 Jul 2026 09:31:37 +0000</pubDate>
</item>
<item>
<title>15 Free Frontend Tools Every Developer Should Know in 2026</title>
<link>https://coderlegion.com/22579/15-free-frontend-tools-every-developer-should-know-in-2026</link>
<description>&lt;h2 id=&quot;introduction-3&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;When I started learning frontend development, I thought I needed expensive software to build things that looked &quot;professional.&quot; Turns out, that's completely false.&lt;/p&gt;
&lt;p&gt;Some of the best frontend tools out there are free — and a lot of developers, even experienced ones, don't know half of them exist. Whether you're picking colors, generating CSS animations, testing responsiveness, or just trying to find the right icon, there's a free tool for almost everything.&lt;/p&gt;
&lt;p&gt;In this post, I'm sharing 15 tools I personally use (or have used) while building real projects — from client websites to personal portfolios. No fluff, no &quot;100 tools you'll never open&quot; lists. Just the ones that actually save time.&lt;/p&gt;
&lt;p&gt;Let's get into it.&lt;/p&gt;
&lt;h3 id=&quot;1-design-amp-color-tools&quot;&gt;1. Design &amp;amp; Color Tools&lt;/h3&gt;
&lt;p&gt;Good design starts with good colors and layout inspiration. These tools take the guesswork out of it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Coolors&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Coolors is the fastest way to generate a color palette. Hit the spacebar and it keeps generating combinations until you find one you like. You can lock colors you love and regenerate the rest.&lt;/p&gt;
&lt;p&gt;Why beginners love it: No design theory needed — just pick what looks good.&lt;br&gt;
🔗 coolors.co&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Adobe Color&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;More advanced than Coolors, this lets you build palettes based on color theory rules (complementary, triadic, monochromatic, etc.) and even extract a palette from an image.&lt;/p&gt;
&lt;p&gt;Why it's useful: Great when you want a palette that matches a client's brand photo or logo.&lt;br&gt;
🔗 color.adobe.com&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;2-css-generators-stop-writing-gradients-by-hand-&quot;&gt;2. CSS Generators (Stop Writing Gradients by Hand)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;CSS Gradient&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Pick colors, choose the direction, and copy the ready-made CSS. Simple, fast, and works for linear and radial gradients.&lt;br&gt;
🔗 cssgradient.io&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Neumorphism.io&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you want that soft, &quot;pressed-in&quot; neumorphic UI style, this generator gives you the exact box-shadow code — adjustable by distance, blur, and intensity, with light/dark background support.&lt;br&gt;
🔗 neumorphism.io&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Clippy&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Need a custom shape (hexagon, arrow, triangle) using clip-path? Clippy lets you drag points visually and copies the CSS for you. No more guessing polygon coordinates.&lt;br&gt;
🔗 bennettfeely.com/clippy&lt;/p&gt;
&lt;p&gt;Pro tip: These generators are amazing for learning too — generate the code, then read it and understand why it works. That's how I actually learned box-shadow and clip-path properties.&lt;/p&gt;
&lt;h3 id=&quot;3-icons-that-don-t-look-generic&quot;&gt;3. Icons That Don't Look Generic&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Lucide Icons&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A clean, consistent icon set that works beautifully with React and Tailwind CSS. Lightweight, tree-shakable, and has an official React package (lucide-react).&lt;br&gt;
🔗 lucide.dev&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Heroicons&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Made by the Tailwind CSS team, so naturally it pairs perfectly with Tailwind projects. Comes in outline and solid styles.&lt;br&gt;
🔗 heroicons.com&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Font Awesome (Free Tier)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The classic. Massive icon library, and the free tier alone covers most needs — social icons, UI icons, brand logos.&lt;br&gt;
🔗 fontawesome.com&lt;/p&gt;
&lt;p&gt;My pick for React + Tailwind projects: Lucide. It's the one I use across my own builds because it's lightweight and easy to customize with Tailwind classes.&lt;/p&gt;
&lt;h3 id=&quot;4-fonts-amp-typography&quot;&gt;4. Fonts &amp;amp; Typography&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Google Fonts&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The obvious one, but don't just grab the first font you see. Google Fonts also shows you popularity, styles, and lets you preview your own text before choosing.&lt;br&gt;
🔗 fonts.google.com&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fontjoy&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Struggling to pair a heading font with a body font? Fontjoy uses AI to suggest font combinations that actually work together — and you can lock one font and regenerate the other.&lt;br&gt;
🔗 fontjoy.com&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;5-online-code-playgrounds&quot;&gt;5. Online Code Playgrounds&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;CodePen&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Great for quick CSS/JS experiments, sharing snippets, and browsing what other developers are building. Perfect for testing an animation idea before adding it to a real project.&lt;br&gt;
🔗 codepen.io&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;StackBlitz&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A full online IDE that runs actual React, Vue, or Node projects in your browser — no local setup needed. I use this a lot when testing a quick React component idea.&lt;br&gt;
🔗 stackblitz.com&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CodeSandbox&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Similar to StackBlitz, with strong support for sharing full working React apps and instant deployment previews. Great for collaborating or showing a client a live demo without deploying anywhere.&lt;br&gt;
🔗 codesandbox.io&lt;/p&gt;
&lt;h3 id=&quot;6-image-optimization-tools&quot;&gt;6. Image Optimization Tools&lt;/h3&gt;
&lt;p&gt;Heavy images kill website speed — and speed affects both user experience and SEO rankings.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Squoosh&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Made by Google. Drag in an image, compare compression formats (WebP, AVIF, JPEG) side by side, and download the optimized version — all in your browser, nothing uploaded to a server.&lt;br&gt;
🔗 squoosh.app&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TinyPNG&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Bulk compress PNG and JPEG images with barely noticeable quality loss. I run every client project's images through this before deployment.&lt;br&gt;
🔗 tinypng.com&lt;/p&gt;
&lt;h3 id=&quot;7-responsive-amp-accessibility-testing&quot;&gt;7. Responsive &amp;amp; Accessibility Testing&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Responsively App&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A free desktop app that shows your website on multiple device sizes at once, side by side, and syncs scrolling and clicks across all of them. Saves so much time compared to resizing your browser manually.&lt;br&gt;
🔗 responsively.app&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Chrome DevTools (Lighthouse)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Built right into Chrome. Open DevTools → Lighthouse tab → run an audit. It scores your site on performance, accessibility, best practices, and SEO, and tells you exactly what to fix.&lt;/p&gt;
&lt;p&gt;Why this matters for freelancers: Clients love seeing a 90+ Lighthouse score. It's an easy way to prove your work is solid.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;WAVE&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A free accessibility checker. Paste your URL and it visually flags missing alt text, low contrast, and other accessibility issues directly on the page.&lt;br&gt;
🔗 wave.webaim.org&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;8-reference-amp-learning&quot;&gt;8. Reference &amp;amp; Learning&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;MDN Web Docs&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The single most reliable source for HTML, CSS, and JavaScript documentation. When in doubt, check MDN before trusting a random blog post (including this one).&lt;br&gt;
🔗 developer.mozilla.org&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Can I Use&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Before using a newer CSS or JS feature, check browser support here. Saves you from shipping something that breaks in Safari or older browsers.&lt;br&gt;
🔗 caniuse.com&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Final Thoughts&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You don't need a paid design subscription or premium plugin to build clean, professional websites. The tools above cover almost every stage of frontend work — design, coding, testing, and optimization — and they're all free.&lt;/p&gt;
&lt;p&gt;My honest advice as someone who's been learning and building for the past couple of years: don't try to learn all 15 tools at once. Pick 2–3 that solve a problem you're facing right now, use them in your next project, and the rest will come naturally as you build more.&lt;/p&gt;
&lt;p&gt;If you found this useful, save it — you'll probably come back to this list more than once.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Written by &lt;strong&gt;Muhammad Farhan&lt;/strong&gt; — Frontend Developer specializing in React.js &amp;amp; Tailwind CSS, based in &lt;strong&gt;Dera Ismail Khan, Pakistan&lt;/strong&gt;.&lt;br&gt;
&lt;strong&gt;Portfolio:&lt;/strong&gt; muhammad-farhan-dev.github.io/muhammadfarhan.dev &lt;br&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; github.com/muhammad-farhan-dev&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/22579/15-free-frontend-tools-every-developer-should-know-in-2026</guid>
<pubDate>Tue, 14 Jul 2026 06:38:30 +0000</pubDate>
</item>
<item>
<title>I Built a QR Label Designer That Connects Directly to Your Data Tables — Here's How It Works</title>
<link>https://coderlegion.com/21717/i-built-a-qr-label-designer-that-connects-directly-to-your-data-tables-heres-how-it-works</link>
<description>&lt;p&gt;Ever had to print 200 warehouse bin labels and realized there's no clean way to do it from a web app?&lt;/p&gt;
&lt;p&gt;That's what pushed me to build &lt;code&gt;qrlayout-core&lt;/code&gt; and &lt;code&gt;qrlayout-ui&lt;/code&gt; — two open source npm packages for designing QR label templates and bulk-printing them from React. This post walks through the full workflow with real code.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-problem&quot;&gt;The Problem&lt;/h3&gt;
&lt;p&gt;Printing QR labels from a web app usually means one of these:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Generating them one at a time manually&lt;/li&gt;
&lt;li&gt;Hardcoding the label layout in your codebase&lt;/li&gt;
&lt;li&gt;Exporting a CSV and using a desktop app&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of these scale. And none of them give non-technical users control over the label design.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-solution-two-packages-one-pipeline&quot;&gt;The Solution: Two Packages, One Pipeline&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;qrlayout-ui&lt;/code&gt;&lt;/strong&gt; — a drag-and-drop label designer your users can embed directly in your app&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;qrlayout-core&lt;/code&gt;&lt;/strong&gt; — the headless rendering engine that turns saved templates + data into PNG, PDF, or ZPL&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install qrlayout-core qrlayout-ui
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The workflow looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Labels Tab   →  Design a template, save it with a title + entity type
Master Table →  Select that saved layout from a dropdown
Table Rows   →  Check the records you want
Export       →  PNG · PDF · ZPL (thermal printer)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key idea: &lt;strong&gt;template and data stay separate&lt;/strong&gt;. One layout generates labels for hundreds of records. And because each template is tagged to an entity (&lt;code&gt;employee&lt;/code&gt;, &lt;code&gt;machine&lt;/code&gt;, &lt;code&gt;storage&lt;/code&gt;), the master table only shows layouts relevant to it.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-1-define-entity-schemas&quot;&gt;Step 1: Define Entity Schemas&lt;/h3&gt;
&lt;p&gt;You tell the designer what fields each entity has. This powers the live preview — every &lt;code&gt;{{field}}&lt;/code&gt; placeholder dropped on the canvas renders with real sample data instantly.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const SAMPLE_SCHEMAS = {
  storage: {
    label: &quot;Storage Master&quot;,
    fields: [
      { name: &quot;binCode&quot;,     label: &quot;BIN Code&quot; },
      { name: &quot;storageType&quot;, label: &quot;Storage Type&quot; },
      { name: &quot;aisle&quot;,       label: &quot;Aisle&quot; },
      { name: &quot;rack&quot;,        label: &quot;Rack Number&quot; },
    ],
    sampleData: {
      binCode: &quot;BIN-A1-R4&quot;, storageType: &quot;Pallet Rack&quot;,
      aisle: &quot;Aisle 01&quot;,    rack: &quot;R-44&quot;
    }
  },
  // employee and machine follow the same shape
};
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-2-mount-the-designer-in-react&quot;&gt;Step 2: Mount the Designer in React&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;qrlayout-ui&lt;/code&gt; is framework-agnostic so you mount it with a &lt;code&gt;ref&lt;/code&gt; + &lt;code&gt;useEffect&lt;/code&gt;. Two things matter most — passing &lt;code&gt;onSave&lt;/code&gt; to capture the layout JSON, and calling &lt;code&gt;destroy()&lt;/code&gt; on unmount to prevent memory leaks.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;useEffect(() =&amp;gt; {
  if (!containerRef.current) return;

  designerRef.current = new QRLayoutDesigner({
    element: containerRef.current,
    entitySchemas: SAMPLE_SCHEMAS,
    initialLayout: editingLayout || { ...DEFAULT_NEW_LAYOUT, id: crypto.randomUUID() },
    onSave: (layout) =&amp;gt; {
      storage.addLabel(layout);        // persist the layout JSON
      setLabels(storage.getLabels());
      setSubView('list');
    }
  });

  return () =&amp;gt; {
    designerRef.current?.destroy();    // always clean up
    designerRef.current = null;
  };
}, [subView, editingLayout]);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the user clicks Save inside the designer, &lt;code&gt;onSave&lt;/code&gt; fires with the complete &lt;code&gt;StickerLayout&lt;/code&gt; JSON — name, target entity, dimensions, and all elements. That single JSON object is what flows through the entire rest of the system.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-3-the-master-table-select-rows-pick-layout-export&quot;&gt;Step 3: The Master Table — Select Rows, Pick Layout, Export&lt;/h3&gt;
&lt;p&gt;Each master table (Employees, Machines, Storage BINs) filters layouts by &lt;code&gt;targetEntity&lt;/code&gt; so users only see relevant templates in the dropdown.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// Only load layouts relevant to this entity
const binLabels = storage.getLabels().filter(l =&amp;gt; l.targetEntity === 'storage');

const getSelectedBins = () =&amp;gt; bins.filter(b =&amp;gt; selectedBinIds.includes(b.id));
const getActiveLayout = () =&amp;gt; labels.find(l =&amp;gt; l.id === selectedLayoutId);

const handleExportPDF = async () =&amp;gt; {
  const layout = getActiveLayout();
  const selected = getSelectedBins();
  if (!layout) return;
  await exportToBatchPDF({
    layout,
    items: selected,
    printer: printer.current,
    baseFilename: 'batch-bin-labels'
  });
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When rows are selected, the export toolbar appears with three options. Until then the page shows a step-by-step guide so users know what to do.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-4-three-export-formats&quot;&gt;Step 4: Three Export Formats&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// PNG — one file per item
printer.renderToDataURL(layout, item, { format: 'png' })

// PDF — all items in one document
import { exportToPDF } from 'qrlayout-core/pdf';
const pdf = await exportToPDF(layout, items);
pdf.save('labels.pdf');

// ZPL — for Zebra and other thermal printers
const zplCommands = printer.exportToZPL(layout, items);
// In production: send over TCP port 9100 directly to the printer
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;exportToPDF&lt;/code&gt; is a separate optional import to keep the core bundle light. ZPL output is an array of strings — one per label — which you can download as a &lt;code&gt;.txt&lt;/code&gt; file or pipe straight to a Zebra printer over TCP.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;try-it-pre-loaded-demo-data&quot;&gt;Try It — Pre-Loaded Demo Data&lt;/h3&gt;
&lt;p&gt;The &lt;a rel=&quot;nofollow&quot; href=&quot;https://qr-layout-designer.netlify.app/&quot;&gt;live demo&lt;/a&gt; seeds everything on first load:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;3 layouts&lt;/strong&gt; — Professional ID Badge, Equipment Asset Tag, Storage Location Label&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3 employees&lt;/strong&gt; — Arjun Mehta, Priya Sharma, Kiran Patel&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3 machines&lt;/strong&gt; — CNC Router X1, Industrial 3D Printer, Hydraulic Press&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3 bins&lt;/strong&gt; — BIN-A1-R1 (Pallet Rack), BIN-A1-R2 (Shelf), BIN-B2-R1 (Cold Storage)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Go to Storage → check all bins → click PDF. Done in 30 seconds.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;this-is-a-frontend-demo-here-s-the-production-path&quot;&gt;This is a Frontend Demo — Here's the Production Path&lt;/h3&gt;
&lt;p&gt;Everything in the demo runs in the browser using localStorage. In a real project you'd swap each layer with a backend equivalent:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Demo &lt;/th&gt;&lt;th&gt;Production&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;localStorage&lt;/code&gt; (layouts) &lt;/td&gt;&lt;td&gt;DB table with a &lt;code&gt;layout_json&lt;/code&gt; column&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;localStorage&lt;/code&gt; (bins/machines) &lt;/td&gt;&lt;td&gt;Your existing ERP or database&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Client-side filter by &lt;code&gt;targetEntity&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;WHERE target_entity = 'storage'&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Browser PDF download &lt;/td&gt;&lt;td&gt;Node.js server-side render → stream to client&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;.txt&lt;/code&gt; ZPL download &lt;/td&gt;&lt;td&gt;TCP socket → Zebra printer (port 9100)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;qrlayout-core&lt;/code&gt; has zero browser dependencies — &lt;code&gt;exportToPDF&lt;/code&gt; and &lt;code&gt;printer.exportToZPL&lt;/code&gt; work identically in Node.js. The full production flow:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;React Frontend
  └── qrlayout-ui  →  user designs label → onSave fires layout JSON
  └── POST /api/layouts  →  save to DB

Node.js Backend
  └── GET /api/layouts?entity=storage  →  filtered layouts to frontend
  └── POST /api/labels/print
        ├── fetch layout from DB
        ├── fetch selected records from ERP / DB
        ├── exportToPDF(layout, items)  →  stream PDF to client
        └── printer.exportToZPL(layout, items)  →  TCP → Zebra printer
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;links&quot;&gt;Links&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;npm: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/qrlayout-core&quot;&gt;qrlayout-core&lt;/a&gt; · &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/qrlayout-ui&quot;&gt;qrlayout-ui&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/shashi089/qr-code-layout-generate-tool&quot;&gt;GitHub — full source + React demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Live demos: &lt;a rel=&quot;nofollow&quot; href=&quot;https://qr-layout-designer.netlify.app/&quot;&gt;React&lt;/a&gt; · &lt;a rel=&quot;nofollow&quot; href=&quot;https://qr-layout-designer-svelte.netlify.app/&quot;&gt;Svelte&lt;/a&gt; · &lt;a rel=&quot;nofollow&quot; href=&quot;https://qr-layout-designer-vue.netlify.app/&quot;&gt;Vue&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Happy to answer questions about the implementation in the comments.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21717/i-built-a-qr-label-designer-that-connects-directly-to-your-data-tables-heres-how-it-works</guid>
<pubDate>Wed, 01 Jul 2026 21:00:59 +0000</pubDate>
</item>
<item>
<title>I Built a QR Label Designer That Connects Directly to Your Data Tables — Here's How It Works</title>
<link>https://coderlegion.com/21716/i-built-a-qr-label-designer-that-connects-directly-to-your-data-tables-heres-how-it-works</link>
<description>&lt;p&gt;Ever had to print 200 warehouse bin labels and realized there's no clean way to do it from a web app?&lt;/p&gt;
&lt;p&gt;That's what pushed me to build &lt;code&gt;qrlayout-core&lt;/code&gt; and &lt;code&gt;qrlayout-ui&lt;/code&gt; — two open source npm packages for designing QR label templates and bulk-printing them from React. This post walks through the full workflow with real code.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-problem-1&quot;&gt;The Problem&lt;/h3&gt;
&lt;p&gt;Printing QR labels from a web app usually means one of these:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Generating them one at a time manually&lt;/li&gt;
&lt;li&gt;Hardcoding the label layout in your codebase&lt;/li&gt;
&lt;li&gt;Exporting a CSV and using a desktop app&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of these scale. And none of them give non-technical users control over the label design.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-solution-two-packages-one-pipeline-1&quot;&gt;The Solution: Two Packages, One Pipeline&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;qrlayout-ui&lt;/code&gt;&lt;/strong&gt; — a drag-and-drop label designer your users can embed directly in your app&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;qrlayout-core&lt;/code&gt;&lt;/strong&gt; — the headless rendering engine that turns saved templates + data into PNG, PDF, or ZPL&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install qrlayout-core qrlayout-ui
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The workflow looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Labels Tab   →  Design a template, save it with a title + entity type
Master Table →  Select that saved layout from a dropdown
Table Rows   →  Check the records you want
Export       →  PNG · PDF · ZPL (thermal printer)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key idea: &lt;strong&gt;template and data stay separate&lt;/strong&gt;. One layout generates labels for hundreds of records. And because each template is tagged to an entity (&lt;code&gt;employee&lt;/code&gt;, &lt;code&gt;machine&lt;/code&gt;, &lt;code&gt;storage&lt;/code&gt;), the master table only shows layouts relevant to it.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-1-define-entity-schemas-1&quot;&gt;Step 1: Define Entity Schemas&lt;/h3&gt;
&lt;p&gt;You tell the designer what fields each entity has. This powers the live preview — every &lt;code&gt;{{field}}&lt;/code&gt; placeholder dropped on the canvas renders with real sample data instantly.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const SAMPLE_SCHEMAS = {
  storage: {
    label: &quot;Storage Master&quot;,
    fields: [
      { name: &quot;binCode&quot;,     label: &quot;BIN Code&quot; },
      { name: &quot;storageType&quot;, label: &quot;Storage Type&quot; },
      { name: &quot;aisle&quot;,       label: &quot;Aisle&quot; },
      { name: &quot;rack&quot;,        label: &quot;Rack Number&quot; },
    ],
    sampleData: {
      binCode: &quot;BIN-A1-R4&quot;, storageType: &quot;Pallet Rack&quot;,
      aisle: &quot;Aisle 01&quot;,    rack: &quot;R-44&quot;
    }
  },
  // employee and machine follow the same shape
};
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-2-mount-the-designer-in-react-1&quot;&gt;Step 2: Mount the Designer in React&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;qrlayout-ui&lt;/code&gt; is framework-agnostic so you mount it with a &lt;code&gt;ref&lt;/code&gt; + &lt;code&gt;useEffect&lt;/code&gt;. Two things matter most — passing &lt;code&gt;onSave&lt;/code&gt; to capture the layout JSON, and calling &lt;code&gt;destroy()&lt;/code&gt; on unmount to prevent memory leaks.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;useEffect(() =&amp;gt; {
  if (!containerRef.current) return;

  designerRef.current = new QRLayoutDesigner({
    element: containerRef.current,
    entitySchemas: SAMPLE_SCHEMAS,
    initialLayout: editingLayout || { ...DEFAULT_NEW_LAYOUT, id: crypto.randomUUID() },
    onSave: (layout) =&amp;gt; {
      storage.addLabel(layout);        // persist the layout JSON
      setLabels(storage.getLabels());
      setSubView('list');
    }
  });

  return () =&amp;gt; {
    designerRef.current?.destroy();    // always clean up
    designerRef.current = null;
  };
}, [subView, editingLayout]);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the user clicks Save inside the designer, &lt;code&gt;onSave&lt;/code&gt; fires with the complete &lt;code&gt;StickerLayout&lt;/code&gt; JSON — name, target entity, dimensions, and all elements. That single JSON object is what flows through the entire rest of the system.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-3-the-master-table-select-rows-pick-layout-export-1&quot;&gt;Step 3: The Master Table — Select Rows, Pick Layout, Export&lt;/h3&gt;
&lt;p&gt;Each master table (Employees, Machines, Storage BINs) filters layouts by &lt;code&gt;targetEntity&lt;/code&gt; so users only see relevant templates in the dropdown.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// Only load layouts relevant to this entity
const binLabels = storage.getLabels().filter(l =&amp;gt; l.targetEntity === 'storage');

const getSelectedBins = () =&amp;gt; bins.filter(b =&amp;gt; selectedBinIds.includes(b.id));
const getActiveLayout = () =&amp;gt; labels.find(l =&amp;gt; l.id === selectedLayoutId);

const handleExportPDF = async () =&amp;gt; {
  const layout = getActiveLayout();
  const selected = getSelectedBins();
  if (!layout) return;
  await exportToBatchPDF({
    layout,
    items: selected,
    printer: printer.current,
    baseFilename: 'batch-bin-labels'
  });
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When rows are selected, the export toolbar appears with three options. Until then the page shows a step-by-step guide so users know what to do.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-4-three-export-formats-1&quot;&gt;Step 4: Three Export Formats&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// PNG — one file per item
printer.renderToDataURL(layout, item, { format: 'png' })

// PDF — all items in one document
import { exportToPDF } from 'qrlayout-core/pdf';
const pdf = await exportToPDF(layout, items);
pdf.save('labels.pdf');

// ZPL — for Zebra and other thermal printers
const zplCommands = printer.exportToZPL(layout, items);
// In production: send over TCP port 9100 directly to the printer
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;exportToPDF&lt;/code&gt; is a separate optional import to keep the core bundle light. ZPL output is an array of strings — one per label — which you can download as a &lt;code&gt;.txt&lt;/code&gt; file or pipe straight to a Zebra printer over TCP.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;try-it-pre-loaded-demo-data-1&quot;&gt;Try It — Pre-Loaded Demo Data&lt;/h3&gt;
&lt;p&gt;The &lt;a rel=&quot;nofollow&quot; href=&quot;https://qr-layout-designer.netlify.app/&quot;&gt;live demo&lt;/a&gt; seeds everything on first load:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;3 layouts&lt;/strong&gt; — Professional ID Badge, Equipment Asset Tag, Storage Location Label&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3 employees&lt;/strong&gt; — Arjun Mehta, Priya Sharma, Kiran Patel&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3 machines&lt;/strong&gt; — CNC Router X1, Industrial 3D Printer, Hydraulic Press&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3 bins&lt;/strong&gt; — BIN-A1-R1 (Pallet Rack), BIN-A1-R2 (Shelf), BIN-B2-R1 (Cold Storage)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Go to Storage → check all bins → click PDF. Done in 30 seconds.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;this-is-a-frontend-demo-here-s-the-production-path-1&quot;&gt;This is a Frontend Demo — Here's the Production Path&lt;/h3&gt;
&lt;p&gt;Everything in the demo runs in the browser using localStorage. In a real project you'd swap each layer with a backend equivalent:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Demo &lt;/th&gt;&lt;th&gt;Production&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;localStorage&lt;/code&gt; (layouts) &lt;/td&gt;&lt;td&gt;DB table with a &lt;code&gt;layout_json&lt;/code&gt; column&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;localStorage&lt;/code&gt; (bins/machines) &lt;/td&gt;&lt;td&gt;Your existing ERP or database&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Client-side filter by &lt;code&gt;targetEntity&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;WHERE target_entity = 'storage'&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Browser PDF download &lt;/td&gt;&lt;td&gt;Node.js server-side render → stream to client&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;.txt&lt;/code&gt; ZPL download &lt;/td&gt;&lt;td&gt;TCP socket → Zebra printer (port 9100)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;qrlayout-core&lt;/code&gt; has zero browser dependencies — &lt;code&gt;exportToPDF&lt;/code&gt; and &lt;code&gt;printer.exportToZPL&lt;/code&gt; work identically in Node.js. The full production flow:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;React Frontend
  └── qrlayout-ui  →  user designs label → onSave fires layout JSON
  └── POST /api/layouts  →  save to DB

Node.js Backend
  └── GET /api/layouts?entity=storage  →  filtered layouts to frontend
  └── POST /api/labels/print
        ├── fetch layout from DB
        ├── fetch selected records from ERP / DB
        ├── exportToPDF(layout, items)  →  stream PDF to client
        └── printer.exportToZPL(layout, items)  →  TCP → Zebra printer
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;links-1&quot;&gt;Links&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;npm: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/qrlayout-core&quot;&gt;qrlayout-core&lt;/a&gt; · &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/qrlayout-ui&quot;&gt;qrlayout-ui&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/shashi089/qr-code-layout-generate-tool&quot;&gt;GitHub — full source + React demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Live demos: &lt;a rel=&quot;nofollow&quot; href=&quot;https://qr-layout-designer.netlify.app/&quot;&gt;React&lt;/a&gt; · &lt;a rel=&quot;nofollow&quot; href=&quot;https://qr-layout-designer-svelte.netlify.app/&quot;&gt;Svelte&lt;/a&gt; · &lt;a rel=&quot;nofollow&quot; href=&quot;https://qr-layout-designer-vue.netlify.app/&quot;&gt;Vue&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Happy to answer questions about the implementation in the comments.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21716/i-built-a-qr-label-designer-that-connects-directly-to-your-data-tables-heres-how-it-works</guid>
<pubDate>Wed, 01 Jul 2026 17:06:32 +0000</pubDate>
</item>
<item>
<title>Stop Juggling Browser Tabs: Why I Built a Desktop App for JSON, Regex, and Cron</title>
<link>https://coderlegion.com/20010/stop-juggling-browser-tabs-why-i-built-a-desktop-app-for-json-regex-and-cron</link>
<description>&lt;p&gt;TL;DR: I got tired of switching between a dozen browser tabs for developer tools. So I built Smart Utils Desktop – a native app that handles JSON formatting, regex testing, and cron scheduling. All offline. All private. All in one place.&lt;br&gt;
The Problem Every Developer Knows&lt;/p&gt;
&lt;p&gt;Let me describe a typical Tuesday.&lt;/p&gt;
&lt;p&gt;I'm debugging an API integration. The response comes back as minified JSON – one massive, unreadable line. I open a browser tab, search for &quot;JSON formatter,&quot; click the first result, paste my data, and click Format.&lt;/p&gt;
&lt;p&gt;Then the ad loads. Then the popup asks me to sign up. Then I close it and try the next site.&lt;/p&gt;
&lt;p&gt;Twenty minutes later, I have my formatted JSON. But now I need to test a regex pattern for email validation. Another tab. Another search. More ads. More popups.&lt;/p&gt;
&lt;p&gt;Then I need a cron expression for a daily backup. You guessed it – another tab.&lt;/p&gt;
&lt;p&gt;By the end of the day, I have fifteen browser tabs open. My laptop fan is screaming. And somewhere out there, a dozen different websites have stored my API keys, my regex patterns, and my data.&lt;/p&gt;
&lt;p&gt;There had to be a better way.&lt;br&gt;
The Breaking Point&lt;/p&gt;
&lt;p&gt;The moment that pushed me over the edge happened last winter.&lt;/p&gt;
&lt;p&gt;I was working on a JSON file that was about 85MB – not huge by modern standards, but too big for most browser-based tools. I tried five different online JSON formatters. Every single one either:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Froze my browser tab

Showed an error message about file size

Asked me to pay for a &quot;pro&quot; plan
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One site actually uploaded my data to their server without telling me. I found out because their privacy policy (which I had to dig for) mentioned &quot;data may be stored for analytics purposes.&quot;&lt;/p&gt;
&lt;p&gt;That was it.&lt;/p&gt;
&lt;p&gt;I closed all my tabs, opened my code editor, and started building the tool I actually wanted to use.&lt;br&gt;
What I Built: Smart Utils Desktop&lt;/p&gt;
&lt;p&gt;Smart Utils Desktop is a native application that combines the most common developer utilities into one clean, fast, offline-first app.&lt;br&gt;
JSON Studio&lt;/p&gt;
&lt;p&gt;This is the heart of the app. You can:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Format – Turn minified JSON into readable, indented structure

Minify – Compress JSON for production use

Validate – Catch syntax errors instantly

Tree View – Visualize nested data and copy any JSONPath

Generate Schema – Create JSON Schema from any document

Generate Code – Produce model classes for TypeScript, Java, Python, Go, Kotlin, and C#
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And unlike web-based tools, you can open and save files directly from your hard drive. The app handles JSON files up to 100MB without breaking a sweat.&lt;br&gt;
Regex Builder&lt;/p&gt;
&lt;p&gt;Regex is powerful, but testing it can be painful. Smart Utils gives you:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Live highlighting – See matches as you type

Pattern library – One-click templates for email, URL, UUID, numbers, words, whitespace

Flag controls – Toggle global, case-insensitive, multiline, and dotall modes

Match count – Instantly see how many matches were found

Save patterns – Keep your frequently-used expressions
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cron Builder&lt;/p&gt;
&lt;p&gt;Cron syntax is easy to forget. This tool makes it visual:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Field-by-field editor – Select minute, hour, day, month, weekday

Preset schedules – Every minute, hourly, daily at 9am, weekdays, monthly

Plain English – &quot;At 09:00 on every day-of-week from Monday through Friday&quot;

One-click copy – Grab the expression and paste it into your crontab
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CSV Converter (Desktop Exclusive)&lt;/p&gt;
&lt;p&gt;Business people love CSV. Developers love JSON. This feature bridges the gap:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;JSON to CSV – Convert any JSON array to CSV format

CSV to JSON – Import CSV files back to JSON for editing

Field mapping – Select exactly which fields to export

Delimiter options – Comma, semicolon, tab, or pipe
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why Desktop Instead of Web?&lt;/p&gt;
&lt;p&gt;This is the question I get asked most. Here's my honest answer:&lt;br&gt;
AspectWeb ToolSmart Utils Desktop&lt;br&gt;
PrivacyYour data goes to their serverYour data stays on your machine&lt;br&gt;
InternetRequiredWorks offline&lt;br&gt;
Large filesUsually failsHandles 100MB+&lt;br&gt;
AdsEverywhereNone&lt;br&gt;
File accessCan't save locallyOpen and save directly&lt;br&gt;
SpeedDepends on networkInstant&lt;/p&gt;
&lt;p&gt;I'm not saying web tools are bad. They're great for quick, one-off tasks. But for daily work? I want something that's always there, always fast, and always private.&lt;br&gt;
The Technical Stack&lt;/p&gt;
&lt;p&gt;If you're curious about how it's built:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Frontend: React 19 + Vite

Desktop framework: Electron (same as VS Code, Slack, and Discord)

Styling: Vanilla CSS with dark/light themes

CSV processing: PapaParse

Packaging: electron-builder
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The entire app is about 80MB – large enough to be feature-rich, small enough to download in seconds.&lt;br&gt;
What's Next?&lt;/p&gt;
&lt;p&gt;I'm actively developing Smart Utils. Here's what's coming:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;YAML support – Format and validate YAML files

JWT decoder – Inspect and verify JSON Web Tokens

Base64 tools – Encode and decode

SQL formatter – Pretty-print SQL queries

More code generators – Add Ruby, PHP, Swift support
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Smart Utils Desktop is available for macOS, Windows, and Linux.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Full license: $1 one-time payment

No subscription: Pay once, use forever
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://payhip.com/b/JamYL&quot;&gt;Download Smart Utils Desktop →&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The Bottom Line&lt;/p&gt;
&lt;p&gt;I built Smart Utils because I needed it. Every day, I use it for JSON formatting, regex testing, and cron scheduling. It saves me time, protects my privacy, and keeps my workflow smooth.&lt;/p&gt;
&lt;p&gt;If you're tired of juggling browser tabs and worrying about where your data is going, give it a try. The free trial costs nothing but a few minutes of your time.&lt;/p&gt;
&lt;p&gt;Your tools should work for you – not against you.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20010/stop-juggling-browser-tabs-why-i-built-a-desktop-app-for-json-regex-and-cron</guid>
<pubDate>Tue, 09 Jun 2026 06:05:41 +0000</pubDate>
</item>
<item>
<title>I built my first Chrome extension because editing localStorage was annoying</title>
<link>https://coderlegion.com/19618/i-built-my-first-chrome-extension-because-editing-localstorage-was-annoying</link>
<description>&lt;p&gt;I finally published my first Chrome extension.&lt;/p&gt;
&lt;p&gt;It is called &lt;strong&gt;DataSidekick&lt;/strong&gt;, and I built it because I got tired of fighting DevTools every time I needed to inspect or edit &lt;code&gt;localStorage&lt;/code&gt; and &lt;code&gt;sessionStorage&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/Snd6gJ1.png&quot; alt=&quot;DataSideKick opend view&quot;&gt;&lt;/p&gt;
&lt;p&gt;Also, yes, I paid the legendary &lt;strong&gt;$5 Chrome Web Store developer fee&lt;/strong&gt;, so emotionally I had to ship something. Otherwise, it would have been the worst investment portfolio of my life.&lt;/p&gt;
&lt;h3 id=&quot;what-is-datasidekick-&quot;&gt;What is DataSidekick?&lt;/h3&gt;
&lt;p&gt;DataSidekick is a Chrome extension that opens as a side panel and helps developers work with browser storage in a cleaner way.&lt;/p&gt;
&lt;p&gt;Instead of digging through DevTools and editing raw strings, you get a focused interface for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;viewing &lt;code&gt;localStorage&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;viewing &lt;code&gt;sessionStorage&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;searching by key or value&lt;/li&gt;
&lt;li&gt;editing simple values inline&lt;/li&gt;
&lt;li&gt;editing JSON visually&lt;/li&gt;
&lt;li&gt;importing data from JSON&lt;/li&gt;
&lt;li&gt;exporting data to JSON&lt;/li&gt;
&lt;li&gt;hiding noisy keys&lt;/li&gt;
&lt;li&gt;requesting access per site/origin&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can check it out here:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://datasidekick.site&quot;&gt;https://datasidekick.site&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;why-i-built-it&quot;&gt;Why I built it&lt;/h3&gt;
&lt;p&gt;Browser storage is simple until it is not.&lt;/p&gt;
&lt;p&gt;A lot of apps store useful state in &lt;code&gt;localStorage&lt;/code&gt;, but the default developer experience is still pretty rough:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{&quot;user&quot;:{&quot;name&quot;:&quot;Rodrigo&quot;,&quot;settings&quot;:{&quot;theme&quot;:&quot;dark&quot;}}}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Technically readable? Sure.&lt;/p&gt;
&lt;p&gt;Pleasant to edit? Absolutely not.&lt;/p&gt;
&lt;p&gt;I wanted something closer to a small developer cockpit: open the panel, find the key, inspect the value, edit it safely, and move on.&lt;/p&gt;
&lt;h3 id=&quot;the-feature-i-cared-about-most-visual-json-editing&quot;&gt;The feature I cared about most: visual JSON editing&lt;/h3&gt;
&lt;p&gt;The main idea was simple:&lt;/p&gt;
&lt;p&gt;If a value is valid JSON, don't treat it like a random string.&lt;/p&gt;
&lt;p&gt;So instead of this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{&quot;a&quot;:&quot;b&quot;,&quot;settings&quot;:{&quot;theme&quot;:&quot;dark&quot;}}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;DataSidekick displays structured JSON in a more readable way, so it becomes easier to inspect and edit nested values.&lt;/p&gt;
&lt;p&gt;That was the first “okay, this is actually useful” moment.&lt;/p&gt;
&lt;h3 id=&quot;chrome-permissions-were-the-real-boss-fight&quot;&gt;Chrome permissions were the real boss fight&lt;/h3&gt;
&lt;p&gt;The extension needs to read and edit storage from the current page. That means permissions matter.&lt;/p&gt;
&lt;p&gt;At first, I had an unnecessary permission in the manifest and the Chrome Web Store rejected the extension.&lt;/p&gt;
&lt;p&gt;Fair enough. My bad.&lt;/p&gt;
&lt;p&gt;The final approach is more intentional:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;no unnecessary &lt;code&gt;downloads&lt;/code&gt; permission&lt;/li&gt;
&lt;li&gt;site access is requested when needed&lt;/li&gt;
&lt;li&gt;the user sees when access is required&lt;/li&gt;
&lt;li&gt;data stays local in the browser&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For a tool that touches browser storage, I think this transparency is important.&lt;/p&gt;
&lt;h3 id=&quot;exporting-without-the-downloads-permission&quot;&gt;Exporting without the downloads permission&lt;/h3&gt;
&lt;p&gt;One small lesson: exporting a JSON file does not necessarily require the Chrome &lt;code&gt;downloads&lt;/code&gt; permission.&lt;/p&gt;
&lt;p&gt;This is enough for my use case:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const blob = new Blob([JSON.stringify(payload, null, 2)], {
  type: &quot;application/json&quot;
});

const url = URL.createObjectURL(blob);

const a = document.createElement(&quot;a&quot;);
a.href = url;
a.download = &quot;datasidekick-export.json&quot;;
a.click();

URL.revokeObjectURL(url);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since I was not using &lt;code&gt;chrome.downloads.download()&lt;/code&gt;, the permission was unnecessary.&lt;/p&gt;
&lt;p&gt;Chrome Web Store noticed. Chrome Web Store was right.&lt;/p&gt;
&lt;p&gt;Painful, but useful.&lt;/p&gt;
&lt;h3 id=&quot;current-features&quot;&gt;Current features&lt;/h3&gt;
&lt;p&gt;The first public version includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Chrome Side Panel UI&lt;/li&gt;
&lt;li&gt;&lt;code&gt;localStorage&lt;/code&gt; and &lt;code&gt;sessionStorage&lt;/code&gt; support&lt;/li&gt;
&lt;li&gt;visual JSON editor&lt;/li&gt;
&lt;li&gt;key/value search&lt;/li&gt;
&lt;li&gt;import/export JSON&lt;/li&gt;
&lt;li&gt;per-site access flow&lt;/li&gt;
&lt;li&gt;hidden keys&lt;/li&gt;
&lt;li&gt;noisy key filtering&lt;/li&gt;
&lt;li&gt;favorites&lt;/li&gt;
&lt;li&gt;dark/light mode&lt;/li&gt;
&lt;li&gt;font size controls&lt;/li&gt;
&lt;li&gt;playground on the website&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;the-playground&quot;&gt;The playground&lt;/h3&gt;
&lt;p&gt;I also added a playground to the site so people can understand the idea before installing anything.&lt;/p&gt;
&lt;p&gt;That was important because browser storage tools can feel a bit sensitive. I wanted people to see what the extension does before giving it access to a page.&lt;/p&gt;
&lt;p&gt;Try it here:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://datasidekick.site&quot;&gt;https://datasidekick.site&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;what-i-learned&quot;&gt;What I learned&lt;/h3&gt;
&lt;p&gt;A few things I took away from this first release:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Small dev tools are still worth building.&lt;/li&gt;
&lt;li&gt;Chrome extension permissions deserve real attention.&lt;/li&gt;
&lt;li&gt;A simple UX decision can matter more than a complex feature.&lt;/li&gt;
&lt;li&gt;Shipping to a store is very different from “it works locally”.&lt;/li&gt;
&lt;li&gt;Paying $5 makes you emotionally committed.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;what-s-next-&quot;&gt;What's next?&lt;/h3&gt;
&lt;p&gt;This is my first Chrome extension, but definitely not the last.&lt;/p&gt;
&lt;p&gt;Some ideas for future versions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;IndexedDB support&lt;/li&gt;
&lt;li&gt;better diff view&lt;/li&gt;
&lt;li&gt;undo/redo&lt;/li&gt;
&lt;li&gt;schema validation&lt;/li&gt;
&lt;li&gt;storage history&lt;/li&gt;
&lt;li&gt;better JSON editing interactions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For now, I am happy it is live.&lt;/p&gt;
&lt;p&gt;If you work with &lt;code&gt;localStorage&lt;/code&gt; or &lt;code&gt;sessionStorage&lt;/code&gt; often, I would love your feedback:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://datasidekick.site&quot;&gt;https://datasidekick.site&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Or plugin link: &lt;a rel=&quot;nofollow&quot; href=&quot;https://chromewebstore.google.com/detail/datasidekick/mjeonoddcckacjbeeaplmlnppacofgjb&quot;&gt;DataSideKick&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Built by a developer who just wanted to stop editing JSON like a caveman.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/19618/i-built-my-first-chrome-extension-because-editing-localstorage-was-annoying</guid>
<pubDate>Wed, 03 Jun 2026 22:23:18 +0000</pubDate>
</item>
<item>
<title>Stop Pasting JWTs Into Random Websites. Here's What I Built Instead.</title>
<link>https://coderlegion.com/19466/stop-pasting-jwts-into-random-websites-heres-what-i-built-instead</link>
<description>&lt;p&gt;&lt;em&gt;By Abu Sufyan · Lead Systems Architect, WebToolkit Pro&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Every week, thousands of developers do something they know they shouldn't.&lt;/p&gt;
&lt;p&gt;They open a new browser tab, navigate to some third-party JWT decoder, and paste a live production token directly into it.&lt;/p&gt;
&lt;p&gt;That token probably contains a user ID. Maybe a role claim. Maybe an internal service identifier that maps to something sensitive in your infrastructure. And it just left your browser.&lt;/p&gt;
&lt;p&gt;I know, because I used to do it too.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-incident-that-made-me-stop&quot;&gt;The Incident That Made Me Stop&lt;/h3&gt;
&lt;p&gt;In late 2024, I was debugging an auth issue on a client's Node.js API — a financial services platform. Standard stuff: the access token wasn't refreshing correctly and I needed to inspect the payload quickly to verify the &lt;code&gt;exp&lt;/code&gt; claim.&lt;/p&gt;
&lt;p&gt;I instinctively opened a well-known JWT decoder site and pasted the token.&lt;/p&gt;
&lt;p&gt;Then I stopped.&lt;/p&gt;
&lt;p&gt;I was working in a production environment. That token belonged to a real user — a client of theirs. It contained a &lt;code&gt;sub&lt;/code&gt; (subject) claim with a UUID that mapped directly to their user database. It contained an &lt;code&gt;iat&lt;/code&gt; and &lt;code&gt;exp&lt;/code&gt;. And most critically, it contained a &lt;code&gt;roles&lt;/code&gt; claim: &lt;code&gt;[&quot;admin&quot;, &quot;finance_read&quot;]&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I had just sent a production admin token to a third-party server I knew nothing about.&lt;/p&gt;
&lt;p&gt;The site claimed it was &quot;client-side only.&quot; But I couldn't verify that. There was no open-source repo I could inspect. No audit trail. No CSP headers I could check. Just a text box and a promise.&lt;/p&gt;
&lt;p&gt;That afternoon, I filed an internal incident report and started building a replacement.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-makes-a-jwt-decoder-dangerous&quot;&gt;What Makes a JWT Decoder Dangerous&lt;/h3&gt;
&lt;p&gt;Before I get to what I built, it's worth understanding exactly why this matters technically — not just philosophically.&lt;/p&gt;
&lt;p&gt;A JWT has three parts: header, payload, and signature. The first two are just Base64url-encoded JSON. Decoding them requires zero network traffic. It's pure string manipulation you can do in two lines of JavaScript:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const [header, payload] = token.split('.');
const decoded = JSON.parse(atob(payload.replace(/-/g, '+').replace(/_/g, '/')));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's it. There is no cryptographic reason a JWT decoder needs to contact any server whatsoever.&lt;/p&gt;
&lt;p&gt;And yet, when you paste a token into most online decoders, your token travels over the network to a server. That server may or may not be logging requests. It may be behind a CDN that caches POST bodies. It may be owned by a company whose threat model you have no visibility into.&lt;/p&gt;
&lt;p&gt;The attack surface is real:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Session hijacking&lt;/strong&gt; if the token is still valid and the attacker can replay it against your API&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Claim exposure&lt;/strong&gt; — user IDs, email addresses, role names, tenant identifiers&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Infrastructure mapping&lt;/strong&gt; — the &lt;code&gt;iss&lt;/code&gt; (issuer) claim often reveals internal service URLs or identity provider endpoints&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Algorithm fingerprinting&lt;/strong&gt; — the &lt;code&gt;alg&lt;/code&gt; header tells an attacker exactly what signing algorithm you're using, useful for crafting exploit attempts&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-architecture-zero-knowledge-client-side-decoding&quot;&gt;The Architecture: Zero-Knowledge Client-Side Decoding&lt;/h3&gt;
&lt;p&gt;What I built — the &lt;a rel=&quot;nofollow&quot; href=&quot;https://wtkpro.site/tools/jwt-decoder/&quot;&gt;Offline JWT Decoder at WebToolkit Pro&lt;/a&gt; — operates on a simple constraint: &lt;strong&gt;nothing ever leaves the browser tab&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Here's the actual execution model:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;User pastes token → Browser JS splits on '.'
                  → atob() decodes header + payload
                  → JSON.parse() reconstructs the object
                  → Rendered directly into DOM

No fetch(). No XMLHttpRequest(). No WebSocket. No Service Worker relay.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To verify this yourself: open DevTools → Network tab → paste a token → watch. Zero outbound requests. The network panel stays empty.&lt;/p&gt;
&lt;p&gt;The implementation uses the Web Crypto API for signature verification — which also runs entirely in the browser sandbox. There are no API keys, no backend, no telemetry. The Content Security Policy on the page blocks outbound requests at the browser level, so even if a malicious script injection somehow occurred, it couldn't exfiltrate the token.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;beyond-decoding-the-signature-verification-problem&quot;&gt;Beyond Decoding: The Signature Verification Problem&lt;/h3&gt;
&lt;p&gt;Most online JWT decoders only decode. They don't verify.&lt;/p&gt;
&lt;p&gt;Decoding tells you what's inside the token. Verification tells you whether to trust it.&lt;/p&gt;
&lt;p&gt;The difference matters enormously in debugging scenarios. If you're diagnosing an auth failure, you need to know:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Is the token malformed? (decoding fails)&lt;/li&gt;
&lt;li&gt;Is it expired? (check &lt;code&gt;exp&lt;/code&gt; vs current timestamp)&lt;/li&gt;
&lt;li&gt;Is the signature valid against the expected public key? (actual cryptographic verification)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Point 3 is where most tools fall apart. They skip it entirely, leaving you with no way to confirm whether the token was legitimately issued or tampered with.&lt;/p&gt;
&lt;p&gt;The &lt;a rel=&quot;nofollow&quot; href=&quot;https://wtkpro.site/tools/jwt-debugger/&quot;&gt;JWT Debugger &amp;amp; Inspector&lt;/a&gt; I built handles all three. You can paste your JWKS (JSON Web Key Set) endpoint URL or paste the raw public key directly, and the tool performs RS256/ES256 signature verification locally using &lt;code&gt;window.crypto.subtle.verify()&lt;/code&gt; — the same Web Crypto primitive your browser uses for HTTPS.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// What's happening under the hood
const key = await crypto.subtle.importKey(
  'jwk',
  jwkPublicKey,
  { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' },
  false,
  ['verify']
);

const isValid = await crypto.subtle.verify(
  'RSASSA-PKCS1-v1_5',
  key,
  signature,
  data
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No server. No SDK. Pure browser cryptography.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-alg-none-attack-why-your-decoder-needs-to-flag-this&quot;&gt;The &lt;code&gt;alg: none&lt;/code&gt; Attack — Why Your Decoder Needs to Flag This&lt;/h3&gt;
&lt;p&gt;While building this, I added one feature that I think every JWT tool should have: automatic detection of the &lt;code&gt;alg: none&lt;/code&gt; exploit vector.&lt;/p&gt;
&lt;p&gt;Here's the attack. Some JWT libraries — particularly older versions — accept tokens where the algorithm is set to &lt;code&gt;none&lt;/code&gt; and the signature is empty. An attacker who knows this can forge a token with any payload they want:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;// Header
{
  &quot;alg&quot;: &quot;none&quot;,
  &quot;typ&quot;: &quot;JWT&quot;
}
// Payload
{
  &quot;sub&quot;: &quot;1234567890&quot;,
  &quot;roles&quot;: [&quot;admin&quot;],
  &quot;iat&quot;: 1516239022
}
// Signature: empty string
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This gets concatenated as &lt;code&gt;base64(header).base64(payload).&lt;/code&gt; — note the trailing dot with no signature.&lt;/p&gt;
&lt;p&gt;The decoder flags this immediately with a warning: &lt;code&gt;⚠ Algorithm &quot;none&quot; detected. This token carries no cryptographic integrity guarantee. Reject in production.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;It's a small thing, but it's the kind of context-aware output you only get from a tool built by someone who actually works with these tokens in production.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-i-learned-building-privacy-first-tools&quot;&gt;What I Learned Building Privacy-First Tools&lt;/h3&gt;
&lt;p&gt;This JWT decoder is one of 150+ tools I've built at &lt;a rel=&quot;nofollow&quot; href=&quot;https://wtkpro.site&quot;&gt;WebToolkit Pro&lt;/a&gt;. Every single one follows the same constraint: &lt;strong&gt;your data never leaves your browser&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Password generator. Hash generator. Base64 encoder. Regex tester. JSON formatter. AES encryption tool. All of them execute entirely client-side.&lt;/p&gt;
&lt;p&gt;Building this way taught me a few things:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;WebAssembly changed what's possible.&lt;/strong&gt; Operations that used to require a server — like Argon2 password hashing, which is deliberately memory-intensive — can now run in the browser via WASM. The &lt;a rel=&quot;nofollow&quot; href=&quot;https://wtkpro.site/tools/argon2-hasher/&quot;&gt;Argon2 Hasher&lt;/a&gt; generates production-grade password hashes locally in under a second.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Web Workers are underused.&lt;/strong&gt; For tools that process large inputs (the JSON formatter handles payloads up to 50MB), moving the parsing off the main thread via Web Workers keeps the UI responsive. No spinners, no freezes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The hardest part isn't the crypto — it's the UX.&lt;/strong&gt; Security tools have a reputation for being hostile to use. I spent more time on the output formatting and error messaging than on the underlying algorithms. A tool that correctly identifies an &lt;code&gt;exp&lt;/code&gt; claim mismatch but displays it as a raw timestamp integer has failed its user.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;try-it-yourself&quot;&gt;Try It Yourself&lt;/h3&gt;
&lt;p&gt;If you're debugging JWTs today, open DevTools first. Network tab, record. Then go to &lt;a rel=&quot;nofollow&quot; href=&quot;https://wtkpro.site/tools/jwt-decoder/&quot;&gt;wtkpro.site/tools/jwt-decoder&lt;/a&gt; and paste your token.&lt;/p&gt;
&lt;p&gt;Watch the network panel stay silent.&lt;/p&gt;
&lt;p&gt;That silence is the feature.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;links-2&quot;&gt;Links&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://wtkpro.site/tools/jwt-decoder/&quot;&gt;Offline JWT Decoder&lt;/a&gt; — decode without sending tokens to any server&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://wtkpro.site/tools/jwt-debugger/&quot;&gt;JWT Debugger &amp;amp; Inspector&lt;/a&gt; — full signature verification with JWKS support&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://wtkpro.site/tools/jwt-signer/&quot;&gt;JWT Signing Tool&lt;/a&gt; — generate signed tokens locally for testing&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://wtkpro.site/tools/password-entropy-tester/&quot;&gt;Password Entropy Tester&lt;/a&gt; — offline entropy calculation and crack time estimates&lt;/li&gt;
&lt;li&gt;Full tool directory: &lt;a rel=&quot;nofollow&quot; href=&quot;https://wtkpro.site/tools/&quot;&gt;wtkpro.site/tools&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Founder of &lt;a rel=&quot;nofollow&quot; href=&quot;https://wtkpro.site&quot;&gt;WebToolkit Pro&lt;/a&gt; and a systems architect specializing in V8 performance, security tooling, and zero-knowledge web architectures. Find him on &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/abusufyan-netizen&quot;&gt;GitHub&lt;/a&gt; and &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/webtoolkitpro&quot;&gt;Dev.to&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; &lt;code&gt;security&lt;/code&gt; &lt;code&gt;jwt&lt;/code&gt; &lt;code&gt;webdev&lt;/code&gt; &lt;code&gt;javascript&lt;/code&gt; &lt;code&gt;privacy&lt;/code&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/19466/stop-pasting-jwts-into-random-websites-heres-what-i-built-instead</guid>
<pubDate>Tue, 02 Jun 2026 06:43:46 +0000</pubDate>
</item>
<item>
<title>How Does Duolingo Monetize? I Decompiled the Android App (v6.79.5)</title>
<link>https://coderlegion.com/19193/how-does-duolingo-monetize-i-decompiled-the-android-app-v6-79-5</link>
<description>&lt;p&gt;I wanted to know how Duolingo makes money on Android -- ads vs Super subscription, which SDKs they use, and how aggressive the free tier is. So I decompiled &lt;strong&gt;Duolingo 6.79.5&lt;/strong&gt; (164 MB APK, 11 DEX files, 242 activities).&lt;/p&gt;
&lt;h3 id=&quot;tl-dr-1&quot;&gt;TL;DR&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;IronSource mediation&lt;/strong&gt; sits at the center -- Unity Ads, Vungle, Moloco, AdMob, Facebook, and Pangle all plug in around it&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Facebook Audience Network&lt;/strong&gt; ships as a &lt;strong&gt;5 MB hidden DEX&lt;/strong&gt; in assets (same trick as CamScanner)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;7+ ad networks&lt;/strong&gt; for free users; &lt;strong&gt;Google Play Billing&lt;/strong&gt; for Super/Plus (no RevenueCat found)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kotlin + Jetpack Compose&lt;/strong&gt; for the main app; &lt;strong&gt;Unity&lt;/strong&gt; for some game modules (chess, etc.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Staging APIs&lt;/strong&gt; (&lt;code&gt;android-api-stage&lt;/code&gt;, &lt;code&gt;goals-api-stage-*&lt;/code&gt;) are hardcoded in the production build&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Adjust&lt;/strong&gt; + &lt;strong&gt;Sentry&lt;/strong&gt; (+ Firebase) for attribution and crashes&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;how-duolingo-makes-money&quot;&gt;How Duolingo Makes Money&lt;/h3&gt;
&lt;h4 id=&quot;subscriptions-super-plus-&quot;&gt;Subscriptions (Super / Plus)&lt;/h4&gt;
&lt;p&gt;Duolingo uses &lt;strong&gt;Google Play Billing&lt;/strong&gt; directly -- &lt;code&gt;ProxyBillingActivity&lt;/code&gt;, &lt;code&gt;PlusPurchaseFlowActivity&lt;/code&gt;, strings for gems and family plans. Deep links hit &lt;code&gt;duolingo.com/settings/super&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I did &lt;strong&gt;not&lt;/strong&gt; find RevenueCat or Superwall in the DEX. Subscription logic appears first-party on top of Google's billing library.&lt;/p&gt;
&lt;h4 id=&quot;ads-free-tier-&quot;&gt;Ads (free tier)&lt;/h4&gt;
&lt;p&gt;Free users hit a &lt;strong&gt;mediation-heavy&lt;/strong&gt; stack:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Network &lt;/th&gt;&lt;th&gt;Role&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;IronSource &lt;/td&gt;&lt;td&gt;Mediation hub (most SDK surface area)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Unity Ads &lt;/td&gt;&lt;td&gt;Mediated demand + game ad units&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Google AdMob &lt;/td&gt;&lt;td&gt;Google demand, MRAID native templates&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Facebook AN &lt;/td&gt;&lt;td&gt;Hidden &lt;code&gt;audience_network.dex&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Pangle (TikTok) &lt;/td&gt;&lt;td&gt;Rewarded, interstitial, app open&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Vungle &lt;/td&gt;&lt;td&gt;Video&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Moloco &lt;/td&gt;&lt;td&gt;Programmatic / MRAID / VAST&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Duolingo-owned ad screens include &lt;code&gt;CustomNativeAdActivity&lt;/code&gt;, &lt;code&gt;FullscreenNetworkNativeAdActivity&lt;/code&gt;, and &lt;code&gt;PlusPromoVideoActivity&lt;/code&gt; -- ads show up at &lt;strong&gt;session end&lt;/strong&gt; and league flows, not random webviews.&lt;/p&gt;
&lt;h4 id=&quot;the-hidden-facebook-dex-again-&quot;&gt;The hidden Facebook DEX (again)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;assets/audience_network.dex  ~5 MB
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Meta's SDK is not fully embedded in the main multidex load. It is loaded from assets when needed -- a common pattern to protect cold start on huge apps.&lt;/p&gt;
&lt;h3 id=&quot;tech-stack-what-they-built-with-&quot;&gt;Tech Stack (what they built with)&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Layer &lt;/th&gt;&lt;th&gt;Choice&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;UI &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Kotlin + Jetpack Compose&lt;/strong&gt; (thousands of references)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Games &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Unity&lt;/strong&gt; (&lt;code&gt;UnityActivity&lt;/code&gt;, chess promo assets)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Network &lt;/td&gt;&lt;td&gt;OkHttp, Retrofit&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Media &lt;/td&gt;&lt;td&gt;ExoPlayer, Lottie, Rive&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Attribution &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Adjust&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Crashes &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Sentry&lt;/strong&gt; (&lt;code&gt;duolingo-sentry.sentry.io&lt;/code&gt;) + Firebase&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Support &lt;/td&gt;&lt;td&gt;Zendesk&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Min SDK &lt;strong&gt;29&lt;/strong&gt; -- they have dropped pre-Android 10 devices.&lt;/p&gt;
&lt;h3 id=&quot;staging-servers-in-production&quot;&gt;Staging servers in production&lt;/h3&gt;
&lt;p&gt;Same class of mistake as other big apps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;android-api-stage.duolingo.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;goals-api-stage.duolingo.com&lt;/code&gt; (+ stage-2, stage-3)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;infra-edge-gateway-stage-cf.duolingo.com&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Useful if you are researching API behavior; risky if those hosts expose looser auth.&lt;/p&gt;
&lt;h3 id=&quot;duolingo-vs-camscanner-quick-compare-&quot;&gt;Duolingo vs CamScanner (quick compare)&lt;/h3&gt;
&lt;p&gt;I &lt;a rel=&quot;nofollow&quot; href=&quot;https://appxray.blackorange.org/case/camscanner.html&quot;&gt;decompiled CamScanner earlier&lt;/a&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;CamScanner &lt;/th&gt;&lt;th&gt;Duolingo&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Mediation lead &lt;/td&gt;&lt;td&gt;AdMob + PubMatic bidding &lt;/td&gt;&lt;td&gt;IronSource + Unity&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;UI &lt;/td&gt;&lt;td&gt;Native + Flutter &lt;/td&gt;&lt;td&gt;Compose + Unity games&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Ad networks &lt;/td&gt;&lt;td&gt;6 &lt;/td&gt;&lt;td&gt;7+&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Hidden Meta DEX &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Education apps and utility apps converge on the same playbook: &lt;strong&gt;many networks, one mediator, lazy-load Meta&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;what-i-left-out-of-this-post&quot;&gt;What I left out of this post&lt;/h3&gt;
&lt;p&gt;The full report includes 60+ first-party API hosts, complete permission breakdown, schools/DET endpoints, and module-level Activity mapping. This post is the highlights.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;I run &lt;a rel=&quot;nofollow&quot; href=&quot;https://appxray.blackorange.org&quot;&gt;AppXray&lt;/a&gt;&lt;/strong&gt; -- send any Google Play link, get a full reverse-engineering report (PDF + Markdown) in ~2 hours. $29 for one app, $19/app for batches.&lt;/p&gt;
&lt;p&gt;Free samples: &lt;a rel=&quot;nofollow&quot; href=&quot;https://appxray.blackorange.org/case/camscanner.html&quot;&gt;CamScanner teardown&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Questions about Duolingo's stack? Comment below.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/19193/how-does-duolingo-monetize-i-decompiled-the-android-app-v6-79-5</guid>
<pubDate>Fri, 29 May 2026 03:14:43 +0000</pubDate>
</item>
<item>
<title>Introducing NitroIDE — Building a Faster Browser-Based IDE for Developers</title>
<link>https://coderlegion.com/18912/introducing-nitroide-building-a-faster-browser-based-ide-for-developers</link>
<description>&lt;h2 id=&quot;introducing-nitroide-engineering-a-faster-browser-ide-for-modern-developers&quot;&gt;Introducing NitroIDE — Engineering a Faster Browser IDE for Modern Developers&lt;/h2&gt;
&lt;p&gt;Hey everyone &lt;/p&gt;
&lt;p&gt;Over the past few months, I’ve been working on a project called NitroIDE — a modern browser-based IDE focused on performance, low latency, and creating a smoother developer experience directly inside the browser.&lt;/p&gt;
&lt;p&gt;The idea behind NitroIDE came from a simple frustration:&lt;/p&gt;
&lt;p&gt;Most online code editors still don’t fully feel “native.”&lt;/p&gt;
&lt;p&gt;Even though browser technology has improved massively over the years, many web-based development environments still suffer from small delays, cluttered interfaces, unnecessary complexity, and workflows that interrupt developer focus.&lt;/p&gt;
&lt;p&gt;As developers, we spend hours every day inside editors.&lt;/p&gt;
&lt;p&gt;Even tiny interruptions in responsiveness, animation smoothness, typing latency, navigation speed, or UI clarity can slowly reduce productivity and break flow state.&lt;/p&gt;
&lt;p&gt;That’s the problem I wanted to explore.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;why-build-another-browser-ide-&quot;&gt;Why Build Another Browser IDE?&lt;/h2&gt;
&lt;p&gt;Traditional online editors are often designed around remote execution and cloud infrastructure first.&lt;/p&gt;
&lt;p&gt;While cloud-based development is incredibly powerful, the frontend experience itself sometimes becomes secondary.&lt;/p&gt;
&lt;p&gt;I wanted to experiment with a different philosophy:&lt;/p&gt;
&lt;p&gt;What if a browser IDE focused heavily on responsiveness, fluid interaction, and developer flow?&lt;/p&gt;
&lt;p&gt;NitroIDE is my attempt at building a development environment that feels:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fast&lt;/li&gt;
&lt;li&gt;Lightweight&lt;/li&gt;
&lt;li&gt;Modern&lt;/li&gt;
&lt;li&gt;Responsive&lt;/li&gt;
&lt;li&gt;Distraction-free&lt;/li&gt;
&lt;li&gt;Smooth to interact with&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The goal isn’t just to “run code in the browser.”&lt;/p&gt;
&lt;p&gt;The goal is to make browser development feel enjoyable.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;core-areas-i-m-focusing-on&quot;&gt;Core Areas I’m Focusing On&lt;/h2&gt;
&lt;h3 id=&quot;-frontend-performance-optimization&quot;&gt;⚡ Frontend Performance Optimization&lt;/h3&gt;
&lt;p&gt;Performance is one of the biggest priorities in NitroIDE.&lt;/p&gt;
&lt;p&gt;I’ve been experimenting with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Faster rendering workflows&lt;/li&gt;
&lt;li&gt;Optimized UI updates&lt;/li&gt;
&lt;li&gt;Efficient state management&lt;/li&gt;
&lt;li&gt;Lightweight interaction systems&lt;/li&gt;
&lt;li&gt;Reduced visual clutter&lt;/li&gt;
&lt;li&gt;Smooth editor responsiveness&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Even small optimizations can dramatically improve how an application feels during daily use.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;developer-flow-amp-ux&quot;&gt;Developer Flow &amp;amp; UX&lt;/h3&gt;
&lt;p&gt;One thing I’ve realized while building products is that developer experience is deeply psychological.&lt;/p&gt;
&lt;p&gt;Good tools help you stay focused.&lt;/p&gt;
&lt;p&gt;Bad tools constantly interrupt your thinking.&lt;/p&gt;
&lt;p&gt;NitroIDE is heavily focused on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cleaner interface design&lt;/li&gt;
&lt;li&gt;Better visual hierarchy&lt;/li&gt;
&lt;li&gt;Reduced distractions&lt;/li&gt;
&lt;li&gt;Faster interactions&lt;/li&gt;
&lt;li&gt;Simpler workflows&lt;/li&gt;
&lt;li&gt;Minimal friction between idea and execution&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I want the editor to feel almost invisible while coding.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;browser-based-development-is-evolving&quot;&gt;Browser-Based Development Is Evolving&lt;/h2&gt;
&lt;p&gt;Modern browsers are becoming incredibly capable platforms.&lt;/p&gt;
&lt;p&gt;With advancements in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;WebAssembly&lt;/li&gt;
&lt;li&gt;Real-time rendering&lt;/li&gt;
&lt;li&gt;Modern JavaScript engines&lt;/li&gt;
&lt;li&gt;Edge infrastructure&lt;/li&gt;
&lt;li&gt;Browser APIs&lt;/li&gt;
&lt;li&gt;Real-time collaboration systems&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;…it feels like browser-based development tools are entering a completely new era.&lt;/p&gt;
&lt;p&gt;NitroIDE is partly an exploration of what modern web technologies can achieve when performance and UX become first-class priorities.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;technologies-amp-areas-i-m-exploring&quot;&gt;Technologies &amp;amp; Areas I’m Exploring&lt;/h2&gt;
&lt;p&gt;Some of the areas involved while building NitroIDE include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Full-stack web development&lt;/li&gt;
&lt;li&gt;Frontend architecture&lt;/li&gt;
&lt;li&gt;Real-time web systems&lt;/li&gt;
&lt;li&gt;React &amp;amp; Next.js workflows&lt;/li&gt;
&lt;li&gt;TypeScript-based architecture&lt;/li&gt;
&lt;li&gt;WebSockets &amp;amp; live systems&lt;/li&gt;
&lt;li&gt;Browser performance engineering&lt;/li&gt;
&lt;li&gt;UI/UX systems&lt;/li&gt;
&lt;li&gt;Developer tooling&lt;/li&gt;
&lt;li&gt;Responsive design systems&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The project continues evolving as I experiment, learn, and improve the architecture over time.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;challenges-of-building-developer-tools&quot;&gt;Challenges of Building Developer Tools&lt;/h2&gt;
&lt;p&gt;Building developer tools is honestly one of the most interesting technical challenges I’ve worked on.&lt;/p&gt;
&lt;p&gt;Developers notice everything:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Latency&lt;/li&gt;
&lt;li&gt;Interaction quality&lt;/li&gt;
&lt;li&gt;UI consistency&lt;/li&gt;
&lt;li&gt;Keyboard responsiveness&lt;/li&gt;
&lt;li&gt;Layout behavior&lt;/li&gt;
&lt;li&gt;Animation smoothness&lt;/li&gt;
&lt;li&gt;Workflow friction&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Small details matter much more in developer products than many people realize.&lt;/p&gt;
&lt;p&gt;That’s also what makes building them exciting.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;long-term-vision&quot;&gt;Long-Term Vision&lt;/h2&gt;
&lt;p&gt;My long-term vision for NitroIDE is to continue improving the browser development experience while experimenting with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Faster workflows&lt;/li&gt;
&lt;li&gt;Better performance&lt;/li&gt;
&lt;li&gt;Real-time collaboration&lt;/li&gt;
&lt;li&gt;AI-assisted development workflows&lt;/li&gt;
&lt;li&gt;Modern editor experiences&lt;/li&gt;
&lt;li&gt;More fluid developer interaction systems&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I’m still continuously building, learning, testing ideas, and refining the platform.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;final-thoughts-3&quot;&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;This is still an evolving project, but I wanted to finally share it publicly and connect with other developers who are interested in modern web tooling, browser IDEs, frontend performance, and developer experience engineering.&lt;/p&gt;
&lt;p&gt;I’d genuinely appreciate feedback, ideas, suggestions, or even criticism from fellow developers.&lt;/p&gt;
&lt;p&gt;Website: &lt;a rel=&quot;nofollow&quot; href=&quot;https://nitroide.com&quot;&gt;https://nitroide.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Thanks for reading &lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/18912/introducing-nitroide-building-a-faster-browser-based-ide-for-developers</guid>
<pubDate>Mon, 25 May 2026 17:32:29 +0000</pubDate>
</item>
<item>
<title>Building a Production-Ready .NET 9 &amp; React SaaS Core (With Global Tenant Isolation)</title>
<link>https://coderlegion.com/18000/building-a-production-ready-net-9-react-saas-core-with-global-tenant-isolation</link>
<description>&lt;p&gt;Hey everyone!&lt;/p&gt;
&lt;p&gt;Let’s be honest: the absolute worst part of starting a brand new SaaS idea is the first 40 hours of setup. Before you can even build the actual feature you're excited about, your momentum gets completely killed because you're stuck doing the boring stuff.&lt;/p&gt;
&lt;p&gt;You have to configure JWT refresh tokens, make sure your database won't leak data between users, build a team invitation flow, and wrestle with Stripe webhook signatures.&lt;/p&gt;
&lt;p&gt;I finally got tired of repeating this cycle every single time I wanted to launch something. So, over the last few weeks, I locked myself in a room and built a production-ready foundation using a Clean Architecture .NET 9 Web API and a super clean, dark-mode React + Tailwind CSS frontend.&lt;/p&gt;
&lt;p&gt;I wanted to share a quick look at how it's put together under the hood!&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;how-it-handles-the-tricky-parts&quot;&gt;How it handles the tricky parts&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. Simple, Secure Auth (No bloated packages)&lt;/strong&gt;&lt;br&gt;
Instead of fighting with massive, monolithic identity frameworks that are impossible to customize, I went with a lightweight JWT setup. It uses short-lived access tokens alongside database-backed refresh tokens. It keeps user sessions perfectly alive without sacrificing an ounce of security.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Bulletproof Tenant Isolation&lt;/strong&gt;&lt;br&gt;
Data leaking between companies is a total nightmare. To fix this, the database layer hooks into Entity Framework Core's global query filters.&lt;/p&gt;
&lt;p&gt;Basically, the backend automatically sniffs out the TenantId from the user's secure token and injects it into every database query. You don't have to remember to type .Where(x =&amp;gt; x.TenantId == current) on every single controller—it just works natively.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Plug-and-Play Stripe Webhooks&lt;/strong&gt;&lt;br&gt;
Handling asynchronous billing states can get messy fast. I set up a dedicated endpoint that listens for Stripe events (like successful checkouts or canceled subscriptions), verifies the webhook signature using your .env keys to keep things secure, and instantly flips the user's dashboard access state in real-time.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;want-to-skip-the-plumbing-work-this-weekend-&quot;&gt;Want to skip the plumbing work this weekend?&lt;/h3&gt;
&lt;p&gt;I've packaged this entire engine into a minimal starter kit called SaaSGenesis. It includes a beautiful, high-end dashboard layout (I even cleared out the traditional, clunky client lists so the UI stays focused strictly on your core modules).&lt;/p&gt;
&lt;p&gt;It’s officially live on Gumroad for $49, but I really want to get some honest feedback from fellow developers who are building cool stuff right now.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I’m giving away exactly 5 copies for 100% FREE.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you are planning to build or launch something soon and want to skip a week of foundational setup:&lt;/p&gt;
&lt;p&gt;Take a look at the project details here: &lt;a rel=&quot;nofollow&quot; href=&quot;https://yaman95.gumroad.com/l/saas-genesis&quot;&gt;https://yaman95.gumroad.com/l/saas-genesis&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/18000/building-a-production-ready-net-9-react-saas-core-with-global-tenant-isolation</guid>
<pubDate>Fri, 15 May 2026 18:16:57 +0000</pubDate>
</item>
<item>
<title>Building HOLO.SYS — A Futuristic 3D Holographic Visualization System with React, Three.js &amp; GLSL</title>
<link>https://coderlegion.com/17092/building-holo-sys-futuristic-3d-holographic-visualization-system-with-react-three-glsl</link>
<description>&lt;h3 id=&quot;introduction-4&quot;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;Modern web development is rapidly evolving beyond traditional websites and static user interfaces. Today, developers are building immersive digital experiences directly inside the browser using technologies like WebGL, Three.js, GLSL shaders and advanced Web architectures.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;As a Full Stack Web Developer, I always wanted to explore the creative side of Web engineering — where design, interaction, animation, performance and GPU rendering come together to create something visually powerful.&lt;/p&gt;
&lt;p&gt;That vision led me to build &lt;strong&gt;HOLO.SYS&lt;/strong&gt; — a futuristic cyberpunk-inspired 3D holographic visualization system developed using:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;React.js&lt;/li&gt;
&lt;li&gt;Three.js&lt;/li&gt;
&lt;li&gt;GLSL Shader Programming&lt;/li&gt;
&lt;li&gt;GSAP Animations&lt;/li&gt;
&lt;li&gt;WebGL Rendering&lt;/li&gt;
&lt;li&gt;Modern JavaScript Architecture&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This project is not just another Web experiment.&lt;br&gt;
It is a complete creative Web engineering project focused on immersive web experiences, real-time rendering, shader effects, and cinematic UI systems.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-is-holo-sys-&quot;&gt;What is HOLO.SYS?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;HOLO.SYS&lt;/strong&gt; is a real-time interactive holographic visualization system inspired by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cyberpunk interfaces&lt;/li&gt;
&lt;li&gt;Futuristic HUD systems&lt;/li&gt;
&lt;li&gt;Sci-fi holographic displays&lt;/li&gt;
&lt;li&gt;AI visualization panels&lt;/li&gt;
&lt;li&gt;Advanced UI/UX concepts&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The goal of this project was to simulate a futuristic holographic rendering system directly inside the browser using modern Web technologies.&lt;/p&gt;
&lt;p&gt;The application features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Real-time 3D rendering&lt;/li&gt;
&lt;li&gt;Interactive camera controls&lt;/li&gt;
&lt;li&gt;Dynamic holographic shader effects&lt;/li&gt;
&lt;li&gt;Scanline visual systems&lt;/li&gt;
&lt;li&gt;Animated glitch transitions&lt;/li&gt;
&lt;li&gt;Fresnel rim glow lighting&lt;/li&gt;
&lt;li&gt;Smooth geometry morph transitions&lt;/li&gt;
&lt;li&gt;Live GUI customization controls&lt;/li&gt;
&lt;li&gt;Performance optimized rendering&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This project represents the combination of:&lt;/p&gt;
&lt;p&gt;✅ Creative Development&lt;/p&gt;
&lt;p&gt;✅ Modern Frontend Engineering&lt;/p&gt;
&lt;p&gt;✅ WebGL Rendering&lt;/p&gt;
&lt;p&gt;✅ Real-Time Graphics&lt;/p&gt;
&lt;p&gt;✅ Interactive UI Systems&lt;/p&gt;
&lt;p&gt;✅ Shader Programming&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;live-project&quot;&gt;Live Project&lt;/h2&gt;
&lt;h3 id=&quot;live-demo&quot;&gt;Live Demo&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://holographic-threejs.vercel.app/&quot;&gt;https://holographic-threejs.vercel.app/&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;github-repository&quot;&gt;GitHub Repository&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/YasirAwan4831/holographic-shader-visualizer-three.Js&quot;&gt;https://github.com/YasirAwan4831/holographic-shader-visualizer-three.Js&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;github-profile&quot;&gt;GitHub Profile&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/YasirAwan4831&quot;&gt;https://github.com/YasirAwan4831&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-technologies-used&quot;&gt;⚛️ Technologies Used&lt;/h2&gt;
&lt;p&gt;One of the most exciting parts of this project was combining multiple modern frontend technologies together.&lt;/p&gt;
&lt;h3 id=&quot;react-js&quot;&gt;React.js&lt;/h3&gt;
&lt;p&gt;React was used to structure the application into reusable and scalable components.&lt;/p&gt;
&lt;p&gt;It helped manage:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Component architecture&lt;/li&gt;
&lt;li&gt;UI organization&lt;/li&gt;
&lt;li&gt;Application structure&lt;/li&gt;
&lt;li&gt;Rendering lifecycle&lt;/li&gt;
&lt;li&gt;Future scalability&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;React makes it much easier to build modern interactive web applications while keeping the codebase clean and maintainable.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;three-js&quot;&gt;Three.js&lt;/h3&gt;
&lt;p&gt;Three.js was responsible for handling:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;3D scene creation&lt;/li&gt;
&lt;li&gt;Geometry rendering&lt;/li&gt;
&lt;li&gt;Camera systems&lt;/li&gt;
&lt;li&gt;Lighting setup&lt;/li&gt;
&lt;li&gt;Mesh transformations&lt;/li&gt;
&lt;li&gt;WebGL abstraction&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Instead of manually writing low-level WebGL code, Three.js provides a powerful abstraction layer that allows developers to create advanced 3D experiences much faster.&lt;/p&gt;
&lt;p&gt;The project includes multiple 3D geometries such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Torus Knot&lt;/li&gt;
&lt;li&gt;Icosahedron&lt;/li&gt;
&lt;li&gt;Torus Geometry&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These objects transition dynamically with holographic effects.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;glsl-shader-programming&quot;&gt;GLSL Shader Programming&lt;/h3&gt;
&lt;p&gt;One of the most advanced parts of this project is the custom GLSL shader system.&lt;/p&gt;
&lt;p&gt;The shaders were handwritten to create:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Holographic scanlines&lt;/li&gt;
&lt;li&gt;Dynamic glitch displacement&lt;/li&gt;
&lt;li&gt;Fresnel rim glow&lt;/li&gt;
&lt;li&gt;Transparency falloff&lt;/li&gt;
&lt;li&gt;Transition masking&lt;/li&gt;
&lt;li&gt;Animated distortion systems&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;vertex-shader-features&quot;&gt;Vertex Shader Features&lt;/h4&gt;
&lt;p&gt;The vertex shader manipulates geometry positions in real-time.&lt;/p&gt;
&lt;p&gt;It creates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Glitch wave displacement&lt;/li&gt;
&lt;li&gt;Randomized distortion&lt;/li&gt;
&lt;li&gt;Progress-based transition effects&lt;/li&gt;
&lt;li&gt;Animated geometry movement&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;fragment-shader-features&quot;&gt;Fragment Shader Features&lt;/h4&gt;
&lt;p&gt;The fragment shader controls:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pixel colouring&lt;/li&gt;
&lt;li&gt;Hologram transparency&lt;/li&gt;
&lt;li&gt;Glow intensity&lt;/li&gt;
&lt;li&gt;Fresnel lighting&lt;/li&gt;
&lt;li&gt;Scanline rendering&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This combination creates the cinematic holographic appearance visible in the final system.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;gsap-animations&quot;&gt;GSAP Animations&lt;/h3&gt;
&lt;p&gt;GSAP was used to create smooth and performant animations.&lt;/p&gt;
&lt;p&gt;It powers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Geometry transitions&lt;/li&gt;
&lt;li&gt;Shader progress animations&lt;/li&gt;
&lt;li&gt;Smooth effect timing&lt;/li&gt;
&lt;li&gt;UI interaction flow&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;GSAP provides better control and smoother animations compared to many traditional animation libraries.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;webgl-rendering&quot;&gt;WebGL Rendering&lt;/h3&gt;
&lt;p&gt;WebGL is the foundation behind the entire rendering pipeline.&lt;/p&gt;
&lt;p&gt;It enables GPU acceleration directly inside the browser.&lt;/p&gt;
&lt;p&gt;This allows the project to render:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Real-time graphics&lt;/li&gt;
&lt;li&gt;Interactive 3D systems&lt;/li&gt;
&lt;li&gt;Shader effects&lt;/li&gt;
&lt;li&gt;Dynamic animations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;without needing external software.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;major-features-of-holo-sys&quot;&gt;Major Features of HOLO.SYS&lt;/h2&gt;
&lt;h3 id=&quot;real-time-holographic-rendering&quot;&gt;Real-Time Holographic Rendering&lt;/h3&gt;
&lt;p&gt;The project creates a futuristic holographic appearance using:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Transparency blending&lt;/li&gt;
&lt;li&gt;Additive rendering&lt;/li&gt;
&lt;li&gt;Glow effects&lt;/li&gt;
&lt;li&gt;Scanline overlays&lt;/li&gt;
&lt;li&gt;Animated distortion&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The final output feels similar to sci-fi hologram systems seen in futuristic films and games.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;dynamic-glitch-effects&quot;&gt;Dynamic Glitch Effects&lt;/h3&gt;
&lt;p&gt;A custom glitch engine was developed using mathematical shader calculations.&lt;/p&gt;
&lt;p&gt;The glitch system includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Time-based sine waves&lt;/li&gt;
&lt;li&gt;Randomized displacement&lt;/li&gt;
&lt;li&gt;Transition distortion&lt;/li&gt;
&lt;li&gt;Multi-layered visual noise&lt;/li&gt;
&lt;li&gt;Animated geometry corruption&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These effects make the hologram feel alive and dynamic.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;fresnel-rim-glow&quot;&gt;Fresnel Rim Glow&lt;/h3&gt;
&lt;p&gt;A Fresnel lighting system was implemented to create edge glow effects based on viewing angle.&lt;/p&gt;
&lt;p&gt;This adds:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Depth perception&lt;/li&gt;
&lt;li&gt;Futuristic glow&lt;/li&gt;
&lt;li&gt;Cinematic visuals&lt;/li&gt;
&lt;li&gt;Enhanced realism&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is one of the most visually impactful effects in the project.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;animated-object-transitions&quot;&gt;Animated Object Transitions&lt;/h3&gt;
&lt;p&gt;The application cycles through multiple 3D geometries using shader-based transition masking.&lt;/p&gt;
&lt;p&gt;Instead of instantly switching objects, the project creates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Smooth vertical sweep transitions&lt;/li&gt;
&lt;li&gt;Controlled visibility masking&lt;/li&gt;
&lt;li&gt;Cinematic object replacement&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This creates a much more polished and professional visual experience.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;interactive-gui-controls&quot;&gt;Interactive GUI Controls&lt;/h3&gt;
&lt;p&gt;A real-time GUI panel was implemented using lil-gui.&lt;/p&gt;
&lt;p&gt;Users can dynamically change:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hologram colors&lt;/li&gt;
&lt;li&gt;Stage colors&lt;/li&gt;
&lt;li&gt;Ambient lighting&lt;/li&gt;
&lt;li&gt;Directional lighting&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This makes the system interactive and customizable.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;performance-optimization&quot;&gt;Performance Optimization&lt;/h3&gt;
&lt;p&gt;Performance was a major focus during development.&lt;/p&gt;
&lt;p&gt;Optimization techniques include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;request Animation Frame loops&lt;/li&gt;
&lt;li&gt;Controlled DPR scaling&lt;/li&gt;
&lt;li&gt;Efficient shader calculations&lt;/li&gt;
&lt;li&gt;Proper clean-up systems&lt;/li&gt;
&lt;li&gt;Memory leak prevention&lt;/li&gt;
&lt;li&gt;Optimized rendering pipeline&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These improvements help maintain smooth rendering performance.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-i-learned-from-this-project&quot;&gt;What I Learned From This Project&lt;/h2&gt;
&lt;p&gt;This project significantly improved my understanding of:&lt;/p&gt;
&lt;h3 id=&quot;advanced-web-development&quot;&gt;Advanced Web Development&lt;/h3&gt;
&lt;p&gt;I learned how modern Web engineering goes far beyond simple layouts.&lt;/p&gt;
&lt;p&gt;Web development today includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Real-time rendering&lt;/li&gt;
&lt;li&gt;GPU acceleration&lt;/li&gt;
&lt;li&gt;Interactive systems&lt;/li&gt;
&lt;li&gt;Creative coding&lt;/li&gt;
&lt;li&gt;Visual programming&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;shader-programming-concepts&quot;&gt;Shader Programming Concepts&lt;/h3&gt;
&lt;p&gt;Before this project, shader programming felt complex.&lt;/p&gt;
&lt;p&gt;During development, I learned:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GLSL basics&lt;/li&gt;
&lt;li&gt;Vertex shader manipulation&lt;/li&gt;
&lt;li&gt;Fragment shader rendering&lt;/li&gt;
&lt;li&gt;Coordinate systems&lt;/li&gt;
&lt;li&gt;Lighting calculations&lt;/li&gt;
&lt;li&gt;Procedural visual effects&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This project became a huge learning experience.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;three-js-architecture&quot;&gt;Three.js Architecture&lt;/h3&gt;
&lt;p&gt;I gained practical experience with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Scene management&lt;/li&gt;
&lt;li&gt;Camera systems&lt;/li&gt;
&lt;li&gt;Mesh rendering&lt;/li&gt;
&lt;li&gt;Materials&lt;/li&gt;
&lt;li&gt;Geometries&lt;/li&gt;
&lt;li&gt;WebGL pipelines&lt;/li&gt;
&lt;li&gt;Orbit Controls&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This helped me better understand 3D development for the web.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;modern-full-stack-development-journey&quot;&gt;Modern Full Stack Development Journey&lt;/h3&gt;
&lt;p&gt;As a Full Stack Developer, I believe modern web engineering should combine:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Functionality&lt;/li&gt;
&lt;li&gt;Performance&lt;/li&gt;
&lt;li&gt;Scalability&lt;/li&gt;
&lt;li&gt;Creativity&lt;/li&gt;
&lt;li&gt;User experience&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;HOLO.SYS represents that mindset.&lt;/p&gt;
&lt;p&gt;It is not only a frontend showcase — it demonstrates creative engineering and technical exploration.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;why-creative-frontend-development-matters&quot;&gt;Why Creative Frontend Development Matters&lt;/h2&gt;
&lt;p&gt;The future of web development is moving toward:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Immersive experiences&lt;/li&gt;
&lt;li&gt;3D interfaces&lt;/li&gt;
&lt;li&gt;Interactive visual systems&lt;/li&gt;
&lt;li&gt;AI-powered UIs&lt;/li&gt;
&lt;li&gt;Real-time graphics&lt;/li&gt;
&lt;li&gt;WebXR and AR/VR&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Projects like HOLO.SYS help developers explore the next generation of web experiences.&lt;/p&gt;
&lt;p&gt;Creative frontend engineering is becoming one of the most exciting areas in software development.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;future-improvements&quot;&gt;Future Improvements&lt;/h2&gt;
&lt;p&gt;This project is still evolving.&lt;/p&gt;
&lt;p&gt;Future upgrades may include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bloom post-processing&lt;/li&gt;
&lt;li&gt;Audio reactive effects&lt;/li&gt;
&lt;li&gt;GLTF/GLB model loading&lt;/li&gt;
&lt;li&gt;Mobile touch optimization&lt;/li&gt;
&lt;li&gt;WebXR support&lt;/li&gt;
&lt;li&gt;AI visualization systems&lt;/li&gt;
&lt;li&gt;Advanced particle systems&lt;/li&gt;
&lt;li&gt;Dynamic environment lighting&lt;/li&gt;
&lt;li&gt;Procedural geometry systems&lt;/li&gt;
&lt;li&gt;Cyberpunk dashboard integrations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The goal is to continue transforming HOLO.SYS into a more advanced interactive visualization framework.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;about-the-developer&quot;&gt;About the Developer&lt;/h2&gt;
&lt;p&gt;Hi, I’m Muhammad Yasir — also known online as YasirAwan4831.&lt;/p&gt;
&lt;p&gt;I’m a Full Stack Web Developer focused on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Modern Web Development&lt;/li&gt;
&lt;li&gt;Creative Frontend Engineering&lt;/li&gt;
&lt;li&gt;React.js Applications&lt;/li&gt;
&lt;li&gt;JavaScript Ecosystems&lt;/li&gt;
&lt;li&gt;Three.js &amp;amp; WebGL&lt;/li&gt;
&lt;li&gt;UI/UX Development&lt;/li&gt;
&lt;li&gt;Interactive Web Experiences&lt;/li&gt;
&lt;li&gt;Performance Optimization&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I continuously work on improving my skills by building real-world projects and experimenting with modern technologies.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;final-thoughts-4&quot;&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;Building HOLO.SYS was an exciting journey into the world of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Creative coding&lt;/li&gt;
&lt;li&gt;Real-time graphics&lt;/li&gt;
&lt;li&gt;WebGL rendering&lt;/li&gt;
&lt;li&gt;Shader programming&lt;/li&gt;
&lt;li&gt;Advanced frontend development&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Projects like this remind me how powerful the modern web has become.&lt;/p&gt;
&lt;p&gt;Today, browsers are capable of rendering cinematic experiences that once required dedicated graphics software.&lt;/p&gt;
&lt;p&gt;More futuristic projects, advanced UI systems and immersive web experiences are coming soon.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;connect-with-me&quot;&gt;Connect With Me&lt;/h2&gt;
&lt;h3 id=&quot;github&quot;&gt;GitHub&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/YasirAwan4831&quot;&gt;https://github.com/YasirAwan4831&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;project-repository&quot;&gt;Project Repository&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/YasirAwan4831/holographic-shader-visualizer-three.Js&quot;&gt;https://github.com/YasirAwan4831/holographic-shader-visualizer-three.Js&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;live-demo-1&quot;&gt;Live Demo&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://holographic-threejs.vercel.app/&quot;&gt;https://holographic-threejs.vercel.app/&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;#ReactJS #ThreeJS #WebGL #GLSL #ShaderProgramming #FrontendDevelopment #CreativeCoding #CyberpunkUI #WebDevelopment #JavaScript #GSAP #ModernWebDevelopment #InteractiveWebDesign #FullStackDeveloper #CreativeFrontend #RealtimeRendering #HolographicUI #OpenSource #UIUX #Programming #WebDesign #ThreejsProjects #ReactDeveloper #WebGLProjects #YasirAwan4831 #MuhammadYasir&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17092/building-holo-sys-futuristic-3d-holographic-visualization-system-with-react-three-glsl</guid>
<pubDate>Fri, 08 May 2026 18:36:53 +0000</pubDate>
</item>
<item>
<title>I Built a Full-Stack E-Learning Platform with AI Quiz Generation — Here's Everything I Learned</title>
<link>https://coderlegion.com/15748/i-built-a-full-stack-e-learning-platform-with-ai-quiz-generation-heres-everything-learned</link>
<description>&lt;p&gt;I'm Karim, a full-stack developer and AI/ML researcher from Cairo. Over the past year, I built Qodrateman — a production e-learning platform now live at qodrateman.com, serving Arabic-speaking students across 12+ courses.&lt;br&gt;
This isn't a &quot;look what I made&quot; post. It's the full breakdown — stack decisions, what broke, how I fixed it, and what I'd do differently. Let's get into it.&lt;/p&gt;
&lt;p&gt;What Is Qodrateman?&lt;br&gt;
An e-learning platform built for the Arabic-speaking market with:&lt;/p&gt;
&lt;p&gt;Structured courses with instructor/learner role separation&lt;br&gt;
JWT authentication + Role-Based Access Control (RBAC)&lt;br&gt;
AI quiz generation — instructors get a full quiz in under 30 seconds&lt;br&gt;
12+ live courses, 500+ authenticated users&lt;br&gt;
Deployed on Vercel (frontend) + Render (backend) with zero downtime&lt;/p&gt;
&lt;p&gt;The Stack — and the Reasoning Behind Every Choice&lt;br&gt;
React + TypeScript + Tailwind CSS&lt;br&gt;
E-learning UI state is genuinely complex — active course, current lesson, user progress, enrollment status. React handles this cleanly. TypeScript caught dozens of bugs before they ever hit production. Tailwind made responsive design fast without drowning in custom CSS.&lt;br&gt;
Django REST Framework&lt;br&gt;
DRF gives you serializers, viewsets, and a built-in admin panel out of the box. I had a fully functional backend faster than I've seen with any other framework. For REST APIs, it's still my first pick.&lt;br&gt;
PostgreSQL&lt;br&gt;
E-learning data is deeply relational — users, courses, lessons, enrollments, quiz attempts, all interconnected. PostgreSQL handles this naturally. SQLite is fine locally, but never in production.&lt;br&gt;
JWT Auth with simplejwt&lt;br&gt;
pythonREST_FRAMEWORK = {&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;'DEFAULT_AUTHENTICATION_CLASSES': (
    'rest_framework_simplejwt.authentication.JWTAuthentication',
),
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;br&gt;
Access token in memory (not localStorage — that's a security risk). Refresh token handles silent re-authentication. Frontend and backend stay fully decoupled.&lt;br&gt;
Vercel + Render&lt;br&gt;
Zero-config deploys on every push with Vercel. Render for the Django backend — free tier works at early stage, scales easily when needed. Total setup time: under 2 hours.&lt;/p&gt;
&lt;p&gt;The Feature I'm Most Proud Of: AI Quiz Generation&lt;br&gt;
Instructors told me quiz creation was their biggest time sink — around 20 minutes per quiz. Writing quality multiple-choice questions is genuinely hard.&lt;br&gt;
I built AI-assisted quiz generation that brings that down to under 30 seconds.&lt;br&gt;
Here's how it works:&lt;/p&gt;
&lt;p&gt;Instructor enters topic + difficulty level&lt;br&gt;
System sends a structured prompt to the AI API&lt;br&gt;
API returns a complete quiz in JSON — questions, options, correct answers&lt;br&gt;
Platform parses and saves it directly to the database&lt;br&gt;
Instructor reviews and publishes in one click&lt;/p&gt;
&lt;p&gt;The structured prompt was the key engineering decision:&lt;br&gt;
pythondef generate_quiz_prompt(topic, num_questions, difficulty):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;return f&quot;&quot;&quot;
Generate a quiz about: {topic}
Number of questions: {num_questions}
Difficulty: {difficulty}

Respond ONLY with valid JSON in this exact format:
{{
    &quot;questions&quot;: [
        {{
            &quot;question&quot;: &quot;question text&quot;,
            &quot;options&quot;: [&quot;A&quot;, &quot;B&quot;, &quot;C&quot;, &quot;D&quot;],
            &quot;correct_answer&quot;: &quot;A&quot;,
            &quot;explanation&quot;: &quot;why this is correct&quot;
        }}
    ]
}}
Do not include any text outside the JSON.
&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Forcing structured output and validating before saving eliminated the inconsistency problem entirely. Early versions failed whenever the model returned extra text around the JSON — strict format constraints fixed this.&lt;/p&gt;
&lt;p&gt;RBAC: Two User Types, Zero Overlap&lt;br&gt;
Instructors and learners have completely separate capabilities. Here's the core of how that's modeled:&lt;br&gt;
python# models.py&lt;br&gt;
class UserProfile(models.Model):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ROLES = [
    ('instructor', 'Instructor'),
    ('learner', 'Learner'),
]
user = models.OneToOneField(User, on_delete=models.CASCADE)
role = models.CharField(max_length=20, choices=ROLES)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;permissions-py&quot;&gt;permissions.py&lt;/h2&gt;
&lt;p&gt;class IsInstructor(BasePermission):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def has_permission(self, request, view):
    return (
        request.user.is_authenticated and
        request.user.userprofile.role == 'instructor'
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This permission class protects every instructor-only endpoint — course creation, quiz generation, student progress. Learners can read published content and submit their own quiz attempts. Nothing more.&lt;/p&gt;
&lt;p&gt;What Broke in Production (and How I Fixed It)&lt;br&gt;
The N+1 Query Problem&lt;br&gt;
The course listing API was slow. Every course card needed the instructor name, lesson count, and enrollment count — three queries per course. With 12+ courses, that's 36+ database hits per page load.&lt;br&gt;
Fix:&lt;br&gt;
pythoncourses = Course.objects.select_related('instructor').annotate(&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;lesson_count=Count('lessons'),
enrollment_count=Count('enrollments')
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;)&lt;br&gt;
One query. Page load time dropped by 70%.&lt;br&gt;
Inconsistent AI Responses&lt;br&gt;
Early quiz generation would sometimes return extra text around the JSON, breaking json.loads().&lt;br&gt;
Fix — strip everything outside the JSON object before parsing:&lt;br&gt;
pythondef parse_ai_response(response_text):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;try:
    start = response_text.index('{')
    end = response_text.rindex('}') + 1
    return json.loads(response_text[start:end])
except (ValueError, json.JSONDecodeError):
    raise ValidationError(&quot;AI response could not be parsed&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Render Cold Starts&lt;br&gt;
Render's free tier spins down inactive services. First request after inactivity: 30+ seconds. Terrible UX.&lt;br&gt;
Fix: a lightweight ping endpoint that a Vercel cron job hits every 14 minutes. Cost: zero. Cold starts for active users: eliminated.&lt;/p&gt;
&lt;p&gt;What I'd Do Differently&lt;br&gt;
Add React Query from day one. Managing server state with raw useEffect and useState gets messy fast. React Query handles caching, refetching, and loading states far more cleanly. I retrofitted it partway through — not fun.&lt;br&gt;
Map the permission model before writing code. I refactored RBAC twice because I hadn't thought through edge cases upfront. Can instructors see other instructors' content? Can learners preview unenrolled courses? Draw this out before touching code.&lt;br&gt;
Use Docker locally from the start. &quot;Works on my machine&quot; is a real problem. Docker Compose makes the jump from local to production significantly smoother.&lt;/p&gt;
&lt;p&gt;By the Numbers&lt;br&gt;
MetricResultCourses live12+Authenticated users500+Quiz generation time~20 min → under 30 secPost-launch rollbacks0Downtime since launch0&lt;/p&gt;
&lt;p&gt;What's Next&lt;br&gt;
I'm also building Letra — a custom-trained bilingual OCR mobile app for iOS and Android using TensorFlow, Keras, PaddleOCR, and OpenCV. Currently at 98% English accuracy on 800+ labeled samples, deployed to TestFlight and Google Play Beta. Arabic OCR is in active iteration targeting 85%+.&lt;/p&gt;
&lt;p&gt;The hardest part of building a real product isn't writing the code. It's the decisions you make before you write any — schema design, permission models, deployment architecture. Get those right and everything else follows.&lt;br&gt;
If you're building on Django + React or have questions about the AI quiz generation approach, drop a comment. Happy to dig into any part of this.&lt;/p&gt;
&lt;p&gt;Full-stack dev &amp;amp; AI/ML researcher based in Cairo&lt;br&gt;
 karimkhamis.com ·  qodrateman.com ·  github.com/karim-99-99&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/15748/i-built-a-full-stack-e-learning-platform-with-ai-quiz-generation-heres-everything-learned</guid>
<pubDate>Fri, 24 Apr 2026 21:16:50 +0000</pubDate>
</item>
<item>
<title> From Chaos to Control: How GJSDocs Is Changing the Way Work with Document</title>
<link>https://coderlegion.com/15197/from-chaos-to-control-how-gjsdocs-is-changing-the-way-work-with-document</link>
<description>&lt;p&gt;In today’s fast-paced world of development and business, time is the most valuable resource. While some teams are still manually copying data into invoices, contracts, and reports, others are using automation tools. Today, we’re taking a look at GJSDocs — a platform that promises to eliminate &quot;copy-paste&quot; once and for all.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What is GJSDocs?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://gjsdocs.com/&quot;&gt;GJSDocs&lt;/a&gt; is a next-generation SaaS platform for document automation. The core concept is simple: you create a professional template once, connect your data (via API or integrations), and the service generates ready-to-use documents in seconds.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key Features&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Visual Template Editor: Forget about complex markup. The service offers a powerful builder where you can flexibly customize document structure, fonts, and styles to ensure a professional result.&lt;/p&gt;
&lt;p&gt;Data Integration: The standout feature is the connection to your business. You can feed data from CRMs, databases, or other applications directly into variables within your document.&lt;/p&gt;
&lt;p&gt;Next-Gen Automation: Positioned as a solution for &quot;modern teams,&quot; it implies seamless collaboration, cloud storage, and high-speed generation.&lt;/p&gt;
&lt;p&gt;API &amp;amp; Ecosystem: The &quot;Integrations&quot; section suggests that GJSDocs easily fits into any existing corporate workflow.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Who is it for?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Developers: Quickly implement PDF report or invoice generation into an app without spending weeks writing custom generation libraries.&lt;/p&gt;
&lt;p&gt;HR &amp;amp; Legal: Streamline the creation of job offers, employment contracts, or NDAs.&lt;/p&gt;
&lt;p&gt;Sales Departments: Automatically generate commercial proposals based on client data.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why is it worth trying?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The main problem with manual document entry is human error. A typo in a contract amount or bank details can be costly. GJSDocs minimizes these risks because the data is pulled automatically from the primary source.&lt;/p&gt;
&lt;p&gt;The platform’s interface is modern and clean, which is essential for tools used on a daily basis. The &quot;Pricing&quot; and &quot;Documentation&quot; sections indicate a transparent business model and readiness to scale for any business size.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Verdict&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;GJSDocs is a powerful tool for anyone looking to bring order to their documentation. It’s not just a &quot;text editor&quot; but a full-scale bridge between your data and the final PDF/Docx file.&lt;/p&gt;
&lt;p&gt;If your team is still spending more than 10 minutes creating a standard document, it’s time to check out the GJSDocs Dashboard.&lt;/p&gt;
&lt;p&gt;Website: &lt;a rel=&quot;nofollow&quot; href=&quot;https://gjsdocs.com/&quot;&gt;https://gjsdocs.com/&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/15197/from-chaos-to-control-how-gjsdocs-is-changing-the-way-work-with-document</guid>
<pubDate>Sun, 19 Apr 2026 22:30:38 +0000</pubDate>
</item>
<item>
<title>Building Real Projects, One Step at a Time</title>
<link>https://coderlegion.com/14366/building-real-projects-one-step-at-a-time</link>
<description>&lt;h3 id=&quot;building-real-projects-one-step-at-a-time&quot;&gt;Building Real Projects, One Step at a Time&lt;/h3&gt;
&lt;p&gt;As part of my internship at ARCH Technologies, I’ve successfully completed &lt;strong&gt;Task 3 — a Web-Based Music Player&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This project wasn’t just about building a UI, but about understanding how real applications work — handling user interactions, managing state and integrating external APIs.&lt;/p&gt;
&lt;p&gt;I focused on creating a clean and modern experience where users can:&lt;br&gt;
 Search music dynamically&lt;br&gt;
 Play, pause, and control audio&lt;br&gt;
 Explore songs through categories&lt;br&gt;
 Navigate playlists smoothly&lt;/p&gt;
&lt;p&gt;️ Tech Stack:&lt;br&gt;
React (Vite) • JavaScript • Tailwind CSS • Music API&lt;/p&gt;
&lt;p&gt;What made this project valuable for me was learning how to connect frontend logic with real-time data and create a smooth user experience.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt; Live Demo:&lt;/strong&gt;  &lt;a rel=&quot;nofollow&quot; href=&quot;https://yasirawaninfomusic.vercel.app/&quot;&gt;https://yasirawaninfomusic.vercel.app/&lt;/a&gt;&lt;br&gt;
&lt;strong&gt; GitHub Repo:&lt;/strong&gt;  &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/YasirAwan4831/arch-Technologies-internship-task3-music-player&quot;&gt;https://github.com/YasirAwan4831/arch-Technologies-internship-task3-music-player&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This is another step forward in my journey as a Full Stack Developer — more projects coming soon!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;#React #JavaScript #AppDevelopment #WebDevelopment #Internship #Projects #MusicPlayer #WebDeveloper #FullStackDeveloper #MuhammadYasir #YasirTech #YasirAwan4831&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/14366/building-real-projects-one-step-at-a-time</guid>
<pubDate>Thu, 09 Apr 2026 18:34:35 +0000</pubDate>
</item>
<item>
<title>Start Building Tables Faster with These Free Shadcn Table Components &amp; Blocks</title>
<link>https://coderlegion.com/14070/start-building-tables-faster-with-these-free-shadcn-table-components-blocks</link>
<description>&lt;p&gt;Tables are one of those components you don’t think about much… until you need one.&lt;/p&gt;
&lt;p&gt;User lists, invoices, dashboards, analytics almost every app needs a way to display structured data. And building tables from scratch every time? That quickly becomes repetitive.&lt;/p&gt;
&lt;p&gt;If you’re working with React or Next.js, using ready-made shadcn components can help you move faster without compromising flexibility.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-developers-are-switching-to-shadcn-tables&quot;&gt;Why Developers Are Switching to Shadcn Tables&lt;/h3&gt;
&lt;p&gt;If you've recently built something with React or Next.js, chances are you've come across shadcn.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;The biggest reason? Control.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Unlike traditional table libraries like MUI Data Grid, Mantine, or TanStack Table, shadcn doesn’t lock you into a predefined system.&lt;/p&gt;
&lt;p&gt;You can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Style it the way you want&lt;/li&gt;
&lt;li&gt;Extend features easily&lt;/li&gt;
&lt;li&gt;Keep everything aligned with your design system&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;&lt;p&gt;With shadcn tables, you're not fighting the library you're building on top of it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt; &lt;strong&gt;Built tables using shadcn ?&lt;/strong&gt;&lt;br&gt;
Feel free to share it &lt;br&gt;
I’d love to check it out and feature it here.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;official-shadcn-table-with-actions&quot;&gt;Official Shadcn Table with Actions&lt;/h3&gt;
&lt;p&gt;This is the official table component from the shadcn team.&lt;br&gt;
&lt;img src=&quot;https://ik.imagekit.io/shadcnspace/Shadcn%20Tables/1%20Official%20Shadcn%20Table.png&quot; alt=&quot;Official Shadcn Table&quot;&gt;&lt;br&gt;
It’s a solid starting point when you need actions like dropdown menus inside table rows.&lt;/p&gt;
&lt;h4 id=&quot;features&quot;&gt;Features&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Responsive layout&lt;/li&gt;
&lt;li&gt;Dropdown actions built-in&lt;/li&gt;
&lt;li&gt;Available in Base UI and Radix UI versions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Built with:&lt;/strong&gt; React, Tailwind, Base UI&lt;/p&gt;
&lt;p&gt;⬇️ &lt;a rel=&quot;nofollow&quot; href=&quot;https://ui.shadcn.com/docs/components/radix/table#actions&quot;&gt;Get Code&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;project-management-table-dashboard-style-&quot;&gt;Project Management Table (Dashboard Style)&lt;/h3&gt;
&lt;p&gt;A more advanced table layout designed for dashboards.&lt;br&gt;
&lt;img src=&quot;https://ik.imagekit.io/shadcnspace/Shadcn%20Tables/1%20Shadcn%20Space%20Data%20Table.png&quot; alt=&quot;Shadcn Space Data Table&quot;&gt;&lt;br&gt;
You get useful elements like avatars, progress indicators, and search helpful when managing team or project data.&lt;/p&gt;
&lt;h4 id=&quot;features-1&quot;&gt;Features&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Dashboard-friendly layout&lt;/li&gt;
&lt;li&gt;Profile image support&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Built with:&lt;/strong&gt; React, Next.js, Tailwind, Base UI, Radix UI&lt;/p&gt;
&lt;p&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://shadcnspace.com/preview/table-01&quot;&gt;Live Preview&lt;/a&gt; • ⬇️ &lt;a rel=&quot;nofollow&quot; href=&quot;https://shadcnspace.com/blocks/dashboard-ui/tables&quot;&gt;Get Code&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;shadcn-table-by-tailwindadmin&quot;&gt;Shadcn Table by Tailwindadmin&lt;/h3&gt;
&lt;p&gt;A clean and flexible table component built using Tailwind and shadcn.&lt;br&gt;
&lt;img src=&quot;https://ik.imagekit.io/shadcnspace/Shadcn%20Tables/Shadcn%20Table%20by%20Tailwindadmin.png&quot; alt=&quot;Shadcn Table by Tailwindadmin&quot;&gt;&lt;br&gt;
It’s designed to work across multiple frameworks, so you can use it not just in React or Next.js, but also in Vue, Angular, or even plain HTML projects without much effort.&lt;/p&gt;
&lt;h4 id=&quot;features-2&quot;&gt;Features&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Supports both light and dark mode&lt;/li&gt;
&lt;li&gt;Comes with a complete admin template&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Built with:&lt;/strong&gt; Shadcn, Tailwind, React, Next.js, Vue, Angular, HTML&lt;/p&gt;
&lt;p&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://react.tailwind-admin.com/shadcn-tables/basic&quot;&gt;Live Preview&lt;/a&gt; • ⬇️ &lt;a rel=&quot;nofollow&quot; href=&quot;https://tailwind-admin.com/&quot;&gt;Download&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;special-recognition-shadcn-table-builder&quot;&gt;Special Recognition: Shadcn Table Builder&lt;/h3&gt;
&lt;p&gt;An AI-powered table builder designed for Shadcn, React and Next.js projects.&lt;br&gt;
&lt;img src=&quot;https://ik.imagekit.io/shadcnspace/Shadcn%20Tables/Tailwind%20Table%20Builder.png&quot; alt=&quot;Tailwind Table Builder.png&quot;&gt;&lt;br&gt;
It helps you quickly generate tables using JSON, API data, or even CSV uploads no need to build everything manually.&lt;/p&gt;
&lt;p&gt;Perfect when you want to move fast and create dynamic tables without spending hours on setup.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;table-with-checkbox-filter&quot;&gt;Table with Checkbox Filter&lt;/h3&gt;
&lt;p&gt;An example built with Next.js for handling dynamic data.&lt;br&gt;
&lt;img src=&quot;https://ik.imagekit.io/shadcnspace/Shadcn%20Tables/8%20Shadcn%20Table%20with%20Check%20Box%20Filter.png&quot; alt=&quot;Shadcn Table with Check Box Filter&quot;&gt;&lt;br&gt;
Supports server-side pagination, sorting, and filtering with checkboxes.&lt;/p&gt;
&lt;h4 id=&quot;features-3&quot;&gt;Features&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Pagination support&lt;/li&gt;
&lt;li&gt;Dynamic data handling&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Built with:&lt;/strong&gt; TanStack, Next.js, Shadcn&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;data-table-with-sorting-amp-filters&quot;&gt;Data Table with Sorting &amp;amp; Filters&lt;/h3&gt;
&lt;p&gt;If you're working with larger datasets, this one is useful.&lt;br&gt;
&lt;img src=&quot;https://ik.imagekit.io/shadcnspace/Shadcn%20Tables/4%20Shadcn%20Data%20Table%20with%20Filter%20&amp;amp;%20Sorting.png&quot; alt=&quot;Shadcn Data Table with Filter &amp;amp; Sorting&quot;&gt;&lt;br&gt;
It handles server-side filtering, sorting, and pagination while keeping the UI clean.&lt;/p&gt;
&lt;h4 id=&quot;features-4&quot;&gt;Features&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Light and dark mode&lt;/li&gt;
&lt;li&gt;Filtering and sorting&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Built with:&lt;/strong&gt; Next.js, Tailwind, Drizzle, Zod, Planetscale&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;basic-data-table&quot;&gt;Basic Data Table&lt;/h3&gt;
&lt;p&gt;A simple and minimal table for everyday use.&lt;br&gt;
&lt;img src=&quot;https://ik.imagekit.io/shadcnspace/Shadcn%20Tables/7%20Basic%20Shadcn%20Data%20Table.png&quot; alt=&quot;Basic Shadcn Data Table.png&quot;&gt;&lt;br&gt;
Great when you just need to display data without adding too many features.&lt;/p&gt;
&lt;h4 id=&quot;features-5&quot;&gt;Features&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Clean UI&lt;/li&gt;
&lt;li&gt;Badge support&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Built with:&lt;/strong&gt; TanStack, Shadcn, Tailwind, React, Zod&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;shadcn-table-maker&quot;&gt;Shadcn Table Maker&lt;/h3&gt;
&lt;p&gt;This one is more like a helper tool.&lt;br&gt;
&lt;img src=&quot;https://ik.imagekit.io/shadcnspace/Shadcn%20Tables/5%20Shadcn%20Table%20Maker.png&quot; alt=&quot;Shadcn Table Maker&quot;&gt;&lt;br&gt;
It lets you create dynamic tables quickly without writing everything manually.&lt;/p&gt;
&lt;h4 id=&quot;features-6&quot;&gt;Features&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Build custom tables&lt;/li&gt;
&lt;li&gt;Works across multiple use cases&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Built with:&lt;/strong&gt; Next.js, Tailwind, Drizzle, Zod, Planetscale&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;table-for-music-media-data&quot;&gt;Table for Music / Media Data&lt;/h3&gt;
&lt;p&gt;A unique example built using Rails with shadcn styling.&lt;br&gt;
&lt;img src=&quot;https://ik.imagekit.io/shadcnspace/Shadcn%20Tables/6%20Shadcn%20Music%20Table.png&quot; alt=&quot;Shadcn Music Table.png&quot;&gt;&lt;br&gt;
Shows that shadcn is not limited to just React — you can adapt it across stacks.&lt;/p&gt;
&lt;h4 id=&quot;features-7&quot;&gt;Features&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Built for media or audio data&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Built with:&lt;/strong&gt; Ruby on Rails, Shadcn&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;invoice-table&quot;&gt;Invoice Table&lt;/h3&gt;
&lt;p&gt;A clean table layout designed for invoice data.&lt;br&gt;
&lt;img src=&quot;https://ik.imagekit.io/shadcnspace/Shadcn%20Tables/3%20Shadcn%20Invoice%20Table.png&quot; alt=&quot;Shadcn Invoice Table&quot;&gt;&lt;br&gt;
Simple, readable, and easy to adapt for other structured data.&lt;/p&gt;
&lt;h4 id=&quot;features-8&quot;&gt;Features&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Fully responsive&lt;/li&gt;
&lt;li&gt;Clean layout&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Built with:&lt;/strong&gt; React, Tailwind, Base UI&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;final-thoughts-5&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;Tables are everywhere in modern apps.&lt;/p&gt;
&lt;p&gt;Instead of rebuilding the same UI again and again, using &lt;strong&gt;shadcn table components&lt;/strong&gt; helps you save time and keep your UI consistent.&lt;/p&gt;
&lt;p&gt;Pick a block, customize it based on your needs, and ship faster.&lt;/p&gt;
&lt;hr&gt;
&lt;blockquote&gt;&lt;p&gt; Need more Shadcn Table Components?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Explore more &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://shadcnspace.com/blocks/&quot;&gt;shadcn ui blocks&lt;/a&gt;&lt;/strong&gt; to speed up your development workflow.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/14070/start-building-tables-faster-with-these-free-shadcn-table-components-blocks</guid>
<pubDate>Mon, 06 Apr 2026 12:32:15 +0000</pubDate>
</item>
<item>
<title>Shadcn Sidebar Patterns I’d Actually Use in a Real Project</title>
<link>https://coderlegion.com/13514/shadcn-sidebar-patterns-id-actually-use-in-a-real-project</link>
<description>&lt;p&gt;In most SaaS dashboards, the sidebar is not just a menu.&lt;/p&gt;
&lt;p&gt;It controls navigation, layout structure, permissions, and even performance. If you build with Next.js and React, your sidebar design will directly affect how scalable and maintainable your app becomes.&lt;/p&gt;
&lt;p&gt;This article explains the most effective  Shadcn sidebar patterns  and how to think about them from a system design perspective.&lt;/p&gt;
&lt;h3 id=&quot;what-is-a-shadcn-sidebar-&quot;&gt;What Is a Shadcn Sidebar?&lt;/h3&gt;
&lt;p&gt;A  &lt;a rel=&quot;nofollow&quot; href=&quot;https://shadcnspace.com/blocks/dashboard-ui/sidebars&quot;&gt;Shadcn sidebar&lt;/a&gt;  is a navigation system built using shadcn/ui components and Tailwind CSS.&lt;/p&gt;
&lt;p&gt;In real projects, it usually does the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lives inside a shared dashboard layout&lt;/li&gt;
&lt;li&gt;Persists across route changes&lt;/li&gt;
&lt;li&gt;Highlights active routes&lt;/li&gt;
&lt;li&gt;Supports role based access&lt;/li&gt;
&lt;li&gt;Handles responsive behavior&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In a Next.js App Router setup, the sidebar is placed inside  &lt;code&gt;layout.tsx&lt;/code&gt;  under a dashboard route group. This prevents it from unmounting during navigation.&lt;/p&gt;
&lt;p&gt;Typical structure:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;app/  
  (dashboard)/  
    layout.tsx  
    page.tsx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Navigation items are stored in a config file and mapped into UI components. This keeps logic clean and avoids repeating code.&lt;/p&gt;
&lt;h3 id=&quot;why-sidebar-architecture-matters&quot;&gt;Why Sidebar Architecture Matters&lt;/h3&gt;
&lt;p&gt;In small projects, developers hardcode links in JSX.&lt;/p&gt;
&lt;p&gt;In large SaaS systems, that approach fails quickly.&lt;/p&gt;
&lt;p&gt;A well designed sidebar improves:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Maintainability&lt;/li&gt;
&lt;li&gt;Scalability&lt;/li&gt;
&lt;li&gt;Developer productivity&lt;/li&gt;
&lt;li&gt;Routing consistency&lt;/li&gt;
&lt;li&gt;Permission handling&lt;/li&gt;
&lt;li&gt;Performance&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If your navigation grows from 10 items to 50 items, poor structure will create technical debt.&lt;/p&gt;
&lt;h3 id=&quot;architecture-decisions-you-should-make-first&quot;&gt;Architecture Decisions You Should Make First&lt;/h3&gt;
&lt;p&gt;Before choosing a sidebar style, answer these questions.&lt;/p&gt;
&lt;h3 id=&quot;1-how-deep-is-your-navigation&quot;&gt;1. How deep is your navigation&lt;/h3&gt;
&lt;p&gt;If your app has nested sections, use a config driven structure with children arrays and recursive rendering.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export const navItems = [  
  {  
    title: &quot;Dashboard&quot;,  
    href: &quot;/dashboard&quot;  
  },  
  {  
    title: &quot;Users&quot;,  
    children: [  
      { title: &quot;All Users&quot;, href: &quot;/users&quot; },  
      { title: &quot;Roles&quot;, href: &quot;/users/roles&quot; }  
    ]  
  }  
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This keeps your components reusable and avoids large JSX blocks.&lt;/p&gt;
&lt;h3 id=&quot;2-will-the-sidebar-collapse&quot;&gt;2. Will the sidebar collapse&lt;/h3&gt;
&lt;p&gt;If you support collapse and expand behavior, store the state in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;React Context&lt;/li&gt;
&lt;li&gt;Zustand&lt;/li&gt;
&lt;li&gt;A global store&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Do not store it inside a single component if you want layout persistence.&lt;/p&gt;
&lt;h3 id=&quot;3-how-will-active-routes-be-handled&quot;&gt;3. How will active routes be handled&lt;/h3&gt;
&lt;p&gt;In Next.js, use  &lt;code&gt;usePathname()&lt;/code&gt;  to match the current path with navigation items.&lt;/p&gt;
&lt;p&gt;Keep matching logic outside UI components. This makes the sidebar easier to test and reuse.&lt;/p&gt;
&lt;h3 id=&quot;4-do-you-need-role-based-navigation&quot;&gt;4. Do you need role based navigation&lt;/h3&gt;
&lt;p&gt;If your app has different roles, filter the navigation config before rendering.&lt;/p&gt;
&lt;p&gt;Do not mix permission logic inside presentational components.&lt;/p&gt;
&lt;h3 id=&quot;5-is-the-app-mobile-friendly&quot;&gt;5. Is the app mobile friendly&lt;/h3&gt;
&lt;p&gt;Use the same navigation config for both desktop and mobile.&lt;/p&gt;
&lt;p&gt;Render it as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fixed sidebar on desktop&lt;/li&gt;
&lt;li&gt;Drawer or sheet on mobile&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Never duplicate navigation logic for different screen sizes.&lt;/p&gt;
&lt;h3 id=&quot;7-effective-shadcn-sidebar-patterns&quot;&gt;7 Effective Shadcn Sidebar Patterns&lt;/h3&gt;
&lt;p&gt;Here are the most practical patterns used in production dashboards.&lt;/p&gt;
&lt;h3 id=&quot;1-admin-dashboard-sidebar&quot;&gt;1. Admin Dashboard Sidebar&lt;/h3&gt;
&lt;p&gt;Press enter or click to view image in full size&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://miro.medium.com/v2/resize:fit:700/0*LepBSPOC6IRFimt2.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;A multi level navigation sidebar with grouped sections and collapsible items. It supports nested routes and works well with role based filtering. This is the most common pattern in serious SaaS products.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://medium.com/plans?source=promotion_paragraph---post_body_banner_rabbit_hole_scribble--ef1235cc920d---------------------------------------&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use cases&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CRM systems&lt;/li&gt;
&lt;li&gt;Analytics dashboards&lt;/li&gt;
&lt;li&gt;Role based admin panels&lt;/li&gt;
&lt;li&gt;Complex SaaS platforms&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://shadcnspace.com/blocks/dashboard-ui/sidebars#sidebar-01&quot;&gt;Explore Admin Dashboard Sidebar&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;2-mini-sidebar-navigation&quot;&gt;2. Mini Sidebar Navigation&lt;/h3&gt;
&lt;p&gt;Press enter or click to view image in full size&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://miro.medium.com/v2/resize:fit:700/0*tXbPo92ge7zSUfaX.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;A collapsed first sidebar that shows icons by default and expands on interaction. It reduces horizontal space usage and keeps the main content area larger.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use cases&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Data heavy dashboards&lt;/li&gt;
&lt;li&gt;Trading platforms&lt;/li&gt;
&lt;li&gt;Reporting tools&lt;/li&gt;
&lt;li&gt;High density interfaces&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://shadcnspace.com/blocks/dashboard-ui/sidebars#sidebar-02&quot;&gt;Explore Mini Sidebar Navigation&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;3-two-column-sidebar&quot;&gt;3. Two Column Sidebar&lt;/h3&gt;
&lt;p&gt;Press enter or click to view image in full size&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://miro.medium.com/v2/resize:fit:700/0*ZnTE-G1MmZVTI02r.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;A dual layer navigation system. The first column handles top level sections. The second column updates dynamically based on selection. This avoids deep nesting and improves structure clarity.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use cases&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Enterprise systems&lt;/li&gt;
&lt;li&gt;Project management tools&lt;/li&gt;
&lt;li&gt;Large internal platforms&lt;/li&gt;
&lt;li&gt;Feature rich applications&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://shadcnspace.com/blocks/dashboard-ui/sidebars#sidebar-03&quot;&gt;Explore Two Column Sidebar&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;4-compact-dashboard-navigation&quot;&gt;4. Compact Dashboard Navigation&lt;/h3&gt;
&lt;p&gt;Press enter or click to view image in full size&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://miro.medium.com/v2/resize:fit:700/0*-HLd6y3w5Elh4be7.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;A tighter version of a traditional sidebar. It keeps labels visible but reduces padding and spacing for better information density.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use cases&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Internal admin tools&lt;/li&gt;
&lt;li&gt;Moderate complexity dashboards&lt;/li&gt;
&lt;li&gt;Operations panels&lt;/li&gt;
&lt;li&gt;Back office systems&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://shadcnspace.com/blocks/dashboard-ui/sidebars#sidebar-04&quot;&gt;Explore Compact Dashboard Navigation&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;5-admin-sidebar-with-promo-section&quot;&gt;5. Admin Sidebar with Promo Section&lt;/h3&gt;
&lt;p&gt;Press enter or click to view image in full size&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://miro.medium.com/v2/resize:fit:700/0*vHft2CUHMbul4KHv.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;A navigation layout that includes a promotional or announcement block inside the sidebar. It integrates product messaging without interrupting workflow.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use cases&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SaaS products with upgrade tiers&lt;/li&gt;
&lt;li&gt;Feature launch announcements&lt;/li&gt;
&lt;li&gt;Product led growth flows&lt;/li&gt;
&lt;li&gt;Subscription based platforms&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://shadcnspace.com/blocks/dashboard-ui/sidebars#sidebar-05&quot;&gt;Explore Admin Sidebar with Promo&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;6-fully-responsive-sidebar&quot;&gt;6. Fully Responsive Sidebar&lt;/h3&gt;
&lt;p&gt;Press enter or click to view image in full size&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://miro.medium.com/v2/resize:fit:700/0*PyTxotM7yXL0ZdY3.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;A layout that switches between fixed desktop sidebar and mobile drawer. It uses the same navigation config for both views to avoid logic duplication.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use cases&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cross device SaaS apps&lt;/li&gt;
&lt;li&gt;Mobile friendly dashboards&lt;/li&gt;
&lt;li&gt;Field service platforms&lt;/li&gt;
&lt;li&gt;Multi device enterprise tools&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://v0.app/t/65s08PdDe0f&quot;&gt;Explore Shadcn Responsive Sidebar&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;7-composable-developer-first-sidebar&quot;&gt;7. Composable Developer First Sidebar&lt;/h3&gt;
&lt;p&gt;Press enter or click to view image in full size&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://miro.medium.com/v2/resize:fit:700/0*pEYQzx8SmMj-Ur0z.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;A fully config driven and modular sidebar system. Navigation data is separated from UI components. It supports dynamic routes, permissions, and feature flags.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use cases&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Custom dashboard frameworks&lt;/li&gt;
&lt;li&gt;Multi product SaaS platforms&lt;/li&gt;
&lt;li&gt;Design systems&lt;/li&gt;
&lt;li&gt;Long term scalable projects&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/salimi-my/shadcn-ui-sidebar&quot;&gt;Explore Shadcn Sidebar with Navigation&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;final-thoughts-6&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;In modern SaaS applications, the sidebar affects more than navigation. It controls layout persistence, routing behavior, permission logic, and long term maintainability.&lt;/p&gt;
&lt;p&gt;If you build with Shadcn and Next.js, use a configuration driven structure from day one. It will make your codebase easier to scale and easier to maintain.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;A clean sidebar architecture reduces technical debt and improves developer speed over time. That is why the sidebar is not just design. It is infrastructure.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/13514/shadcn-sidebar-patterns-id-actually-use-in-a-real-project</guid>
<pubDate>Thu, 26 Mar 2026 11:44:52 +0000</pubDate>
</item>
<item>
<title>Shadcn Dialogs for React &amp; Next.js Websites</title>
<link>https://coderlegion.com/13424/shadcn-dialogs-for-react-next-js-websites</link>
<description>&lt;p&gt;Dialog (modal) components are a core part of modern websites whether it’s collecting emails, onboarding users, or handling quick actions without leaving the page.&lt;/p&gt;
&lt;p&gt;From newsletter popups to onboarding flows, dialogs are used everywhere. If you’re building with &lt;a rel=&quot;nofollow&quot; href=&quot;https://react.dev/&quot;&gt;&lt;strong&gt;React&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;,&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://nextjs.org/&quot;&gt;&lt;strong&gt;Next.js&lt;/strong&gt;&lt;/a&gt;, and &lt;a rel=&quot;nofollow&quot; href=&quot;https://ui.shadcn.com/&quot;&gt;&lt;strong&gt;shadcn/ui&lt;/strong&gt;&lt;/a&gt;, you don’t need to build them from scratch anymore.&lt;/p&gt;
&lt;p&gt;In this post, we’ll explore some ready-to-use shadcn dialog components you can plug into your projects.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-use-dialog-components&quot;&gt;Why Use Dialog Components&lt;/h3&gt;
&lt;p&gt;Shadcn Dialog components help keep users focused without breaking the flow of your react website.&lt;/p&gt;
&lt;h4 id=&quot;instead-of-navigating-to-new-pages-users-can-&quot;&gt;Instead of navigating to new pages, users can:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Complete actions instantly&lt;/li&gt;
&lt;li&gt;Interact with forms&lt;/li&gt;
&lt;li&gt;Manage data quickly&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;this-shadcn-dialog-box-leads-to-&quot;&gt;This shadcn dialog box leads to:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Better UX&lt;/li&gt;
&lt;li&gt;Faster interactions&lt;/li&gt;
&lt;li&gt;Higher conversions&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;blockquote&gt;&lt;p&gt; &lt;em&gt;Got a shadcn dialog component to share?&lt;/em&gt;  &lt;/p&gt;
&lt;p&gt;Feel free to reach out on&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/isanjayjoshi/&quot;&gt;LinkedIn&lt;/a&gt;,&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://twitter.com/isanjayjoshi&quot;&gt;Twitter&lt;/a&gt;,&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://peerlist.io/sanjayjoshi&quot;&gt;Peerlist&lt;/a&gt;, or anywhere you prefer;&lt;br&gt;
I’d love to check it out and add it to this list.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-newsletter-subscription-shadcn-dialog&quot;&gt;1. Newsletter Subscription Shadcn Dialog&lt;/h3&gt;
&lt;p&gt;A modern dialog designed for capturing email subscriptions with a clean and conversion-focused layout.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=6153167627590127244&quot; alt=&quot;Newsletter Subscription Shadcn Dialog&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;features-9&quot;&gt;Features&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Hero-style layout&lt;/li&gt;
&lt;li&gt;Email input field&lt;/li&gt;
&lt;li&gt;Optional opt-out checkbox&lt;/li&gt;
&lt;li&gt;Clear CTA&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;tech-stack&quot;&gt;Tech Stack&lt;/h4&gt;
&lt;p&gt;React, Next.js, shadcn/ui&lt;/p&gt;
&lt;p&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://shadcnspace.com/blocks/dashboard-ui/dialog&quot;&gt;Get Code&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Button } from &quot;@/components/ui/button&quot;;
import { Checkbox } from &quot;@/components/ui/checkbox&quot;;
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from &quot;@/components/ui/dialog&quot;;
import { FieldLabel } from &quot;@/components/ui/field&quot;;
import { Input } from &quot;@/components/ui/input&quot;;

const DialogBlock = () =&amp;gt; {
  return (
    &amp;lt;section className=&quot;bg-primary dark:bg-background lg:py-20 sm:py-16 py-8&quot;&amp;gt;
      &amp;lt;div className=&quot;max-w-7xl xl:px-16 lg:px-8 px-4 mx-auto&quot;&amp;gt;
        &amp;lt;Dialog defaultOpen&amp;gt;
          &amp;lt;DialogTrigger&amp;gt;
            &amp;lt;div className=&quot;bg-background px-2.5 py-1.5 rounded-md border border-border text-sm font-semibold&quot;&amp;gt;
              Open Dialog
            &amp;lt;/div&amp;gt;
          &amp;lt;/DialogTrigger&amp;gt;
          &amp;lt;DialogContent className=&quot;md:max-w-4xl p-0 rounded-none&quot;&amp;gt;
            &amp;lt;DialogHeader className=&quot;sr-only&quot;&amp;gt;
              &amp;lt;DialogTitle&amp;gt;Newsletter Popup&amp;lt;/DialogTitle&amp;gt;
            &amp;lt;/DialogHeader&amp;gt;
            &amp;lt;div className=&quot;flex md:flex-row flex-col&quot;&amp;gt;
              &amp;lt;div className=&quot;md:max-w-md w-full&quot;&amp;gt;
                &amp;lt;img
                  src=&quot;https://images.shadcnspace.com/assets/backgrounds/newsletter-image.webp&quot;
                  alt=&quot;newsletter-image&quot;
                  className=&quot;w-full object-cover sm:h-full h-40&quot;
                /&amp;gt;
              &amp;lt;/div&amp;gt;
              &amp;lt;div className=&quot;md:p-16 p-6 w-full &quot;&amp;gt;
                &amp;lt;div className=&quot;flex flex-col gap-6&quot;&amp;gt;
                  &amp;lt;div className=&quot;flex flex-col gap-4&quot;&amp;gt;
                    &amp;lt;h2 className=&quot;text-card-foreground text-3xl font-medium&quot;&amp;gt;
                      Subscribe to the latest updates of Shadcn Space
                    &amp;lt;/h2&amp;gt;
                    &amp;lt;p className=&quot;text-muted-foreground text-base font-normal&quot;&amp;gt;
                      Subscribe our newsletters and get the latest business
                      updates
                    &amp;lt;/p&amp;gt;
                  &amp;lt;/div&amp;gt;
                  &amp;lt;form className=&quot;flex flex-col gap-4&quot;&amp;gt;
                    &amp;lt;div className=&quot;flex flex-col gap-3&quot;&amp;gt;
                      &amp;lt;Input
                        id=&quot;email&quot;
                        type=&quot;email&quot;
                        placeholder=&quot;*Emails are not allowed*&quot;
                        required
                        className=&quot;dark:bg-background rounded-lg h-9 shadow-xs&quot;
                      /&amp;gt;
                      &amp;lt;Button
                        type=&quot;submit&quot;
                        size={&quot;lg&quot;}
                        className=&quot;rounded-lg h-10 cursor-pointer hover:bg-primary/80&quot;
                      &amp;gt;
                        Subscribe now
                      &amp;lt;/Button&amp;gt;
                    &amp;lt;/div&amp;gt;
                    &amp;lt;div className=&quot;flex flex-row items-center justify-between w-full&quot;&amp;gt;
                      &amp;lt;div className=&quot;flex items-center gap-3 &quot;&amp;gt;
                        &amp;lt;Checkbox id=&quot;newsletter&quot; className=&quot;cursor-pointer&quot; /&amp;gt;
                        &amp;lt;FieldLabel
                          htmlFor=&quot;newsletter&quot;
                          className=&quot;text-sm text-primary font-normal cursor-pointer&quot;
                        &amp;gt;
                          Don't show this popup again
                        &amp;lt;/FieldLabel&amp;gt;
                      &amp;lt;/div&amp;gt;
                    &amp;lt;/div&amp;gt;
                  &amp;lt;/form&amp;gt;
                &amp;lt;/div&amp;gt;
              &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/DialogContent&amp;gt;
        &amp;lt;/Dialog&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/section&amp;gt;
  );
};

export default DialogBlock;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-simple-shadcn-dialog-box-classic-&quot;&gt;2. Simple Shadcn Dialog Box (Classic)&lt;/h3&gt;
&lt;p&gt;A minimal and familiar dialog UI inspired by traditional modal designs.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://media4.giphy.com/media/v1.Y2lkPTc5MGI3NjExZXVkdGE0NnFoYjBidmptNW40bWFjbGd3Z2VhdWdpNThveHU1eHgwYSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/ieGOn98ovlYrRea70a/giphy.gif&quot; alt=&quot;Simple Shadcn Dialog Box&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;features-10&quot;&gt;Features&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Clean layout&lt;/li&gt;
&lt;li&gt;Smooth animations&lt;/li&gt;
&lt;li&gt;Accessible structure&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;tech-stack-1&quot;&gt;Tech Stack&lt;/h4&gt;
&lt;p&gt;Built using Radix UI Dialog primitive and styled with Tailwind CSS&lt;/p&gt;
&lt;p&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.shadcn.io/ui/dialog#dialog-showcase&quot;&gt;Get Code&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx shadcn@latest add dialog
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-add-a-writer-shadcn-dialog&quot;&gt;3. Add a Writer Shadcn Dialog&lt;/h3&gt;
&lt;p&gt;A practical dialog component for adding contributors or writers inside an application.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://media0.giphy.com/media/v1.Y2lkPTc5MGI3NjExenNkZ3Nid2dzY2xlbzZjMXF3d3dsdnNhOTdvNThiMWcwaXdua2tjeCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/Ja1JKQgQuHk3wS7N4t/giphy.gif&quot; alt=&quot;Add a Writer Shadcn Dialog&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;use-cases&quot;&gt;Use Cases&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;CMS platforms&lt;/li&gt;
&lt;li&gt;Blogging tools&lt;/li&gt;
&lt;li&gt;LMS dashboards&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;features-11&quot;&gt;Features&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Simple input structure&lt;/li&gt;
&lt;li&gt;Clean UI&lt;/li&gt;
&lt;li&gt;Easy data handling&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://blocks.so/dialogs&quot;&gt;Get Code&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;use client&quot;;

import { Plus, UserRoundIcon, X } from &quot;lucide-react&quot;;
import { useRef, useState } from &quot;react&quot;;

import { Avatar, AvatarFallback, AvatarImage } from &quot;@/components/ui/avatar&quot;;
import { Button } from &quot;@/components/ui/button&quot;;
import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from &quot;@/components/ui/dialog&quot;;
import { Input } from &quot;@/components/ui/input&quot;;
import { Label } from &quot;@/components/ui/label&quot;;

export default function Dialog12() {
  const [open, setOpen] = useState(true);
  const [authorName, setAuthorName] = useState(&quot;Ephraim Duncan&quot;);
  const [title, setTitle] = useState(&quot;Design Engineer&quot;);
  const [image, setImage] = useState&amp;lt;string | null&amp;gt;(null);
  const fileInputRef = useRef&amp;lt;HTMLInputElement&amp;gt;(null);

  const handleFileChange = (e: React.ChangeEvent&amp;lt;HTMLInputElement&amp;gt;) =&amp;gt; {
    const file = e.target.files?.[0];
    if (file) {
      if (file.size &amp;gt; 1048576) {
        alert(&quot;File size exceeds 1MB limit&quot;);
        return;
      }

      const reader = new FileReader();
      reader.onload = (event) =&amp;gt; {
        setImage(event.target?.result as string);
      };
      reader.readAsDataURL(file);
    }
  };

  const triggerFileInput = () =&amp;gt; {
    fileInputRef.current?.click();
  };

  return (
    &amp;lt;Dialog open={open} onOpenChange={setOpen}&amp;gt;
      &amp;lt;DialogTrigger asChild&amp;gt;
        &amp;lt;Button&amp;gt;Open Dialog&amp;lt;/Button&amp;gt;
      &amp;lt;/DialogTrigger&amp;gt;
      &amp;lt;DialogContent className=&quot;sm:max-w-lg p-0 rounded-3xl gap-0&quot;&amp;gt;
        &amp;lt;DialogHeader className=&quot;border-b px-6 py-4&quot;&amp;gt;
          &amp;lt;DialogTitle className=&quot;text-balance font-medium&quot;&amp;gt;Add a writer&amp;lt;/DialogTitle&amp;gt;
        &amp;lt;/DialogHeader&amp;gt;

        &amp;lt;div className=&quot;grid grid-cols-1 md:grid-cols-5 px-6 pt-4 pb-6&quot;&amp;gt;
          &amp;lt;div className=&quot;flex flex-col items-center justify-center  md:col-span-2&quot;&amp;gt;
            &amp;lt;div className=&quot;relative mb-2&quot;&amp;gt;
              &amp;lt;Avatar className=&quot;h-24 w-24 border-2 border-muted&quot;&amp;gt;
                &amp;lt;AvatarImage src={image || undefined} alt=&quot;Profile&quot; /&amp;gt;
                &amp;lt;AvatarFallback&amp;gt;
                  &amp;lt;UserRoundIcon
                    size={52}
                    className=&quot;text-muted-foreground&quot;
                    aria-hidden=&quot;true&quot;
                  /&amp;gt;
                &amp;lt;/AvatarFallback&amp;gt;
              &amp;lt;/Avatar&amp;gt;
              &amp;lt;Button
                variant=&quot;ghost&quot;
                size=&quot;icon-sm&quot;
                className=&quot;absolute -top-0.5 -right-0.5 bg-accent rounded-full border-[3px] border-background hover:bg-accent&quot;
                onClick={() =&amp;gt; {
                  if (image) {
                    setImage(null);
                    if (fileInputRef.current) {
                      fileInputRef.current.value = &quot;&quot;;
                    }
                  } else {
                    triggerFileInput();
                  }
                }}
              &amp;gt;
                {image ? (
                  &amp;lt;X className=&quot;h-4 w-4 text-muted-foreground&quot; /&amp;gt;
                ) : (
                  &amp;lt;Plus className=&quot;h-3 w-3 text-muted-foreground&quot; /&amp;gt;
                )}
                &amp;lt;span className=&quot;sr-only&quot;&amp;gt;
                  {image ? &quot;Remove image&quot; : &quot;Upload image&quot;}
                &amp;lt;/span&amp;gt;
              &amp;lt;/Button&amp;gt;
            &amp;lt;/div&amp;gt;

            &amp;lt;p className=&quot;text-pretty text-center font-medium&quot;&amp;gt;Upload Image&amp;lt;/p&amp;gt;
            &amp;lt;p className=&quot;text-pretty text-center text-sm text-muted-foreground&quot;&amp;gt;
              Max file size: 1MB
            &amp;lt;/p&amp;gt;
            &amp;lt;input
              type=&quot;file&quot;
              ref={fileInputRef}
              onChange={handleFileChange}
              accept=&quot;image/*&quot;
              className=&quot;hidden&quot;
            /&amp;gt;
            &amp;lt;Button
              variant=&quot;outline&quot;
              size=&quot;sm&quot;
              className=&quot;mt-2&quot;
              onClick={triggerFileInput}
            &amp;gt;
              Add Image
            &amp;lt;/Button&amp;gt;
          &amp;lt;/div&amp;gt;

          &amp;lt;div className=&quot;flex flex-col justify-between md:col-span-3&quot;&amp;gt;
            &amp;lt;div className=&quot;space-y-4&quot;&amp;gt;
              &amp;lt;div className=&quot;space-y-1&quot;&amp;gt;
                &amp;lt;Label htmlFor=&quot;author-name&quot; className=&quot;flex items-center&quot;&amp;gt;
                  Author name &amp;lt;span className=&quot;text-primary&quot;&amp;gt;*&amp;lt;/span&amp;gt;
                &amp;lt;/Label&amp;gt;
                &amp;lt;Input
                  id=&quot;author-name&quot;
                  value={authorName}
                  onChange={(e) =&amp;gt; setAuthorName(e.target.value)}
                  required
                /&amp;gt;
              &amp;lt;/div&amp;gt;

              &amp;lt;div className=&quot;space-y-1&quot;&amp;gt;
                &amp;lt;div className=&quot;flex items-center&quot;&amp;gt;
                  &amp;lt;Label htmlFor=&quot;title&quot;&amp;gt;Title&amp;lt;/Label&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;Input
                  id=&quot;title&quot;
                  value={title}
                  onChange={(e) =&amp;gt; setTitle(e.target.value)}
                /&amp;gt;
              &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;

            &amp;lt;div className=&quot;flex justify-end gap-2&quot;&amp;gt;
              &amp;lt;Button variant=&quot;outline&quot; onClick={() =&amp;gt; setOpen(false)}&amp;gt;
                Cancel
              &amp;lt;/Button&amp;gt;
              &amp;lt;Button className=&quot;bg-foreground text-background hover:bg-foreground/90&quot;&amp;gt;
                Save Changes
              &amp;lt;/Button&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/DialogContent&amp;gt;
    &amp;lt;/Dialog&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-onboarding-shadcn-dialog&quot;&gt;4. Onboarding Shadcn Dialog&lt;/h3&gt;
&lt;p&gt;A dialog built for guiding users through onboarding steps.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://media0.giphy.com/media/v1.Y2lkPTc5MGI3NjExYzhlNGJuZmo5bmdmamNveTJzMmIxcTdmN3BlZHQxd2kydjZwZno2YyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/hJudSx10NgX6hh9PQU/giphy.gif&quot; alt=&quot;Onboarding Shadcn Dialog&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;features-12&quot;&gt;Features&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Step-based flow&lt;/li&gt;
&lt;li&gt;Smooth transitions&lt;/li&gt;
&lt;li&gt;User-friendly interface&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;tech-stack-2&quot;&gt;Tech Stack&lt;/h4&gt;
&lt;p&gt;React, Tailwind, shadcn/ui&lt;/p&gt;
&lt;p&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.shadcnblocks.com/component/dialog/dialog-standard-10&quot;&gt;Get Code&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Button } from &quot;@/components/ui/button&quot;;
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from &quot;@/components/ui/dialog&quot;;
import { Input } from &quot;@/components/ui/input&quot;;
import { Label } from &quot;@/components/ui/label&quot;;
import { Separator } from &quot;@/components/ui/separator&quot;;

export const title = &quot;Signup Form&quot;;

const Example = () =&amp;gt; (
  &amp;lt;Dialog&amp;gt;
    &amp;lt;DialogTrigger asChild&amp;gt;
      &amp;lt;Button variant=&quot;outline&quot;&amp;gt;Sign Up&amp;lt;/Button&amp;gt;
    &amp;lt;/DialogTrigger&amp;gt;
    &amp;lt;DialogContent className=&quot;sm:max-w-md&quot;&amp;gt;
      &amp;lt;DialogHeader&amp;gt;
        &amp;lt;DialogTitle&amp;gt;Create an account&amp;lt;/DialogTitle&amp;gt;
        &amp;lt;DialogDescription&amp;gt;
          Enter your details below to create your account.
        &amp;lt;/DialogDescription&amp;gt;
      &amp;lt;/DialogHeader&amp;gt;
      &amp;lt;div className=&quot;space-y-4&quot;&amp;gt;
        &amp;lt;div className=&quot;space-y-2&quot;&amp;gt;
          &amp;lt;Label htmlFor=&quot;name&quot;&amp;gt;Full name&amp;lt;/Label&amp;gt;
          &amp;lt;Input id=&quot;name&quot; placeholder=&quot;John Doe&quot; /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div className=&quot;space-y-2&quot;&amp;gt;
          &amp;lt;Label htmlFor=&quot;email&quot;&amp;gt;Email&amp;lt;/Label&amp;gt;
          &amp;lt;Input id=&quot;email&quot; placeholder=&quot;*Emails are not allowed*&quot; type=&quot;email&quot; /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div className=&quot;space-y-2&quot;&amp;gt;
          &amp;lt;Label htmlFor=&quot;password&quot;&amp;gt;Password&amp;lt;/Label&amp;gt;
          &amp;lt;Input id=&quot;password&quot; type=&quot;password&quot; /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;Button className=&quot;w-full&quot;&amp;gt;Create Account&amp;lt;/Button&amp;gt;
        &amp;lt;div className=&quot;relative flex items-center gap-2&quot;&amp;gt;
          &amp;lt;Separator className=&quot;flex-1&quot; /&amp;gt;
          &amp;lt;span className=&quot;shrink-0 px-2 text-xs text-muted-foreground uppercase&quot;&amp;gt;
            Or continue with
          &amp;lt;/span&amp;gt;
          &amp;lt;Separator className=&quot;flex-1&quot; /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;Button className=&quot;w-full&quot; variant=&quot;outline&quot;&amp;gt;
          &amp;lt;svg className=&quot;mr-2 h-4 w-4&quot; viewBox=&quot;0 0 24 24&quot;&amp;gt;
            &amp;lt;path
              d=&quot;M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z&quot;
              fill=&quot;#4285F4&quot;
            /&amp;gt;
            &amp;lt;path
              d=&quot;M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z&quot;
              fill=&quot;#34A853&quot;
            /&amp;gt;
            &amp;lt;path
              d=&quot;M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z&quot;
              fill=&quot;#FBBC05&quot;
            /&amp;gt;
            &amp;lt;path
              d=&quot;M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z&quot;
              fill=&quot;#EA4335&quot;
            /&amp;gt;
          &amp;lt;/svg&amp;gt;
          Continue with Google
        &amp;lt;/Button&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;DialogFooter className=&quot;sm:justify-center&quot;&amp;gt;
        &amp;lt;p className=&quot;text-sm text-muted-foreground&quot;&amp;gt;
          Already have an account?{&quot; &quot;}
          &amp;lt;button className=&quot;font-medium underline&quot; type=&quot;button&quot;&amp;gt;
            Sign in
          &amp;lt;/button&amp;gt;
        &amp;lt;/p&amp;gt;
      &amp;lt;/DialogFooter&amp;gt;
    &amp;lt;/DialogContent&amp;gt;
  &amp;lt;/Dialog&amp;gt;
);

export default Example;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;5-neobrutalism-shadcn-dialog-box&quot;&gt;5. NeoBrutalism Shadcn Dialog Box&lt;/h3&gt;
&lt;p&gt;A bold dialog design inspired by NeoBrutalism with strong visual styling.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=6700552203097579181&quot; alt=&quot;NeoBrutalism Shadcn Dialog Box&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;features-13&quot;&gt;Features&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;High contrast UI&lt;/li&gt;
&lt;li&gt;Unique design style&lt;/li&gt;
&lt;li&gt;Great for creative apps&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;tech-stack-3&quot;&gt;Tech Stack&lt;/h4&gt;
&lt;p&gt;React, Tailwind, shadcn/ui&lt;/p&gt;
&lt;p&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.retroui.dev/docs/components/dialog&quot;&gt;Get Code&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Button } from &quot;@/components/retroui/Button&quot;;
import { Dialog } from &quot;@/components/retroui/Dialog&quot;;
import { Text } from &quot;@/components/retroui/Text&quot;;
 
export default function DialogStyleDefault() {
  return (
    &amp;lt;Dialog&amp;gt;
      &amp;lt;Dialog.Trigger asChild&amp;gt;
        &amp;lt;Button&amp;gt;Open Dialog&amp;lt;/Button&amp;gt;
      &amp;lt;/Dialog.Trigger&amp;gt;
      &amp;lt;Dialog.Content&amp;gt;
        &amp;lt;Dialog.Header&amp;gt;
          &amp;lt;Text as=&quot;h5&quot;&amp;gt;Confirm your action?&amp;lt;/Text&amp;gt;
        &amp;lt;/Dialog.Header&amp;gt;
        &amp;lt;section className=&quot;flex flex-col gap-4 p-4&quot;&amp;gt;
          &amp;lt;section className=&quot;text-xl&quot;&amp;gt;
            &amp;lt;p&amp;gt;Are you sure you want to delete this item?&amp;lt;/p&amp;gt;
            &amp;lt;p&amp;gt;This action cannout be undone.&amp;lt;/p&amp;gt;
          &amp;lt;/section&amp;gt;
          &amp;lt;section className=&quot;flex w-full justify-end&quot;&amp;gt;
            &amp;lt;Dialog.Trigger asChild&amp;gt;
              &amp;lt;Button&amp;gt;Confirm&amp;lt;/Button&amp;gt;
            &amp;lt;/Dialog.Trigger&amp;gt;
          &amp;lt;/section&amp;gt;
        &amp;lt;/section&amp;gt;
      &amp;lt;/Dialog.Content&amp;gt;
    &amp;lt;/Dialog&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;final-thoughts-7&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;Dialog components are more than just popups they are essential UI building blocks.&lt;/p&gt;
&lt;h4 id=&quot;common-use-cases&quot;&gt;Common Use Cases&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Collecting emails&lt;/li&gt;
&lt;li&gt;Onboarding users&lt;/li&gt;
&lt;li&gt;Managing data&lt;/li&gt;
&lt;li&gt;Displaying forms&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Using &lt;a rel=&quot;nofollow&quot; href=&quot;https://shadcnspace.com/components&quot;&gt;pre-built shadcn components&lt;/a&gt; like these helps you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Save development time&lt;/li&gt;
&lt;li&gt;Maintain consistency&lt;/li&gt;
&lt;li&gt;Ship faster&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt; If you’re looking for more components, check out these &lt;a rel=&quot;nofollow&quot; href=&quot;https://shadcnspace.com/blocks&quot;&gt;shadcn UI blocks&lt;/a&gt; and explore the full &lt;a rel=&quot;nofollow&quot; href=&quot;http://shadcnspace.com/&quot;&gt;shadcn library&lt;/a&gt; to speed up your workflow.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/13424/shadcn-dialogs-for-react-next-js-websites</guid>
<pubDate>Tue, 24 Mar 2026 11:20:47 +0000</pubDate>
</item>
<item>
<title>We Built 3 Websites. None of Them Ranked.</title>
<link>https://coderlegion.com/13268/we-built-3-websites-none-of-them-ranked</link>
<description>&lt;p&gt;We launched three products this year at Axion Deep Labs. Three websites. All built on Next.js, all statically exported, all &quot;optimized for SEO.&quot; Except they weren't. Not even close. Here's every real mistake we made and how we found out the hard way.&lt;/p&gt;
&lt;h3 id=&quot;1-our-entire-website-was-invisible-to-google&quot;&gt;1. Our Entire Website Was Invisible to Google&lt;/h3&gt;
&lt;p&gt;This one still hurts.&lt;/p&gt;
&lt;p&gt;Next.js has a file called &lt;code&gt;loading.tsx&lt;/code&gt;. Drop it in your app directory and it shows a loading spinner while the page hydrates. Great for UX. Terrible for SEO.&lt;/p&gt;
&lt;p&gt;That file creates a Suspense boundary around ALL page content in the static HTML export. When Google's crawler hit our site, it didn't see our homepage, our service pages, or our blog posts. It saw a loading spinner. On every single page.&lt;/p&gt;
&lt;p&gt;We spent days fixing heading hierarchy, anchor text, meta descriptions. None of it mattered. The crawler was looking at an empty page the whole time. The fix was deleting one file.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Don't trust your source code for SEO. Run the build, open the actual HTML output, and look at what the crawler sees. We wrote a script that counts words and checks for spinners in our built files now.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-google-analytics-said-our-tag-was-working-it-wasn-t-&quot;&gt;2. Google Analytics Said Our Tag Was Working. It Wasn't.&lt;/h3&gt;
&lt;p&gt;Google has a tag verification tool. You click &quot;Test my website,&quot; get a green checkmark, and move on. We got the green checkmark. Two weeks later: zero data. Not low traffic. Zero.&lt;/p&gt;
&lt;p&gt;The problem was Google Consent Mode v2. We'd implemented a cookie consent banner that defaulted &lt;code&gt;analytics_storage&lt;/code&gt; to &quot;denied&quot; and only switched to &quot;granted&quot; when someone clicked &quot;Accept.&quot; Most people don't click cookie banners. They ignore them.&lt;/p&gt;
&lt;p&gt;Google's tag test checks if the script is present in the HTML. It doesn't check if consent mode is blocking data collection. Green checkmark. Zero data. Weeks of analytics, gone.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; If you're a US company, you probably don't need opt-in consent for analytics. Default to granted, make the banner an opt-out. And check your Realtime report after deploying, don't just trust the tag test.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-we-had-five-h1-tags-on-one-page&quot;&gt;3. We Had Five H1 Tags on One Page&lt;/h3&gt;
&lt;p&gt;Our homepage had a server-rendered article with an H1. A client component with another H1. Service cards with H1s inside them. Google doesn't know which one matters when there are five, so it kind of shrugs.&lt;/p&gt;
&lt;p&gt;We also had H4 tags in the footer that appeared in the DOM before the H1. Heading hierarchy was completely broken. We didn't notice because visually it looked fine.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; One H1 per page. Everything else is H2 or lower. The H1 has to be the first heading in the DOM, not just the first one visible on screen.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-every-link-on-our-site-said-learn-more-&quot;&gt;4. Every Link on Our Site Said &quot;Learn More&quot;&lt;/h3&gt;
&lt;p&gt;Twelve service cards. Every single one had a &quot;Learn more&quot; link. Identical anchor text, twelve times, pointing to twelve different pages.&lt;/p&gt;
&lt;p&gt;Google uses anchor text to understand what the linked page is about. When every anchor says &quot;Learn more,&quot; you're telling Google twelve different pages are about nothing specific.&lt;/p&gt;
&lt;p&gt;We replaced them with descriptive anchors. &quot;Explore our SEO services.&quot; &quot;See our web development process.&quot; &quot;Learn how lead capture works.&quot; Took 20 minutes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Every link on your page should have unique, descriptive anchor text. If you can't tell what the target page is about from the anchor text alone, rewrite it.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;5-client-components-killed-our-server-rendered-content&quot;&gt;5. Client Components Killed Our Server-Rendered Content&lt;/h3&gt;
&lt;p&gt;Next.js server components render to HTML on the server. Client components need JavaScript to render, which means crawlers might not see them.&lt;/p&gt;
&lt;p&gt;We had components using &lt;code&gt;usePathname()&lt;/code&gt; from Next.js. That single hook forces the entire component to become a client component. Our content shell and footer were client components for no good reason. Just to highlight the current nav item.&lt;/p&gt;
&lt;p&gt;We refactored them to server components, moved the pathname logic to a tiny client child, and suddenly our static HTML had actual content instead of empty divs waiting for hydration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; If you're using Next.js, grep your components for &lt;code&gt;&quot;use client&quot;&lt;/code&gt; and ask if each one truly needs to be client-side. One misplaced hook can make an entire page invisible to crawlers.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;6-our-privacy-page-had-the-wrong-analytics-id&quot;&gt;6. Our Privacy Page Had the Wrong Analytics ID&lt;/h3&gt;
&lt;p&gt;We switched Google Analytics properties and updated the tag in our layout. But our privacy policy still had the old measurement ID hardcoded in the text. Anyone trying to verify what we track would have seen a different ID than what was actually running.&lt;/p&gt;
&lt;p&gt;Not an SEO issue technically. But it's a trust issue. And trust issues become bounce rate issues.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;7-our-sitemap-had-wrong-dates&quot;&gt;7. Our Sitemap Had Wrong Dates&lt;/h3&gt;
&lt;p&gt;Our &lt;code&gt;sitemap.xml&lt;/code&gt; was generated at build time using &lt;code&gt;new Date()&lt;/code&gt; in JavaScript. The build ran on a CI server with a slightly off clock. Our sitemap was telling Google that pages were last modified at times that didn't match reality. We hardcoded the dates.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;8-78-images-with-zero-metadata&quot;&gt;8. 78 Images With Zero Metadata&lt;/h3&gt;
&lt;p&gt;Every image was optimized for file size and format. WebP, compressed, proper dimensions. But none had IPTC or XMP metadata. No titles, no descriptions, no keywords, no copyright.&lt;/p&gt;
&lt;p&gt;Google Image Search uses this metadata for indexing. Social platforms use it as fallback when sharing. It's also how you prove ownership if someone takes your images.&lt;/p&gt;
&lt;p&gt;We wrote &lt;code&gt;exiftool&lt;/code&gt; scripts to tag every image with contextual titles, descriptions, keywords, author, copyright, and source URLs. 78 images. Took an hour.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;9-our-old-brand-name-was-still-in-five-places&quot;&gt;9. Our Old Brand Name Was Still in Five Places&lt;/h3&gt;
&lt;p&gt;We built from a template and rebranded everything. Or so we thought. Five places still had the old brand name. Two had the old email. The hero section still had template copy. All buried in secondary pages, JSON-LD schema, and error states that we never checked.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Find and replace across your entire codebase after a rebrand. Then do it again.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-4-things-that-actually-matter&quot;&gt;The 4 Things That Actually Matter&lt;/h3&gt;
&lt;p&gt;After months of fixing SEO across three sites, it comes down to this:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Crawlability&lt;/strong&gt;&lt;br&gt;
Can Google actually see your content? Suspense boundaries, client rendering, and JavaScript dependencies can make your pages invisible.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rendered Content&lt;/strong&gt;&lt;br&gt;
What does the built HTML look like? Not your source code. The actual output. Check it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Data Accuracy&lt;/strong&gt;&lt;br&gt;
Is your analytics collecting? Are your sitemaps correct? Are your meta tags matching reality? If your data is wrong, every decision you make from it is wrong.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Structure&lt;/strong&gt;&lt;br&gt;
One H1. Descriptive anchors. Proper heading hierarchy. Image metadata. These are boring. They also compound over every page on your site.&lt;/p&gt;
&lt;p&gt;Get these right first. Then worry about keyword strategy and backlinks.&lt;/p&gt;
&lt;p&gt;We got a 98 Lighthouse score on one of our sites and it generated zero leads. Performance is necessary but not sufficient. The basics have to work.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Most of these issues are invisible unless you know where to look.&lt;br&gt;
We built a free tool that checks 60+ of them in seconds.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Try DeepAudit AI Free →&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/13268/we-built-3-websites-none-of-them-ranked</guid>
<pubDate>Fri, 20 Mar 2026 20:46:33 +0000</pubDate>
</item>
<item>
<title>Getting Started with WebGPU: The Future of High-Performance Web Graphics</title>
<link>https://coderlegion.com/13063/getting-started-with-webgpu-the-future-of-high-performance-web-graphics</link>
<description>&lt;p&gt;Web development is entering a new era — one where you can tap directly into GPU power from the browser. &lt;strong&gt;WebGPU&lt;/strong&gt; is the modern standard that replaces WebGL for many use cases, offering better performance, flexibility, and control.&lt;/p&gt;
&lt;p&gt;If you’ve ever wanted to build:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;High-performance visualizations&lt;/li&gt;
&lt;li&gt;Games in the browser&lt;/li&gt;
&lt;li&gt;AI / ML workloads on GPU&lt;/li&gt;
&lt;li&gt;Complex simulations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; WebGPU is your gateway.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-is-webgpu-&quot;&gt;What is WebGPU?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;WebGPU&lt;/strong&gt; is a low-level graphics and compute API for the web, designed to provide near-native performance by giving developers &lt;strong&gt;direct access to the GPU&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id=&quot;key-highlights&quot;&gt;Key Highlights&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Modern replacement for WebGL&lt;/li&gt;
&lt;li&gt;Works with Vulkan, Metal, and Direct3D under the hood&lt;/li&gt;
&lt;li&gt;Supports both &lt;strong&gt;graphics rendering&lt;/strong&gt; and &lt;strong&gt;compute workloads&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Designed for performance and explicit control&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-webgpu-vs-webgl&quot;&gt;⚖️ WebGPU vs WebGL&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Feature         &lt;/th&gt;&lt;th&gt;WebGL      &lt;/th&gt;&lt;th&gt;WebGPU&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;API style       &lt;/td&gt;&lt;td&gt;High-level &lt;/td&gt;&lt;td&gt;Low-level&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Performance     &lt;/td&gt;&lt;td&gt;Moderate   &lt;/td&gt;&lt;td&gt;High&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Compute shaders &lt;/td&gt;&lt;td&gt;❌          &lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;GPU control     &lt;/td&gt;&lt;td&gt;Limited    &lt;/td&gt;&lt;td&gt;Extensive&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Future-proof    &lt;/td&gt;&lt;td&gt;❌          &lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-prerequisites&quot;&gt;️ Prerequisites&lt;/h2&gt;
&lt;p&gt;Before you start:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A modern browser (Chrome, Edge, or Safari Technology Preview)&lt;/li&gt;
&lt;li&gt;Basic JavaScript knowledge&lt;/li&gt;
&lt;li&gt;Understanding of GPU concepts (optional but helpful)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; Enable WebGPU in Chrome:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;chrome://flags → Enable “Unsafe WebGPU”
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;your-first-webgpu-program&quot;&gt;Your First WebGPU Program&lt;/h2&gt;
&lt;p&gt;Let’s render a simple colored canvas.&lt;/p&gt;
&lt;h3 id=&quot;project-structure&quot;&gt;Project Structure&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-id=&quot;&gt;webgpu-app/
├── index.html
└── main.js
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;index-html&quot;&gt;index.html&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;title&amp;gt;WebGPU Demo&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;canvas id=&quot;gpuCanvas&quot;&amp;gt;&amp;lt;/canvas&amp;gt;
  &amp;lt;script type=&quot;module&quot; src=&quot;main.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-main-js&quot;&gt;⚙️ main.js&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;async function initWebGPU() {
  if (!navigator.gpu) {
    console.error(&quot;WebGPU not supported&quot;);
    return;
  }

  // 1. Request adapter &amp;amp; device
  const adapter = await navigator.gpu.requestAdapter();
  const device = await adapter.requestDevice();

  // 2. Setup canvas
  const canvas = document.getElementById(&quot;gpuCanvas&quot;);
  const context = canvas.getContext(&quot;webgpu&quot;);

  const format = navigator.gpu.getPreferredCanvasFormat();

  context.configure({
    device,
    format,
  });

  // 3. Create command encoder
  const encoder = device.createCommandEncoder();

  const textureView = context.getCurrentTexture().createView();

  // 4. Render pass
  const renderPass = encoder.beginRenderPass({
    colorAttachments: [
      {
        view: textureView,
        clearValue: { r: 0.2, g: 0.4, b: 0.8, a: 1.0 },
        loadOp: &quot;clear&quot;,
        storeOp: &quot;store&quot;,
      },
    ],
  });

  renderPass.end();

  // 5. Submit to GPU
  device.queue.submit([encoder.finish()]);
}

initWebGPU();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt; This will render a &lt;strong&gt;blue-colored canvas using GPU&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;understanding-the-pipeline&quot;&gt;Understanding the Pipeline&lt;/h2&gt;
&lt;p&gt;WebGPU works in stages:&lt;/p&gt;
&lt;h4 id=&quot;core-concepts&quot;&gt;Core Concepts&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Adapter&lt;/strong&gt; → GPU selection&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Device&lt;/strong&gt; → Logical GPU access&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Context&lt;/strong&gt; → Canvas binding&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Command Encoder&lt;/strong&gt; → Record GPU commands&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Render Pass&lt;/strong&gt; → Draw operations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Queue&lt;/strong&gt; → Execute commands&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&quot;drawing-a-triangle-next-step-&quot;&gt;Drawing a Triangle (Next Step)&lt;/h2&gt;
&lt;p&gt;To go beyond a blank screen, you need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Shaders (written in WGSL)&lt;/li&gt;
&lt;li&gt;Pipeline configuration&lt;/li&gt;
&lt;li&gt;Vertex buffers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Example WGSL shader:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-wgsl&quot;&gt;@vertex
fn vs_main(@builtin(vertex_index) VertexIndex : u32)
     -&amp;gt; @builtin(position) vec4&amp;lt;f32&amp;gt; {
  var pos = array&amp;lt;vec2&amp;lt;f32&amp;gt;, 3&amp;gt;(
    vec2&amp;lt;f32&amp;gt;(0.0, 0.5),
    vec2&amp;lt;f32&amp;gt;(-0.5, -0.5),
    vec2&amp;lt;f32&amp;gt;(0.5, -0.5)
  );
  return vec4&amp;lt;f32&amp;gt;(pos[VertexIndex], 0.0, 1.0);
}

@fragment
fn fs_main() -&amp;gt; @location(0) vec4&amp;lt;f32&amp;gt; {
  return vec4&amp;lt;f32&amp;gt;(1.0, 0.0, 0.0, 1.0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt; This renders a &lt;strong&gt;red triangle&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-why-webgpu-matters&quot;&gt;⚡ Why WebGPU Matters&lt;/h2&gt;
&lt;h4 id=&quot;performance&quot;&gt;Performance&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Lower overhead than WebGL&lt;/li&gt;
&lt;li&gt;Better CPU–GPU coordination&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;compute-power&quot;&gt;Compute Power&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Run parallel computations (AI, physics, data processing)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;future-proof&quot;&gt;Future-Proof&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Designed for modern GPUs&lt;/li&gt;
&lt;li&gt;Backed by major browser vendors&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;real-world-use-cases&quot;&gt;Real-World Use Cases&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Browser-based games (AAA-level potential)&lt;/li&gt;
&lt;li&gt;Data visualization dashboards&lt;/li&gt;
&lt;li&gt;Machine learning in browser&lt;/li&gt;
&lt;li&gt;Scientific simulations&lt;/li&gt;
&lt;li&gt;Creative coding tools&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;common-pitfalls&quot;&gt;Common Pitfalls&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Steep learning curve (low-level API)&lt;/li&gt;
&lt;li&gt;Verbose setup compared to WebGL&lt;/li&gt;
&lt;li&gt;Limited documentation (still evolving)&lt;/li&gt;
&lt;li&gt;Browser support still maturing&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;helpful-tools-amp-libraries&quot;&gt;Helpful Tools &amp;amp; Libraries&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Three.js (WebGPU renderer)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Babylon.js (WebGPU support)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;wgsl.dev (shader playground)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;conclusion-6&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;WebGPU is not just an upgrade — it’s a &lt;strong&gt;paradigm shift&lt;/strong&gt; for web development. It unlocks the full power of the GPU directly in the browser, enabling experiences that were previously impossible.&lt;/p&gt;
&lt;p&gt;While it comes with a learning curve, the payoff is massive:&lt;br&gt;
 better performance&lt;br&gt;
 deeper control&lt;br&gt;
 future-ready applications&lt;/p&gt;
&lt;hr&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/13063/getting-started-with-webgpu-the-future-of-high-performance-web-graphics</guid>
<pubDate>Tue, 17 Mar 2026 15:11:42 +0000</pubDate>
</item>
<item>
<title>Getting Started with Bun: The Fast All-in-One JavaScript Runtime</title>
<link>https://coderlegion.com/12871/getting-started-with-bun-the-fast-all-in-one-javascript-runtime</link>
<description>&lt;p&gt;The JavaScript ecosystem has traditionally relied on multiple tools:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Node.js&lt;/strong&gt; for runtime&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;npm/yarn/pnpm&lt;/strong&gt; for package management&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Webpack/Vite&lt;/strong&gt; for bundling&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Jest/Mocha&lt;/strong&gt; for testing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But modern development demands &lt;strong&gt;speed, simplicity, and fewer tools&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This is where &lt;strong&gt;Bun&lt;/strong&gt; comes in.&lt;/p&gt;
&lt;p&gt;Bun is an &lt;strong&gt;all-in-one JavaScript runtime&lt;/strong&gt; designed to replace many tools in the modern development stack while being &lt;strong&gt;extremely fast&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In this guide, you'll learn:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What Bun is&lt;/li&gt;
&lt;li&gt;Why developers are switching to it&lt;/li&gt;
&lt;li&gt;How to install and run your first app&lt;/li&gt;
&lt;li&gt;Best practices for using Bun in real projects&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-is-bun-&quot;&gt;What is Bun?&lt;/h2&gt;
&lt;p&gt;Bun is a &lt;strong&gt;JavaScript runtime + toolkit&lt;/strong&gt; built for performance.&lt;/p&gt;
&lt;p&gt;Unlike traditional setups where you install many tools separately, Bun includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JavaScript runtime&lt;/li&gt;
&lt;li&gt;Package manager&lt;/li&gt;
&lt;li&gt;Bundler&lt;/li&gt;
&lt;li&gt;Test runner&lt;/li&gt;
&lt;li&gt;Task runner&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All inside &lt;strong&gt;one tool&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Bun is written in &lt;strong&gt;Zig&lt;/strong&gt; and uses the &lt;strong&gt;JavaScriptCore&lt;/strong&gt; engine.&lt;/p&gt;
&lt;p&gt;This combination makes it &lt;strong&gt;significantly faster than traditional Node workflows&lt;/strong&gt; in many scenarios.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;why-developers-are-excited-about-bun&quot;&gt;Why Developers Are Excited About Bun&lt;/h2&gt;
&lt;p&gt;Bun focuses on &lt;strong&gt;developer experience and performance&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Key advantages:&lt;/p&gt;
&lt;h4 id=&quot;extremely-fast-package-installation&quot;&gt;Extremely Fast Package Installation&lt;/h4&gt;
&lt;p&gt;Instead of downloading packages repeatedly, Bun caches them efficiently.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bun install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This often runs &lt;strong&gt;2–10x faster than traditional package managers&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;built-in-tools&quot;&gt;Built-in Tools&lt;/h4&gt;
&lt;p&gt;With Bun you don’t need to install:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;separate bundlers&lt;/li&gt;
&lt;li&gt;separate test frameworks&lt;/li&gt;
&lt;li&gt;extra script runners&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One command handles most tasks.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;typescript-support-out-of-the-box&quot;&gt;TypeScript Support Out of the Box&lt;/h4&gt;
&lt;p&gt;Bun runs TypeScript &lt;strong&gt;without additional configuration&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bun run index.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No build step required.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;installing-bun&quot;&gt;Installing Bun&lt;/h2&gt;
&lt;p&gt;Install Bun using a single command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -fsSL https://bun.sh/install | bash
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After installation, verify:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bun --version
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you see a version number, Bun is ready.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;creating-your-first-bun-project&quot;&gt;Creating Your First Bun Project&lt;/h2&gt;
&lt;p&gt;Create a new project:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bun init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This generates a project structure like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;my-bun-app
 ├─ bun.lockb
 ├─ package.json
 ├─ index.ts
 └─ node_modules
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bun automatically configures the project.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;running-your-first-script&quot;&gt;Running Your First Script&lt;/h2&gt;
&lt;p&gt;Create &lt;code&gt;index.ts&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;console.log(&quot;Hello from Bun!&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run the file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bun run index.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Hello from Bun!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bun executes TypeScript &lt;strong&gt;without transpiling manually&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;installing-packages&quot;&gt;Installing Packages&lt;/h2&gt;
&lt;p&gt;Bun works with the existing npm ecosystem.&lt;/p&gt;
&lt;p&gt;Install a dependency:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bun add axios
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install dev dependency:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bun add -d typescript
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Remove a package:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bun remove axios
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bun also creates a &lt;strong&gt;fast lockfile&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bun.lockb
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;running-scripts&quot;&gt;Running Scripts&lt;/h2&gt;
&lt;p&gt;Scripts inside &lt;code&gt;package.json&lt;/code&gt; work the same way.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;scripts&quot;: {
    &quot;dev&quot;: &quot;bun run index.ts&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bun run dev
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;building-http-apis-with-bun&quot;&gt;Building HTTP APIs with Bun&lt;/h2&gt;
&lt;p&gt;Bun includes a &lt;strong&gt;built-in HTTP server&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Example API server:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;Bun.serve({
  port: 3000,
  fetch(req) {
    return new Response(&quot;Hello from Bun API!&quot;);
  },
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bun run server.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Open:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;http://localhost:3000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This creates a &lt;strong&gt;high-performance API server&lt;/strong&gt; with minimal code.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;running-tests-with-bun&quot;&gt;Running Tests with Bun&lt;/h2&gt;
&lt;p&gt;Bun includes a built-in test runner.&lt;/p&gt;
&lt;p&gt;Create a test:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { expect, test } from &quot;bun:test&quot;;

test(&quot;addition works&quot;, () =&amp;gt; {
  expect(2 + 2).toBe(4);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run tests:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bun test
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No additional frameworks required.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;bundling-with-bun&quot;&gt;Bundling with Bun&lt;/h2&gt;
&lt;p&gt;Bun also works as a bundler.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bun build index.ts --outdir=dist
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This bundles your project for production.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;best-practices-for-using-bun&quot;&gt;Best Practices for Using Bun&lt;/h2&gt;
&lt;p&gt;To get the most from Bun, follow these practices.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-use-bun-for-development-tooling&quot;&gt;1. Use Bun for Development Tooling&lt;/h3&gt;
&lt;p&gt;Bun works extremely well for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CLI tools&lt;/li&gt;
&lt;li&gt;development servers&lt;/li&gt;
&lt;li&gt;local utilities&lt;/li&gt;
&lt;li&gt;build scripts&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It can replace many Node-based tooling scripts.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-start-small&quot;&gt;2. Start Small&lt;/h3&gt;
&lt;p&gt;If you're migrating from &lt;strong&gt;Node.js&lt;/strong&gt;, start with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;side projects&lt;/li&gt;
&lt;li&gt;internal tools&lt;/li&gt;
&lt;li&gt;microservices&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Avoid migrating large production systems immediately.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-use-bun-with-modern-frameworks&quot;&gt;3. Use Bun with Modern Frameworks&lt;/h3&gt;
&lt;p&gt;Many frameworks already support Bun.&lt;/p&gt;
&lt;p&gt;Examples include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Next.js&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vite&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SvelteKit&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bun can run development servers faster.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-use-built-in-features-first&quot;&gt;4. Use Built-in Features First&lt;/h3&gt;
&lt;p&gt;Instead of installing extra libraries:&lt;/p&gt;
&lt;p&gt;Use Bun’s built-in tools:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;testing&lt;/li&gt;
&lt;li&gt;bundling&lt;/li&gt;
&lt;li&gt;server runtime&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This keeps your stack &lt;strong&gt;simpler and faster&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;5-keep-compatibility-in-mind&quot;&gt;5. Keep Compatibility in Mind&lt;/h3&gt;
&lt;p&gt;Although Bun supports most Node packages, some Node APIs may behave differently.&lt;/p&gt;
&lt;p&gt;Before adopting Bun in production:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;test dependencies&lt;/li&gt;
&lt;li&gt;check compatibility&lt;/li&gt;
&lt;li&gt;review build tools&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;when-should-you-use-bun-&quot;&gt;When Should You Use Bun?&lt;/h2&gt;
&lt;p&gt;Bun is excellent for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;new JavaScript projects&lt;/li&gt;
&lt;li&gt;backend APIs&lt;/li&gt;
&lt;li&gt;developer tooling&lt;/li&gt;
&lt;li&gt;TypeScript applications&lt;/li&gt;
&lt;li&gt;high-performance scripts&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It may not yet be ideal for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;legacy Node projects&lt;/li&gt;
&lt;li&gt;systems depending on complex Node APIs&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;bun-vs-node-js-quick-comparison-&quot;&gt;Bun vs Node.js (Quick Comparison)&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Feature         &lt;/th&gt;&lt;th&gt;Bun            &lt;/th&gt;&lt;th&gt;Node.js&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Runtime         &lt;/td&gt;&lt;td&gt;JavaScriptCore &lt;/td&gt;&lt;td&gt;V8&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Package Manager &lt;/td&gt;&lt;td&gt;Built-in       &lt;/td&gt;&lt;td&gt;npm/yarn&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Bundler         &lt;/td&gt;&lt;td&gt;Built-in       &lt;/td&gt;&lt;td&gt;External tools&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Test Runner     &lt;/td&gt;&lt;td&gt;Built-in       &lt;/td&gt;&lt;td&gt;Jest/Mocha&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Speed           &lt;/td&gt;&lt;td&gt;Very fast      &lt;/td&gt;&lt;td&gt;Stable but slower&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&quot;final-thoughts-8&quot;&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;Bun represents a &lt;strong&gt;new direction in the JavaScript ecosystem&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Instead of combining many tools, Bun provides a &lt;strong&gt;fast, unified developer experience&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For developers building modern applications with &lt;strong&gt;TypeScript, APIs, and tooling&lt;/strong&gt;, Bun can significantly &lt;strong&gt;simplify your workflow while improving performance&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;If you haven’t tried it yet, Bun is definitely worth exploring.&lt;/p&gt;
&lt;hr&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/12871/getting-started-with-bun-the-fast-all-in-one-javascript-runtime</guid>
<pubDate>Sat, 14 Mar 2026 18:21:10 +0000</pubDate>
</item>
<item>
<title>Deploying a React/Vite App on Google Cloud Storage + CDN: A Faster and 10× Cheaper Alternative</title>
<link>https://coderlegion.com/12529/deploying-a-react-vite-app-on-google-cloud-storage-cdn-a-faster-and-10-cheaper-alternative</link>
<description>&lt;p&gt;Modern frontend frameworks like React, Angular, and Vite produce &lt;strong&gt;static assets&lt;/strong&gt; after building the project. These assets include HTML, CSS, JavaScript, and images. Since they are static, they &lt;strong&gt;do not require a server runtime like Node.js&lt;/strong&gt; to serve them.&lt;/p&gt;
&lt;p&gt;Many developers initially deploy these apps on &lt;strong&gt;Google Cloud Run&lt;/strong&gt;, but that approach often introduces unnecessary complexity and cost. A much better option is deploying the static build to &lt;strong&gt;Google Cloud Storage (GCS) with Cloud CDN&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This article explains why &lt;strong&gt;Cloud Storage + CDN is faster, cheaper, and simpler&lt;/strong&gt;, and how you can deploy your frontend application in minutes.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;why-cloud-run-is-not-ideal-for-static-frontends&quot;&gt;Why Cloud Run is Not Ideal for Static Frontends&lt;/h2&gt;
&lt;p&gt;Cloud Run is designed for &lt;strong&gt;containerized backend applications&lt;/strong&gt;. When you deploy a React or Vite app there, you typically run a Node server such as &lt;code&gt;serve&lt;/code&gt; or &lt;code&gt;nginx&lt;/code&gt; to deliver static files.&lt;/p&gt;
&lt;p&gt;This creates several inefficiencies:&lt;/p&gt;
&lt;p&gt;• You pay for container compute time&lt;br&gt;
• Containers need to start and scale&lt;br&gt;
• Cold starts can slow down responses&lt;br&gt;
• Infrastructure is more complex than necessary&lt;/p&gt;
&lt;p&gt;For applications that only serve static assets, using a compute service is unnecessary.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;why-google-cloud-storage-cdn-is-better&quot;&gt;Why Google Cloud Storage + CDN is Better&lt;/h2&gt;
&lt;p&gt;Google Cloud Storage can serve static files directly, while Cloud CDN caches those files at edge locations worldwide.&lt;/p&gt;
&lt;p&gt;Benefits include:&lt;/p&gt;
&lt;h4 id=&quot;1-much-lower-cost&quot;&gt;1. Much Lower Cost&lt;/h4&gt;
&lt;p&gt;Cloud Storage only charges for &lt;strong&gt;storage and bandwidth&lt;/strong&gt;, not compute time. For most frontend apps, this can reduce costs by &lt;strong&gt;up to 90% compared to Cloud Run&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id=&quot;2-global-edge-performance&quot;&gt;2. Global Edge Performance&lt;/h4&gt;
&lt;p&gt;Cloud CDN caches your assets across Google's global edge network. Users download files from a location close to them, significantly improving load times.&lt;/p&gt;
&lt;h4 id=&quot;3-simpler-infrastructure&quot;&gt;3. Simpler Infrastructure&lt;/h4&gt;
&lt;p&gt;There are no containers, Dockerfiles, or runtime environments to manage. You simply upload files.&lt;/p&gt;
&lt;h4 id=&quot;4-automatic-scalability&quot;&gt;4. Automatic Scalability&lt;/h4&gt;
&lt;p&gt;Static hosting with CDN scales automatically to millions of requests without configuration.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;architecture-overview&quot;&gt;Architecture Overview&lt;/h2&gt;
&lt;p&gt;The deployment architecture looks like this:&lt;/p&gt;
&lt;p&gt;User → Cloud CDN → Google Cloud Load Balancer → Cloud Storage Bucket&lt;/p&gt;
&lt;p&gt;Static files are stored in the bucket, while CDN nodes cache frequently accessed files.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-1-build-your-application&quot;&gt;Step 1: Build Your Application&lt;/h2&gt;
&lt;p&gt;If you're using Vite or React, generate the production build.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This creates a folder such as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dist/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or for CRA:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;build/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This folder contains all files required to host the application.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-2-create-a-cloud-storage-bucket&quot;&gt;Step 2: Create a Cloud Storage Bucket&lt;/h2&gt;
&lt;p&gt;Create a bucket that will host the static files.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gcloud storage buckets create gs://my-react-app-bucket \
  --location=us-central1
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-3-make-the-bucket-public&quot;&gt;Step 3: Make the Bucket Public&lt;/h2&gt;
&lt;p&gt;Allow public access so users can retrieve the assets.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gcloud storage buckets add-iam-policy-binding gs://my-react-app-bucket \
  --member=allUsers \
  --role=roles/storage.objectViewer
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-4-upload-the-build-files&quot;&gt;Step 4: Upload the Build Files&lt;/h2&gt;
&lt;p&gt;Upload the contents of your build folder.&lt;/p&gt;
&lt;p&gt;For Vite:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gcloud storage cp -r dist/* gs://my-react-app-bucket
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For Create React App:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gcloud storage cp -r build/* gs://my-react-app-bucket
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-5-configure-the-website-entry-point&quot;&gt;Step 5: Configure the Website Entry Point&lt;/h2&gt;
&lt;p&gt;Set the default page to &lt;code&gt;index.html&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gcloud storage buckets update gs://my-react-app-bucket \
  --web-main-page-suffix=index.html \
  --web-error-page=index.html
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This ensures React Router works correctly by redirecting unknown routes to &lt;code&gt;index.html&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;step-6-enable-cloud-cdn&quot;&gt;Step 6: Enable Cloud CDN&lt;/h2&gt;
&lt;p&gt;For production-grade deployments, place the bucket behind a &lt;strong&gt;Cloud Load Balancer with Cloud CDN enabled&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Benefits include:&lt;/p&gt;
&lt;p&gt;• Global caching&lt;br&gt;
• HTTPS support&lt;br&gt;
• Custom domains&lt;br&gt;
• Faster asset delivery&lt;/p&gt;
&lt;p&gt;While optional for small projects, CDN is highly recommended for real-world applications.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;cost-comparison&quot;&gt;Cost Comparison&lt;/h2&gt;
&lt;p&gt;Typical monthly cost for a medium frontend application:&lt;/p&gt;
&lt;p&gt;Cloud Run Deployment&lt;/p&gt;
&lt;p&gt;• Compute charges&lt;br&gt;
• Container startup time&lt;br&gt;
• Networking overhead&lt;/p&gt;
&lt;p&gt;Estimated cost: &lt;strong&gt;$10–$30/month&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Cloud Storage + CDN Deployment&lt;/p&gt;
&lt;p&gt;• Storage for assets&lt;br&gt;
• Bandwidth usage only&lt;/p&gt;
&lt;p&gt;Estimated cost: &lt;strong&gt;$1–$5/month&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;when-you-should-use-cloud-run-instead&quot;&gt;When You Should Use Cloud Run Instead&lt;/h2&gt;
&lt;p&gt;Cloud Run is still the right choice when your application includes:&lt;/p&gt;
&lt;p&gt;• Backend APIs&lt;br&gt;
• Server-side rendering (Next.js SSR)&lt;br&gt;
• Authentication services&lt;br&gt;
• AI or database integrations&lt;/p&gt;
&lt;p&gt;In these cases, compute is required.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;final-thoughts-9&quot;&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;If your application is a &lt;strong&gt;pure frontend build&lt;/strong&gt;, deploying it on Cloud Run is unnecessary. Hosting the build on &lt;strong&gt;Google Cloud Storage with Cloud CDN&lt;/strong&gt; is simpler, faster, and significantly cheaper.&lt;/p&gt;
&lt;p&gt;By eliminating containers and compute layers, you can deliver your application globally with minimal infrastructure while benefiting from Google’s edge network.&lt;/p&gt;
&lt;p&gt;For most React, Angular, and Vite applications, &lt;strong&gt;Cloud Storage + CDN is the optimal production deployment strategy&lt;/strong&gt;.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/12529/deploying-a-react-vite-app-on-google-cloud-storage-cdn-a-faster-and-10-cheaper-alternative</guid>
<pubDate>Tue, 10 Mar 2026 15:09:29 +0000</pubDate>
</item>
<item>
<title>Beyond Clean Code: Mastering Parallel Data Fetching with React Router's defer</title>
<link>https://coderlegion.com/12211/beyond-clean-code-mastering-parallel-data-fetching-with-react-routers-defer</link>
<description>&lt;p&gt;In my &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/12179/stop-writing-messy-states-why-i-swapped-useeffect-for-react-router-loaders&quot;&gt;previous post&lt;/a&gt;, I discussed how to get rid of Messy State by moving from &lt;code&gt;useEffect&lt;/code&gt; to &lt;code&gt;React Router Loaders&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We successfully cleaned up our components, but we introduced a new challenge; The &quot;Waiting Game.&quot;&lt;/p&gt;
&lt;p&gt;Because standard Loaders fetch data before the route renders, if your API takes 3 seconds to respond, your user is stuck looking at the old page until that loader finishes. To the user, it feels like the app is frozen or the link is broken.&lt;/p&gt;
&lt;p&gt;Today, we solve that with the &lt;a rel=&quot;nofollow&quot; href=&quot;https://reactrouter.com/6.30.2/guides/deferred&quot;&gt;Defer Pattern&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Understanding the Logic: What exactly is &quot;defer&quot;?&lt;/h2&gt;
&lt;p&gt;To understand defer, you have to think about how a normal &lt;code&gt;loader&lt;/code&gt; works. Usually, a &lt;code&gt;loader&lt;/code&gt; is like a waiter who won't leave the kitchen until every single dish in your order is cooked. If the steak takes 20 minutes, you don't even get your water until the steak is done.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;defer&lt;/code&gt; changes the rules. It allows the waiter to bring the water and appetizers (Fast Data) immediately, while the steak (Slow Data) is still cooking.&lt;/p&gt;
&lt;h4&gt;The Magic of the Promise&lt;/h4&gt;
&lt;p&gt;When you use &lt;code&gt;defer&lt;/code&gt;, you aren't returning raw data; you are returning a Promise.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The Loader: Tells React Router, &quot;Here is some data I have now, and here is a 'ticket' (the promise) for data that is coming later.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Component: Renders immediately. It doesn't wait for that &quot;ticket&quot; to resolve.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The Implementation: v6 vs v7&lt;/h2&gt;
&lt;p&gt;For &lt;code&gt;v6&lt;/code&gt;, to make this work, we need a three-part harmony: the &lt;code&gt;defer&lt;/code&gt; utility, the &lt;code&gt;&amp;lt;Await&amp;gt;&lt;/code&gt; component, and React's built-in &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt;.&lt;br&gt;
However, React Router recently evolved, in &lt;code&gt;v7&lt;/code&gt;, we don't need &lt;code&gt;defer&lt;/code&gt; as the router is smart enough to handle promises automatically.&lt;/p&gt;
&lt;h4&gt;Step 1: Edit the Loader&lt;/h4&gt;
&lt;p&gt;Instead of using await for every fetch, we only await the critical data.&lt;/p&gt;
&lt;p&gt;In React Router v6&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// for v6.4+ we use the &quot;defer&quot;
import { defer } from &quot;react-router-dom&quot;;

export async function loader({ params }) {
  // CRITICAL DATA: We `await` this. The page won't load until we have them.
  const productReq = await fetch(`/api/product/${params.id}`);
  const product = await productReq.json();

  // NON-CRITICAL DATA: No `await` here! We pass the promise directly.
  const reviewsPromise = fetch(`/api/product/${params.id}/reviews`).then(res =&amp;gt; res.json());

  return defer({
    product,
    reviews: reviewsPromise 
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In React Router v7&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// for v7 we use don't use the &quot;defer&quot;
export async function loader({ params }) {
  // CRITICAL DATA: We `await` this. The page won't load until we have them.
  const productReq = await fetch(`/api/product/${params.id}`);
  const product = await productReq.json();

  // NON-CRITICAL DATA: No `await` here! We pass the promise directly.
  const reviewsPromise = fetch(`/api/product/${params.id}/reviews`).then(res =&amp;gt; res.json());

  return {
    product,
    reviews: reviewsPromise 
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Step 2: The UI (Unwrapping the Promise)&lt;/h4&gt;
&lt;p&gt;Regardless of which version you use, the UI implementation is the same.&lt;/p&gt;
&lt;p&gt;Since &lt;code&gt;reviews&lt;/code&gt; is a Promise, we can't just &lt;code&gt;.map()&lt;/code&gt; over it instantly. We use the &lt;code&gt;&amp;lt;Await&amp;gt;&lt;/code&gt; component to &quot;unwrap&quot; it once it arrives.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import { useLoaderData, Await } from &quot;react-router-dom&quot;;
import { Suspense } from &quot;react&quot;;

export default function ProductPage() {
  const { product, reviews } = useLoaderData();

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;{product.title}&amp;lt;/h1&amp;gt; {/* Renders instantly */}

      &amp;lt;Suspense fallback={&amp;lt;p&amp;gt;Loading reviews...&amp;lt;/p&amp;gt;}&amp;gt;
        &amp;lt;Await resolve={reviews} errorElement={&amp;lt;p&amp;gt;Error loading reviews!&amp;lt;/p&amp;gt;}&amp;gt;
          {(resolvedReviews) =&amp;gt; (
            &amp;lt;ul&amp;gt;
              {resolvedReviews.map(r =&amp;gt; &amp;lt;li key={r.id}&amp;gt;{r.comment}&amp;lt;/li&amp;gt;)}
            &amp;lt;/ul&amp;gt;
          )}
        &amp;lt;/Await&amp;gt;
      &amp;lt;/Suspense&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Why this is a UX Game Changer&lt;/h2&gt;
&lt;h4&gt;1. Faster Perceived Performance&lt;/h4&gt;
&lt;p&gt;The user sees the &quot;shell&quot; of the page and the primary content almost instantly. They can start reading the product description while the reviews are still &quot;streaming&quot; in over the wire.&lt;/p&gt;
&lt;h4&gt;2. No more &quot;All-or-Nothing&quot; UI&lt;/h4&gt;
&lt;p&gt;In the old &lt;code&gt;useEffect&lt;/code&gt; days, if one API call failed, you often ended up with a giant error screen. With &lt;code&gt;&amp;lt;Await&amp;gt;&lt;/code&gt;, you get an &lt;code&gt;errorElement&lt;/code&gt; property. If the reviews fail, only that section shows an error. The rest of your page stays functional.&lt;/p&gt;
&lt;h4&gt;3. Better than &quot;Global&quot; Loading Spinners&lt;/h4&gt;
&lt;p&gt;Instead of a giant spinner that covers the whole screen, you can use Skeleton Screens in your fallback to reserve space, preventing that annoying &quot;layout shift&quot; when data pops in.&lt;/p&gt;
&lt;h2&gt;When to defer and when not to&lt;/h2&gt;
&lt;p&gt;Since the focus is user experience, wrong use can act against the goal&lt;br&gt;
&lt;/p&gt;&lt;table&gt;
  &lt;thead&gt;
&lt;/thead&gt;&amp;lt;tr&amp;gt;
  &amp;lt;th&amp;gt;Scenario&amp;lt;/th&amp;gt;
  &amp;lt;th&amp;gt;Use Standard await&amp;lt;/th&amp;gt;
  &amp;lt;th&amp;gt;Use defer&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;

  
  &lt;tbody&gt;
&amp;lt;tr&amp;gt;
  &amp;lt;td&amp;gt;Crucial Data (SEO/Meta tags)&amp;lt;/td&amp;gt;
  &amp;lt;td&amp;gt;✅ Yes&amp;lt;/td&amp;gt;
  &amp;lt;td&amp;gt;❌ No&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
  &amp;lt;td&amp;gt;Fast APIs (&amp;lt; 200ms)&amp;lt;/td&amp;gt;
  &amp;lt;td&amp;gt;✅ Yes&amp;lt;/td&amp;gt;
  &amp;lt;td&amp;gt;❌ No&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
  &amp;lt;td&amp;gt;Slow Analytics/Logs&amp;lt;/td&amp;gt;
  &amp;lt;td&amp;gt;❌ No&amp;lt;/td&amp;gt;
  &amp;lt;td&amp;gt;✅ Yes&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
  &amp;lt;td&amp;gt;Secondary Page Content&amp;lt;/td&amp;gt;
  &amp;lt;td&amp;gt;❌ No&amp;lt;/td&amp;gt;
  &amp;lt;td&amp;gt;✅ Yes&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;

  &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Introducing &lt;code&gt;Loaders&lt;/code&gt; cleaned up our code in &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/12179/stop-writing-messy-states-why-i-swapped-useeffect-for-react-router-loaders&quot;&gt;Part 1&lt;/a&gt;, but &lt;code&gt;defer&lt;/code&gt; makes our apps feel like professional-grade software. By strategically choosing what data to wait for and what data to stream, you give your users a snappier, more reliable experience.&lt;/p&gt;
&lt;p&gt;What’s your favorite way to improve user experience? Let me know in the comments!&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/12211/beyond-clean-code-mastering-parallel-data-fetching-with-react-routers-defer</guid>
<pubDate>Thu, 26 Feb 2026 18:42:15 +0000</pubDate>
</item>
<item>
<title>How we reached 100K+ page views in 28 days – A transparent dev tool growth breakdown (2026)</title>
<link>https://coderlegion.com/12207/how-we-reached-100k-page-views-in-28-days-a-transparent-dev-tool-growth-breakdown-2026</link>
<description>&lt;p&gt;I’m  &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/suniljoshi19&quot;&gt;Sunil Joshi&lt;/a&gt;&lt;/strong&gt;, designer, frontend developer, and digital product builder.&lt;/p&gt;
&lt;p&gt;For the past  &lt;strong&gt;15 years&lt;/strong&gt;, I’ve been building and running digital products. And one thing I’ve learned:  &lt;strong&gt;Growth isn’t magic&lt;/strong&gt;. It’s structure.&lt;/p&gt;
&lt;p&gt;Recently, we launched  &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;http://shadcnspace.com/&quot;&gt;Shadcn Space&lt;/a&gt;&lt;/strong&gt;, a niche Shadcn UI library built specifically for developers who want production-ready blocks and landing pages.&lt;/p&gt;
&lt;p&gt;In the first 28 days after launch, we generated:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;102,000+&lt;/strong&gt;  page views&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;18,000+&lt;/strong&gt;  sessions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1,700+&lt;/strong&gt;  organic clicks&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;300+&lt;/strong&gt;  GitHub stars&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;500+&lt;/strong&gt;  waitlist signups&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;151K+&lt;/strong&gt;  Reddit Views&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;No paid ads.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It was the result of years of building digital products,  &lt;strong&gt;making mistakes, understanding distribution&lt;/strong&gt;, and applying those lessons intentionally.&lt;/p&gt;
&lt;p&gt;In this post, I’ll break down exactly how we achieved this step by step so other builders and founders can apply the same principles to their own products.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-build-curiosity-before-launch&quot;&gt;&lt;strong&gt;1  – Build curiosity before launch&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Most founders build privately for months and then expect a single launch post to change everything.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That rarely works.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Before launching  &lt;strong&gt;ShadcnSpace&lt;/strong&gt;  publicly, we created momentum intentionally.&lt;/p&gt;
&lt;p&gt;We:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Built a  &lt;strong&gt;waitlist&lt;/strong&gt;  landing page&lt;/li&gt;
&lt;li&gt;Shared preview videos of real UI blocks on  &lt;strong&gt;Twitter&lt;/strong&gt;  and  &lt;strong&gt;Reddit&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Showed actual product quality, not mockups&lt;/li&gt;
&lt;li&gt;Posted consistently for  &lt;strong&gt;3 – 4 weeks&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;This helped us get  &lt;strong&gt;500&lt;/strong&gt;+ users joining the Waitlist&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This early phase was not about traffic. It was about positioning.&lt;/p&gt;
&lt;p&gt;When people repeatedly see progress, they subconsciously assign credibility. They feel part of the journey. By the time we launched, we weren’t introducing a new product, we were releasing something people were already expecting.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Social Proof:&lt;/strong&gt;  &lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.reddit.com/r/shadcn/comments/1q5i1f9/im_building_a_curated_library_of_shadcn_ui_blocks/&quot;&gt;Waitlist reddit post&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.reddit.com/r/shadcn/comments/1q4lt6u/agency_open_source_hero_section_coming_soon_on/&quot;&gt;One of preview reddit post&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/suniljoshi19/status/2000924363454124350&quot;&gt;Waitlist tweet&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/suniljoshi19/status/2001292181278212531&quot;&gt;One of preview Twitter post&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Key Insight:&lt;/strong&gt; Don’t build in silence. Build in public.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-open-source-as-a-trust-engine&quot;&gt;&lt;strong&gt;2  – Open Source as a Trust Engine&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;On  &lt;strong&gt;January 26&lt;/strong&gt;, we released the  &lt;strong&gt;open-source&lt;/strong&gt;  version.&lt;/p&gt;
&lt;p&gt;Within 3 weeks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;300+ GitHub stars&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;High-quality developer feedback&lt;/li&gt;
&lt;li&gt;Organic shares within dev communities&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Open source does something powerful for developer tools: it removes skepticism.&lt;/p&gt;
&lt;p&gt;Developers do not trust landing pages.  &lt;strong&gt;They trust code.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When someone can inspect your repository, evaluate the structure, and understand implementation decisions, the barrier to adoption drops dramatically.&lt;/p&gt;
&lt;p&gt;Instead of asking people to “trust us,” we let them verify us.&lt;/p&gt;
&lt;p&gt;That credibility didn’t just drive GitHub stars; it also amplified SEO clicks, Reddit conversions, and direct traffic.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Social Proof:&lt;/strong&gt;&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/shadcnspace/shadcnspace&quot;&gt;GitHub Repo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key Insight:&lt;/strong&gt; Open source reduces trust friction.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Read more  &lt;a rel=&quot;nofollow&quot; href=&quot;https://shadcnspace.com/blog/developer-tool-growth-plan&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/12207/how-we-reached-100k-page-views-in-28-days-a-transparent-dev-tool-growth-breakdown-2026</guid>
<pubDate>Thu, 26 Feb 2026 18:42:01 +0000</pubDate>
</item>
<item>
<title>The Bug That Disappeared… and Then Quietly Multiplied</title>
<link>https://coderlegion.com/11566/the-bug-that-disappeared-and-then-quietly-multiplied</link>
<description>&lt;h3 id=&quot;every-engineer-eventually-meets-a-production-issue-that-refuses-to-behave-like-a-normal-bug-&quot;&gt;Every engineer eventually meets a production issue that refuses to behave like a normal bug.&lt;/h3&gt;
&lt;p&gt;It doesn’t crash loudly.&lt;br&gt;
It doesn’t point to a clear stack trace.&lt;br&gt;
And just when you think it’s gone… it returns, stronger and harder to explain. This is the story of one of those bugs and the lesson it left behind inside a distributed system.&lt;/p&gt;
&lt;h3 id=&quot;a-rule-that-seemed-impossible-to-break-&quot;&gt;A Rule That Seemed Impossible to Break…&lt;/h3&gt;
&lt;p&gt;In the platform I was working on, each organization could define multiple user roles.&lt;/p&gt;
&lt;p&gt;Among them, one role could be marked as the &lt;strong&gt;default&lt;/strong&gt; automatically applied in specific workflows. And there was a strict invariant: Only one default role should exist within a single organization.&lt;/p&gt;
&lt;p&gt;Simple. Logical.&lt;br&gt;
The kind of rule you expect the system to respect forever. Until a client reported something strange:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Two default roles existed at the same time.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;three-days-inside-the-wrong-place-&quot;&gt;Three Days Inside the Wrong Place…&lt;/h3&gt;
&lt;p&gt;Because the architecture was split across multiple microservices, our first instinct was to trace the obvious paths:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The role-management API.&lt;/li&gt;
&lt;li&gt;Permission orchestration logic.&lt;/li&gt;
&lt;li&gt;Frontend validation.&lt;/li&gt;
&lt;li&gt;Synchronous service calls.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For three days we followed request flows, reviewed logs, replayed scenarios, and inspected data transitions between services. Nothing explained the duplication.&lt;/p&gt;
&lt;p&gt;Even more puzzling:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Out of hundreds of organizations.&lt;/li&gt;
&lt;li&gt;Only one showed the issue.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With no reproducible path and pressure to restore stability, we removed the duplicate records directly from production data.&lt;/p&gt;
&lt;p&gt;The error disappeared.&lt;/p&gt;
&lt;p&gt;And, like many teams after an exhausting investigation, we accepted the quiet and moved on.&lt;/p&gt;
&lt;h3 id=&quot;six-months-later-the-quiet-broke-&quot;&gt;Six Months Later, the Quiet Broke…&lt;/h3&gt;
&lt;p&gt;The same client returned.&lt;/p&gt;
&lt;p&gt;Same symptom.&lt;br&gt;
Same invariant violation.&lt;/p&gt;
&lt;p&gt;But this time the scale had changed.&lt;/p&gt;
&lt;p&gt;Now the issue appeared across &lt;strong&gt;dozens of organizations&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Cleaning the data again would hide the problem — not solve it.&lt;br&gt;
And in a distributed system, repeated symptoms usually mean one thing: The real source lives outside the path you’re observing.&lt;/p&gt;
&lt;h3 id=&quot;looking-beyond-synchronous-flows&quot;&gt;Looking Beyond Synchronous Flows&lt;/h3&gt;
&lt;p&gt;Instead of focusing only on request-response behavior, we mapped everything that could modify roles across the microservice landscape:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;asynchronous consumers&lt;/li&gt;
&lt;li&gt;retry queues&lt;/li&gt;
&lt;li&gt;scheduled synchronization jobs&lt;/li&gt;
&lt;li&gt;historical migration utilities&lt;/li&gt;
&lt;li&gt;cross-service reconciliation logic&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In distributed architectures, these background actors often &lt;strong&gt;carry just as much authority&lt;/strong&gt; as the primary APIs but far less visibility.&lt;/p&gt;
&lt;p&gt;And that’s exactly where the trail led.&lt;/p&gt;
&lt;h3 id=&quot;the-hidden-execution-path&quot;&gt;The Hidden Execution Path&lt;/h3&gt;
&lt;p&gt;A background synchronization process existed to guarantee that every organization always had a valid default role.&lt;/p&gt;
&lt;p&gt;Under rare retry conditions, the process could execute using &lt;strong&gt;outdated state from another service&lt;/strong&gt;. Because the system relied on eventual consistency, the check confirming whether a default already existed could briefly return stale information.&lt;/p&gt;
&lt;p&gt;The retry would then recreate a default role that was already present&lt;br&gt;
producing two perfectly valid records from the database’s perspective,&lt;br&gt;
but a broken invariant from the system’s perspective.&lt;/p&gt;
&lt;p&gt;Suddenly, every mystery aligned:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;why the first occurrence was extremely rare&lt;/li&gt;
&lt;li&gt;why synchronous debugging revealed nothing&lt;/li&gt;
&lt;li&gt;why the problem resurfaced months later at scale&lt;/li&gt;
&lt;li&gt;why deleting rows only worked temporarily&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The duplication wasn’t caused by a simple bug in one service. It was born from &lt;strong&gt;timing, retries, and stale state across service boundaries.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;fixing-the-system-not-the-symptom-&quot;&gt;Fixing the System, Not the Symptom…&lt;/h3&gt;
&lt;p&gt;Once we understood the true failure mode, the solution required coordinated changes across services:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;making the synchronization workflow &lt;strong&gt;strictly idempotent&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;forcing &lt;strong&gt;fresh state validation&lt;/strong&gt; before any retry write&lt;/li&gt;
&lt;li&gt;tightening &lt;strong&gt;cross-service consistency guarantees&lt;/strong&gt; around default role    assignment&lt;/li&gt;
&lt;li&gt;adding &lt;strong&gt;observability&lt;/strong&gt; to detect invariant violations immediately&lt;br&gt;
After these changes were deployed, something important happened:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;The duplicates stopped appearing. Completely.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;No more emergency cleanups.&lt;br&gt;
No more unexplained reoccurrences.&lt;br&gt;
Just stability earned, not assumed.&lt;/p&gt;
&lt;h3 id=&quot;what-this-incident-really-taught-me&quot;&gt;What This Incident Really Taught Me&lt;/h3&gt;
&lt;p&gt;Before this, I thought difficult bugs usually lived in complex code.But distributed systems teach a different lesson:&lt;/p&gt;
&lt;p&gt;The hardest failures often emerge&lt;br&gt;
not from broken logic,&lt;br&gt;
but from correct logic running at the wrong time&lt;br&gt;
with the wrong state&lt;br&gt;
in the wrong service.&lt;/p&gt;
&lt;p&gt;Debugging microservices isn’t only about reading code.&lt;br&gt;
It’s about understanding &lt;strong&gt;time, coordination, and invisible execution paths.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;the-ending-that-matters-&quot;&gt;The Ending That Matters…&lt;/h3&gt;
&lt;p&gt;In the end, the success wasn’t that we found a rare edge case.&lt;/p&gt;
&lt;p&gt;The real victory was identifying a hidden cross-service interaction, correcting it at the architectural level,&lt;br&gt;
and ensuring the same class of failure could never quietly return.&lt;/p&gt;
&lt;p&gt;Because in distributed systems, the most satisfying resolution isn’t a clever patch. It’s the moment when uncertainty disappears across &lt;strong&gt;every service boundary&lt;/strong&gt; and stays gone.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/11566/the-bug-that-disappeared-and-then-quietly-multiplied</guid>
<pubDate>Fri, 13 Feb 2026 09:00:14 +0000</pubDate>
</item>
<item>
<title>How I Built a React Portfolio in 7 Days That Landed ₹1.2L in Freelance Work</title>
<link>https://coderlegion.com/11265/how-i-built-a-react-portfolio-in-7-days-that-landed-1-2l-in-freelance-work</link>
<description>&lt;p&gt;If you are a React developer stuck in tutorial hell with no real portfolio, this walkthrough is for you.&lt;/p&gt;
&lt;p&gt;In this article, I’ll break down how I built a &lt;strong&gt;React portfolio website with Vite and Tailwind CSS&lt;/strong&gt; that helped me land &lt;strong&gt;₹1.2L+ in freelance projects&lt;/strong&gt; in under three weeks. No paid tools, no over‑engineered stack just a clean, fast portfolio that real clients could open and understand.&lt;/p&gt;
&lt;h3 id=&quot;step-1-set-up-a-vite-react-project&quot;&gt;Step 1: Set up a Vite + React project&lt;/h3&gt;
&lt;p&gt;Instead of Create React App, I went with Vite because it is extremely fast and lightweight for React projects.&lt;/p&gt;
&lt;p&gt;Run these commands:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm create vite@latest my-portfolio -- --template react
cd my-portfolio
npm install
npm run dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your development server should start at &lt;a rel=&quot;nofollow&quot; href=&quot;http://localhost:5173&quot;&gt;http://localhost:5173&lt;/a&gt;. Once it is running, clear out the default content in &lt;code&gt;App.jsx&lt;/code&gt; so you can start from a blank slate.&lt;/p&gt;
&lt;h3 id=&quot;step-2-configure-tailwind-css&quot;&gt;Step 2: Configure Tailwind CSS&lt;/h3&gt;
&lt;p&gt;Tailwind handled all the styling for this project, and getting the config right early made customization much easier.&lt;/p&gt;
&lt;p&gt;Install Tailwind and its dependencies:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Update &lt;code&gt;tailwind.config.js&lt;/code&gt; to scan the right files:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export default {
  content: [
    &quot;./index.html&quot;,
    &quot;./src/**/*.{js,ts,jsx,tsx}&quot;,
  ],
  theme: {
    extend: {
      colors: {
        primary: '#3b82f6',
        dark: '#1e293b',
      },
      fontFamily: {
        sans: ['Inter', 'sans-serif'],
      },
    },
  },
  plugins: [],
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then add the Tailwind directives to &lt;code&gt;src/index.css&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@tailwind base;
@tailwind components;
@tailwind utilities;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point, you can start using Tailwind utility classes directly in your JSX.&lt;/p&gt;
&lt;h3 id=&quot;step-3-build-reusable-components&quot;&gt;Step 3: Build reusable components&lt;/h3&gt;
&lt;p&gt;To move fast, I leaned heavily on component‑driven design. One of the core building blocks was a &lt;strong&gt;ProjectCard&lt;/strong&gt; component that I reused for each portfolio project.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const ProjectCard = ({ title, description, tech, liveUrl, githubUrl, image }) =&amp;gt; {
  return (
    &amp;lt;div className=&quot;bg-dark rounded-xl shadow-md overflow-hidden flex flex-col&quot;&amp;gt;
      {image &amp;amp;&amp;amp; (
        &amp;lt;img
          src={image}
          alt={title}
          className=&quot;h-48 w-full object-cover&quot;
        &amp;gt;
      )}
      &amp;lt;div className=&quot;p-6 flex-1 flex flex-col&quot;&amp;gt;
        &amp;lt;h3 className=&quot;text-xl font-semibold text-white mb-2&quot;&amp;gt;
          {title}
        &amp;lt;/h3&amp;gt;
        &amp;lt;p className=&quot;text-slate-300 mb-4&quot;&amp;gt;
          {description}
        &amp;lt;/p&amp;gt;
        &amp;lt;div className=&quot;flex flex-wrap gap-2 mb-4&quot;&amp;gt;
          {tech.map((t, i) =&amp;gt; (
            &amp;lt;span
              key={i}
              className=&quot;px-3 py-1 text-xs rounded-full bg-slate-800 text-slate-200&quot;
            &amp;gt;
              {t}
            &amp;lt;/span&amp;gt;
          ))}
        &amp;lt;/div&amp;gt;
        &amp;lt;div className=&quot;mt-auto flex gap-3&quot;&amp;gt;
          {liveUrl &amp;amp;&amp;amp; (
            &amp;lt;a
              href={liveUrl}
              target=&quot;_blank&quot;
              rel=&quot;noreferrer&quot;
              className=&quot;text-sm font-medium text-primary hover:underline&quot;
            &amp;gt;
              Live Demo
            &amp;lt;/a&amp;gt;
          )}
          {githubUrl &amp;amp;&amp;amp; (
            &amp;lt;a
              href={githubUrl}
              target=&quot;_blank&quot;
              rel=&quot;noreferrer&quot;
              className=&quot;text-sm font-medium text-slate-300 hover:underline&quot;
            &amp;gt;
              GitHub
            &amp;lt;/a&amp;gt;
          )}
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can then use ProjectCard in your main page or a Projects section by passing in different props for each project.&lt;/p&gt;
&lt;h3 id=&quot;step-4-deploy-to-vercel-in-minutes&quot;&gt;Step 4: Deploy to Vercel in minutes&lt;/h3&gt;
&lt;p&gt;Once the portfolio was ready, I pushed the code to &lt;strong&gt;GitHub&lt;/strong&gt; and deployed with &lt;strong&gt;Vercel&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Basic steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Push the repository to GitHub.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go to &lt;a rel=&quot;nofollow&quot; href=&quot;https://vercel.com/&quot;&gt;vercel.com&lt;/a&gt; and sign in with GitHub.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click Import Project and select your portfolio repo.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Vercel automatically detects Vite just click Deploy.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Vercel gives you a URL like &lt;code&gt;yourname.vercel.app&lt;/code&gt;. You can later attach a custom domain from the project settings; I used a &lt;code&gt;.tech&lt;/code&gt; domain for a professional look.&lt;/p&gt;
&lt;p&gt;If you prefer Netlify, you can run &lt;code&gt;npm run build&lt;/code&gt; and drag‑and‑drop the &lt;code&gt;dist&lt;/code&gt; folder into Netlify’s UI both options are free and include SSL.&lt;/p&gt;
&lt;h3 id=&quot;step-5-portfolio-layout-that-actually-converts&quot;&gt;Step 5: Portfolio layout that actually converts&lt;/h3&gt;
&lt;p&gt;You can see the live portfolio at &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.dtechsolutions.tech/&quot;&gt;dtechsolutions.tech&lt;/a&gt;. Here is the structure that helped me convert visitors into paying freelance clients:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hero section:&lt;/strong&gt; A clear headline such as “Full‑Stack Developer specializing in React + Node.js” plus a concise subheading and a primary CTA.&lt;/p&gt;
&lt;p&gt;3 - 4 strong projects: Focus on a handful of solid, deployed projects with live demos and GitHub links.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tech stack badges:&lt;/strong&gt; Visual tech tags or icons (for example with react-icons) so clients quickly understand what you use.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Contact form:&lt;/strong&gt; I used Formspree for contact handling without needing a custom backend.&lt;/p&gt;
&lt;p&gt;Tech stack used&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;React 18&lt;/li&gt;
&lt;li&gt;Vite 5&lt;/li&gt;
&lt;li&gt;Tailwind CSS 3&lt;/li&gt;
&lt;li&gt;Vercel (deployment)&lt;/li&gt;
&lt;li&gt;Formspree (contact form)&lt;/li&gt;
&lt;li&gt;GitHub (version control)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This stack was enough to ship fast, iterate quickly, and still look professional to clients.&lt;/p&gt;
&lt;p&gt;Pros and cons of this stack&lt;br&gt;
text&lt;br&gt;
| Pros                                          | Cons                                         |&lt;br&gt;
| --------------------------------------------- | -------------------------------------------- |&lt;br&gt;
| Tailwind utility classes speed up styling     | Initial learning curve for Tailwind syntax   |&lt;br&gt;
| Component reusability saves a lot of time     | Dark mode requires custom setup              |&lt;br&gt;
| Vite dev server is extremely fast             | Smaller ecosystem compared to Next.js        |&lt;br&gt;
| Free deployment on Vercel/Netlify with SSL    | Client-side rendering only (limited SEO)     |&lt;br&gt;
| Mobile responsiveness is easy to achieve      | Larger CSS bundle than handcrafted CSS       |&lt;br&gt;
From zero portfolio to paid gigs&lt;br&gt;
Within about two weeks of making the portfolio public, I started getting actual freelance work:&lt;/p&gt;
&lt;p&gt;On Reddit (subreddits like &lt;code&gt;/r/forhire&lt;/code&gt; and &lt;code&gt;/r/webdev&lt;/code&gt;), I landed a couple of gigs by replying to “looking for developer” posts with my portfolio link.&lt;/p&gt;
&lt;p&gt;On LinkedIn, I shared the portfolio with &lt;strong&gt;#opentowork&lt;/strong&gt; and relevant tech hashtags, which led to multiple projects.&lt;/p&gt;
&lt;p&gt;Typical project sizes ranged from ₹30K for simpler landing pages to ₹50K+ for dashboards and more complex builds.&lt;/p&gt;
&lt;p&gt;Clients particularly liked that they could click a live link and interact with the projects instead of just seeing GitHub repositories.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Practical tips for Indian freelancers&lt;/strong&gt;&lt;br&gt;
If you are targeting Indian clients specifically, these points helped me:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Quote in rupees initially:&lt;/strong&gt; For early projects, starting around ₹20K - ₹30K for smaller builds works well; raise rates as you collect testimonials.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use your time zone as a feature:&lt;/strong&gt; Promise quick responses and same‑day iterations for IST‑based clients.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Know local payment flows:&lt;/strong&gt; Show that you can integrate Razorpay/UPI or other Indian payment gateways.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mention local hosting experience:&lt;/strong&gt; If clients care about data locality or compliance, highlight any work with Indian hosting providers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Be visible on LinkedIn:&lt;/strong&gt; Founders and decision makers are very active there comment on their posts, share your work, and keep your profile updated.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Final thoughts&lt;/strong&gt;&lt;br&gt;
You do not need an overcomplicated stack or months of planning to start getting freelance work. A simple combination of React, Tailwind, and Vercel was enough for me to go from no portfolio to real paid projects in a matter of weeks.&lt;/p&gt;
&lt;p&gt;Focus on shipping a functional, honest portfolio, then refine it as you learn from real client feedback. Your first freelance opportunity might be just one deployed React portfolio away and if you are curious, you can check out mine here: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.dtechsolutions.tech/&quot;&gt;https://www.dtechsolutions.tech/&lt;/a&gt;.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/11265/how-i-built-a-react-portfolio-in-7-days-that-landed-1-2l-in-freelance-work</guid>
<pubDate>Tue, 10 Feb 2026 03:00:00 +0000</pubDate>
</item>
</channel>
</rss>