<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: AddWeb Solution Pvt Ltd</title>
    <description>The latest articles on DEV Community by AddWeb Solution Pvt Ltd (@addwebsolutionpvtltd).</description>
    <link>https://dev.to/addwebsolutionpvtltd</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F11063%2F0b7a4ce4-43ab-4718-abd0-1d314bc88f99.png</url>
      <title>DEV Community: AddWeb Solution Pvt Ltd</title>
      <link>https://dev.to/addwebsolutionpvtltd</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/addwebsolutionpvtltd"/>
    <language>en</language>
    <item>
      <title>Docker Image with Multi-Stage Builds &amp; Docker Images Optimization</title>
      <dc:creator>Rajan Vavadia</dc:creator>
      <pubDate>Wed, 15 Apr 2026 08:24:49 +0000</pubDate>
      <link>https://dev.to/addwebsolutionpvtltd/docker-image-with-multi-stage-builds-docker-images-optimization-4379</link>
      <guid>https://dev.to/addwebsolutionpvtltd/docker-image-with-multi-stage-builds-docker-images-optimization-4379</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;“Docker is not a virtualization technology. It is an application delivery technology.” - Mike Coleman (Docker)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Overview: Multi-Stage Build Strategy (Builder + Production)&lt;/li&gt;
&lt;li&gt;Stage 1: Builder - Compile Extensions, Install Tools &amp;amp; Dependencies&lt;/li&gt;
&lt;li&gt;Stage 2: Production - Minimal Runtime Image&lt;/li&gt;
&lt;li&gt;The .dockerignore File&lt;/li&gt;
&lt;li&gt;Practical Setup (Step-by-step)&lt;/li&gt;
&lt;li&gt;Quotes&lt;/li&gt;
&lt;li&gt;FAQs&lt;/li&gt;
&lt;li&gt;Key Takeaways&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;

&lt;p&gt;This document explains how to build a production-ready PHP 8.4-FPM Docker image using a multi-stage Dockerfile. The image ships with all the PHP extensions, CLI tools (Composer, WP-CLI, Drush, Drupal Console), and Node.js needed for WordPress and Drupal projects - while keeping the final image lean and secure.&lt;/p&gt;

&lt;p&gt;The approach uses a two-stage build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stage 1 (Builder): Compiles PHP extensions, installs development tools, and downloads CLI utilities.&lt;/li&gt;
&lt;li&gt;Stage 2 (Production): Starts from a clean php:8.4-fpm base, copies only the compiled artifacts and runtime libraries, applies security hardening, and runs as a non-root user.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why multi-stage?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A single-stage Dockerfile that compiles extensions and installs build tools leaves behind compilers, header files, and package caches that bloat the image and increase the attack surface. Multi-stage builds solve this by discarding everything that is not needed at runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Overview: Multi-Stage Build Strategy (Builder + Production)
&lt;/h2&gt;

&lt;p&gt;Stage 1 (Builder) starts from php:8.4-fpm and installs: - 20+ PHP extensions using install-php-extensions (handles build dependencies and cleanup internally). - Composer, WP-CLI, Drush, and Drupal Console. - Node.js 20.x for front-end tooling.&lt;/p&gt;

&lt;p&gt;Stage 2 (Production) starts from a fresh php:8.4-fpm and: - Copies compiled extension .so files and their .ini configs from the builder. - Copies CLI binaries (Composer, WP-CLI, Drush, Drupal Console, Node.js). - Installs only the runtime shared libraries needed by the extensions. - Applies OPcache tuning and PHP security settings. - Creates a non-root user (appuser) for PHP-FPM. - Add a health check.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why separate stages?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Smaller image: Build tools (gcc, make, autoconf, header files) are discarded. Only runtime libraries remain.&lt;/li&gt;
&lt;li&gt;Faster pulls and deploys: Less data to transfer across registries and hosts.&lt;/li&gt;
&lt;li&gt;Reduced attack surface: No compilers or dev packages in production.&lt;/li&gt;
&lt;li&gt;Clearer troubleshooting: Build failures are isolated to Stage 1; runtime issues are isolated to Stage 2.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Stage 1: Builder - Compile Extensions, Install Tools &amp;amp; Dependencies
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;3.1 Base Image&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;FROM php:8.4-fpm AS builder&lt;br&gt;
We use the official php:8.4-fpm image (based on Debian 13 Trixie) as the builder base. This gives us the PHP source, phpize, and docker-php-ext-install out of the box.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.2 PHP Extension Installation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Single tool for all PHP extensions (handles build deps + cleanup internally)&lt;br&gt;
COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/bin/&lt;/p&gt;

&lt;p&gt;Install all PHP extensions in one layer&lt;br&gt;
RUN install-php-extensions \&lt;br&gt;
        bcmath \&lt;br&gt;
        bz2 \&lt;br&gt;
        calendar \&lt;br&gt;
        exif \&lt;br&gt;
        gd \&lt;br&gt;
        gmp \&lt;br&gt;
        imagick \&lt;br&gt;
        intl \&lt;br&gt;
        mailparse \&lt;br&gt;
        mongodb \&lt;br&gt;
        mysqli \&lt;br&gt;
        opcache \&lt;br&gt;
        pcntl \&lt;br&gt;
        pdo \&lt;br&gt;
        pdo_mysql \&lt;br&gt;
        pdo_pgsql \&lt;br&gt;
        soap \&lt;br&gt;
        sockets \&lt;br&gt;
        sodium \&lt;br&gt;
        xsl \&lt;br&gt;
        zip&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why install-php-extensions?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It automatically installs the OS-level build dependencies (e.g., libpng-dev, libicu-dev), compiles the extension, and removes the build dependencies - all in one step.&lt;/li&gt;
&lt;li&gt;It replaces the manual apt-get install  &amp;amp;&amp;amp; docker-php-ext-configure &amp;amp;&amp;amp; docker-php-ext-install &amp;amp;&amp;amp; apt-get purge pattern.&lt;/li&gt;
&lt;li&gt;It supports PECL extensions (like imagick, mongodb, mailparse) with the same syntax.&lt;/li&gt;
&lt;li&gt;We use COPY --from=mlocati/php-extension-installer to pull the binary directly from the tool’s official image, without adding another FROM stage.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Extensions included and why:&lt;/strong&gt;&lt;/p&gt;

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

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

&lt;p&gt;&lt;strong&gt;3.3 CLI Tools Installation&lt;/strong&gt;&lt;br&gt;
Install Composer, WP-CLI, Drush, and Drupal Console in one layer&lt;br&gt;
RUN apt-get update &amp;amp;&amp;amp; apt-get install -y --no-install-recommends \&lt;br&gt;
        git \&lt;br&gt;
        unzip \&lt;br&gt;
    &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/* \&lt;br&gt;
      Composer&lt;br&gt;
    &amp;amp;&amp;amp; curl -sSL &lt;a href="https://getcomposer.org/installer" rel="noopener noreferrer"&gt;https://getcomposer.org/installer&lt;/a&gt; | php -- --install-dir=/usr/local/bin --filename=composer \&lt;br&gt;
      WP-CLI&lt;br&gt;
    &amp;amp;&amp;amp; curl -sSL &lt;a href="https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar" rel="noopener noreferrer"&gt;https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar&lt;/a&gt; -o /usr/local/bin/wp \&lt;br&gt;
    &amp;amp;&amp;amp; chmod +x /usr/local/bin/wp \&lt;br&gt;
      Drupal Console&lt;br&gt;
    &amp;amp;&amp;amp; curl -sSL &lt;a href="https://drupalconsole.com/installer" rel="noopener noreferrer"&gt;https://drupalconsole.com/installer&lt;/a&gt; -o /usr/local/bin/drupal \&lt;br&gt;
    &amp;amp;&amp;amp; chmod +x /usr/local/bin/drupal&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Composer: PHP dependency manager. Installed globally at /usr/local/bin/composer.&lt;/li&gt;
&lt;li&gt;WP-CLI: WordPress command-line interface for managing WordPress installations.&lt;/li&gt;
&lt;li&gt;Drupal Console: CLI tool for generating boilerplate code and interacting with Drupal.&lt;/li&gt;
&lt;li&gt;git and unzip are needed by Composer to fetch packages; they are installed here but not carried over to the production stage.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3.4 Drush Installation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ENV COMPOSER_HOME=/usr/local/share/composer&lt;br&gt;
RUN composer global require drush/drush:8.* --no-interaction --prefer-dist&lt;br&gt;
Drush (Drupal Shell) is installed globally via Composer. COMPOSER_HOME is set explicitly so the entire vendor directory can be copied to the production stage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.5 Node.js Installation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;RUN curl -fsSL &lt;a href="https://deb.nodesource.com/setup_20.x" rel="noopener noreferrer"&gt;https://deb.nodesource.com/setup_20.x&lt;/a&gt; | bash - \&lt;br&gt;
    &amp;amp;&amp;amp; apt-get install -y --no-install-recommends nodejs \&lt;br&gt;
    &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/*&lt;br&gt;
Node.js 20.x (LTS) is installed for front-end build tools (npm scripts, asset compilation). NVM is intentionally avoided - a single global Node.js installation is simpler and more predictable in a container.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Build once, run anywhere - but make sure what you build is only what you need.” - Container best practice principle&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  4. Stage 2: Production - Minimal Runtime Image
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;4.1 Fresh Base and Labels&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;FROM php:8.4-fpm AS production&lt;/p&gt;

&lt;p&gt;LABEL maintainer="AddWeb Solutions" \&lt;br&gt;
      description="PHP 8.4-FPM production image with WordPress/Drupal tooling"&lt;br&gt;
A fresh php:8.4-fpm base - none of the builder’s build tools, caches, or temp files exist here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4.2 Copying Artifacts from Builder&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Copy PHP extensions and their configs from builder&lt;br&gt;
COPY --from=builder /usr/local/lib/php/extensions/ /usr/local/lib/php/extensions/&lt;br&gt;
COPY --from=builder /usr/local/etc/php/conf.d/ /usr/local/etc/php/conf.d/&lt;/p&gt;

&lt;p&gt;Copy CLI tools from builder&lt;br&gt;
COPY --from=builder /usr/local/bin/composer /usr/local/bin/composer&lt;br&gt;
COPY --from=builder /usr/local/bin/wp /usr/local/bin/wp&lt;br&gt;
COPY --from=builder /usr/local/bin/drupal /usr/local/bin/drupal&lt;br&gt;
COPY --from=builder /usr/local/share/composer /usr/local/share/composer&lt;/p&gt;

&lt;p&gt;Copy Node.js from builder&lt;br&gt;
COPY --from=builder /usr/bin/node /usr/bin/node&lt;br&gt;
COPY --from=builder /usr/bin/npm /usr/bin/npm&lt;br&gt;
COPY --from=builder /usr/bin/npx /usr/bin/npx&lt;br&gt;
COPY --from=builder /usr/lib/node_modules /usr/lib/node_modules&lt;br&gt;
COPY --from=builder selectively pulls only the compiled .so files, .ini configs, and CLI binaries. Everything else (compilers, header files, build caches) is left behind.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4.3 Runtime Libraries&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;RUN apt-get update &amp;amp;&amp;amp; apt-get install -y --no-install-recommends \&lt;br&gt;
        # Runtime libs required by PHP extensions&lt;br&gt;
        libbz2-1.0 \&lt;br&gt;
        libfreetype6 \&lt;br&gt;
        libgmp10 \&lt;br&gt;
        libicu76 \&lt;br&gt;
        libjpeg62-turbo \&lt;br&gt;
        libmagickwand-7.q16-10 \&lt;br&gt;
        libavif16 \&lt;br&gt;
        libpng16-16 \&lt;br&gt;
        libpq5 \&lt;br&gt;
        libxslt1.1 \&lt;br&gt;
        libzip5 \&lt;br&gt;
        # Essential runtime utilities only&lt;br&gt;
        cron \&lt;br&gt;
        curl \&lt;br&gt;
        default-mysql-client \&lt;br&gt;
        ffmpeg \&lt;br&gt;
        sendmail \&lt;br&gt;
        supervisor \&lt;br&gt;
        unzip \&lt;br&gt;
        sqlite3 \&lt;br&gt;
    &amp;amp;&amp;amp; apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \&lt;br&gt;
    &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key points:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Runtime libs only: We install the shared libraries (.so files) that the compiled PHP extensions link against. These are the -dev-less counterparts (e.g., libpng16-16 not libpng-dev).&lt;/li&gt;
&lt;li&gt;Debian Trixie package names: Since php:8.4-fpm is based on Debian 13 (Trixie), some package names differ from Bookworm (e.g., libicu76 instead of libicu72, libmagickwand-7.q16-10 instead of libmagickwand-6.q16-6, libzip5 instead of libzip4).&lt;/li&gt;
&lt;li&gt;Essential utilities: cron (scheduled tasks), curl (health checks, API calls), default-mysql-client (database operations), ffmpeg (media processing), sendmail (email delivery), supervisor (process management), unzip, sqlite3.&lt;/li&gt;
&lt;li&gt;Cleanup: apt-get purge --auto-remove removes packages that were pulled in only as install dependencies, and rm -rf /var/lib/apt/lists/* clears the package cache.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Runtime libraries mapped to extensions:&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;4.4 Drush Symlink&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ENV COMPOSER_HOME=/usr/local/share/composer&lt;br&gt;
RUN ln -s /usr/local/share/composer/vendor/bin/drush /usr/local/bin/drush&lt;br&gt;
Makes drush available in $PATH without modifying the PATH variable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4.5 OPcache Tuning&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;RUN { \&lt;br&gt;
        echo 'opcache.memory_consumption=128'; \&lt;br&gt;
        echo 'opcache.interned_strings_buffer=8'; \&lt;br&gt;
        echo 'opcache.max_accelerated_files=4000'; \&lt;br&gt;
        echo 'opcache.revalidate_freq=60'; \&lt;br&gt;
        echo 'opcache.fast_shutdown=1'; \&lt;br&gt;
        echo 'opcache.enable_cli=1'; \&lt;br&gt;
        echo 'opcache.validate_timestamps=0'; \&lt;br&gt;
    } &amp;gt; /usr/local/etc/php/conf.d/opcache-recommended.ini&lt;/p&gt;

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

&lt;p&gt;validate_timestamps=0 is a production optimization - PHP never starts the filesystem to check if files have changed. When you deploy new code, restart PHP-FPM to pick up the changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4.6 Security Hardening&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;RUN { \&lt;br&gt;
        echo 'expose_php=Off'; \&lt;br&gt;
        echo 'display_errors=Off'; \&lt;br&gt;
        echo 'log_errors=On'; \&lt;br&gt;
        echo 'error_log=/dev/stderr'; \&lt;br&gt;
        echo 'allow_url_fopen=Off'; \&lt;br&gt;
    } &amp;gt; /usr/local/etc/php/conf.d/security.ini&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;4.7 Non-Root User&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;RUN groupadd -g 1000 appuser &amp;amp;&amp;amp; useradd -u 1000 -g appuser -m appuser&lt;/p&gt;

&lt;p&gt;WORKDIR /var/www/html&lt;br&gt;
RUN chown -R appuser:appuser /var/www/html&lt;br&gt;
Running PHP-FPM as a non-root user reduces the blast radius if the application is compromised. The appuser (UID 1000) owns the web root.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4.8 Health Check&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;HEALTHCHECK --interval=30s --timeout=5s --retries=3 \&lt;br&gt;
    CMD php-fpm-healthcheck || kill -0 $(cat /var/run/php-fpm.pid 2&amp;gt;/dev/null) || exit 1&lt;br&gt;
Docker checks every 30 seconds whether PHP-FPM is responding. After 3 consecutive failures, the container is marked unhealthy, and orchestrators (Docker Compose, Swarm, Kubernetes) can restart it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4.9  Expose and CMD&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;EXPOSE 9000&lt;/p&gt;

&lt;p&gt;CMD ["php-fpm"]&lt;br&gt;
PHP-FPM listens on port 9000 (FastCGI). A reverse proxy (Nginx, Apache, Caddy) forwards requests to this port.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The .dockerignore File
&lt;/h2&gt;

&lt;p&gt;.git&lt;br&gt;
.github&lt;br&gt;
.gitignore&lt;br&gt;
&lt;em&gt;.md&lt;br&gt;
LICENSE&lt;br&gt;
docker-compose&lt;/em&gt;.yml&lt;br&gt;
.env*&lt;br&gt;
.vscode&lt;br&gt;
.idea&lt;br&gt;
node_modules&lt;br&gt;
vendor&lt;br&gt;
.docker&lt;/p&gt;

&lt;p&gt;The .dockerignore prevents unnecessary files from being sent to the Docker build context. This speeds up builds and avoids leaking secrets (.env files) or bloating the image with node_modules or vendor directories.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Practical Setup (Step-by-step)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;6.1 Build the Image&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;docker build -t php8.4-multistage --target production .&lt;br&gt;
The --target production flag tells Docker to stop at the production stage and discard the builder. If --target is omitted, Docker builds up to the last stage (which is already in production in this Dockerfile).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6.2 Run the Container&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;docker run -d \&lt;br&gt;
    --name php-app \&lt;br&gt;
    -p 9000:9000 \&lt;br&gt;
    -v ./src:/var/www/html \&lt;br&gt;
    php8.4-multistage&lt;br&gt;
Mount your application source code at /var/www/html. PHP-FPM will serve it on port 9000.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6.3 Pair with Nginx (docker-compose example)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;version: '3.8'&lt;/p&gt;

&lt;p&gt;services:&lt;br&gt;
  php:&lt;br&gt;
    build:&lt;br&gt;
      context: .&lt;br&gt;
      target: production&lt;br&gt;
    volumes:&lt;br&gt;
      - ./src:/var/www/html&lt;br&gt;
    networks:&lt;br&gt;
      - app-network&lt;/p&gt;

&lt;p&gt;nginx:&lt;br&gt;
    image: nginx:alpine&lt;br&gt;
    ports:&lt;br&gt;
      - "80:80"&lt;br&gt;
    volumes:&lt;br&gt;
      - ./src:/var/www/html&lt;br&gt;
      - ./nginx.conf:/etc/nginx/conf.d/default.conf&lt;br&gt;
    depends_on:&lt;br&gt;
      - php&lt;br&gt;
    networks:&lt;br&gt;
      - app-network&lt;/p&gt;

&lt;p&gt;networks:&lt;br&gt;
  app-network:&lt;br&gt;
    driver: bridge&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6.4 Verify Extensions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;docker exec php-app php -m&lt;br&gt;
This lists all loaded PHP modules. Confirm that gd, imagick, intl, opcache, pdo_mysql, etc., are all present.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6.5 Verify CLI Tools&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;docker exec php-app composer --version&lt;br&gt;
docker exec php-app wp --version&lt;br&gt;
docker exec php-app drush --version&lt;br&gt;
docker exec php-app node --version&lt;br&gt;
docker exec php-app npm --version&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6.6 Check phpinfo&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create a phpinfo file&lt;br&gt;
docker exec php-app bash -c "echo '&amp;lt;?php phpinfo();' &amp;gt; /var/www/html/info.php"&lt;/p&gt;

&lt;p&gt;Quick test with built-in server&lt;br&gt;
docker exec -d php-app php -S 0.0.0.0:8085 -t /var/www/html&lt;br&gt;
Open &lt;a href="http://localhost:8085/info.php" rel="noopener noreferrer"&gt;http://localhost:8085/info.php&lt;/a&gt; in your browser to see the full PHP configuration. Remove info.php after testing - never leave it exposed in production.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;6.7 Common Improvements (optional)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use BuildKit cache mounts to speed up repeated builds: RUN --mount=type=cache,target=/var/cache/apt apt-get update &amp;amp;&amp;amp; ...&lt;/li&gt;
&lt;li&gt;Pin the PHP version (e.g., php:8.4.18-fpm) for reproducible builds instead of using the floating 8.4-fpm tag.&lt;/li&gt;
&lt;li&gt;Add a .env-based PHP config layer for settings that vary between environments (memory_limit, upload_max_filesize).&lt;/li&gt;
&lt;li&gt;Use Docker Compose profiles to add Xdebug only in development (avoid Xdebug in production).&lt;/li&gt;
&lt;li&gt;Scan the image with Trivy or Docker Scout for vulnerability reporting before pushing to a registry.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;“The art of simplicity is a puzzle of complexity.” - Douglas Horton&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  8. FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q1. Why use multi-stage builds instead of a single Dockerfile?&lt;/strong&gt; &lt;br&gt;
A. A single-stage build carries compilers, dev headers, and package caches into the final image. Multi-stage builds discard all of that, resulting in a smaller, more secure image.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q2. Why install-php-extensions instead of docker-php-ext-install?&lt;/strong&gt; A. install-php-extensions handles OS dependency installation, extension compilation, and cleanup automatically. It also supports PECL extensions (imagick, mongodb, mailparse) with the same syntax, eliminating the need for separate pecl install commands.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q3. Why is the base image Debian Trixie? Can I use Bookworm or Alpine?&lt;/strong&gt; &lt;br&gt;
A. The official php:8.4-fpm tag is built on Debian 13 (Trixie) as of 2025. If you need Bookworm, use php:8.4-fpm-bookworm. Alpine (php:8.4-fpm-alpine) is smaller but uses musl libc, which can cause compatibility issues with some extensions (notably ImageMagick).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q4. Why are the runtime library package names different from older guides?&lt;/strong&gt; &lt;br&gt;
A. Debian Trixie renamed several packages as part of the 64-bit time_t transition and library version bumps. For example: libicu72 became libicu76, libmagickwand-6.q16-6 became libmagickwand-7.q16-10, and libzip4 became libzip5. Always verify package names against the base image’s Debian version.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q5. How do I find the correct runtime library names if I change the base image?&lt;/strong&gt; &lt;br&gt;
A. Run a temporary container and use ldd to check for missing libraries:&lt;br&gt;
docker run --rm your-image bash -c \&lt;br&gt;
  "for so in /usr/local/lib/php/extensions/&lt;em&gt;/&lt;/em&gt;.so; do \&lt;br&gt;
     ldd \$so 2&amp;gt;/dev/null | grep 'not found'; \&lt;br&gt;
   done"&lt;br&gt;
Then search for the correct package with apt-cache search .&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q6. What does validate_timestamps=0 mean for deployments?&lt;/strong&gt; &lt;br&gt;
A. OPcache will never check if PHP files on disk have changed. This avoids filesystem stat calls on every request (faster). The tradeoff: after deploying new code, you must restart PHP-FPM (docker restart ) to pick up changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q7. Why create a non-root user?&lt;/strong&gt; &lt;br&gt;
A. Running as root inside a container means a compromised application has full control over the container filesystem. A non-root user limits the damage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q8. Can I add Xdebug for local development?&lt;/strong&gt; &lt;br&gt;
A. Yes, but do not include it in the production image. Use a separate development stage or a Docker Compose override file that installs Xdebug on top of the production image.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Multi-stage builds separate build-time complexity from runtime simplicity.&lt;/li&gt;
&lt;li&gt;install-php-extensions eliminates the manual dance of installing dev packages, compiling, and cleaning up.&lt;/li&gt;
&lt;li&gt;Always match runtime library package names to the base image’s Debian version (Trixie uses libicu76, libzip5, libmagickwand-7.q16-10, libavif16).&lt;/li&gt;
&lt;li&gt;OPcache with validate_timestamps=0 is a significant performance win for production.&lt;/li&gt;
&lt;li&gt;Security hardening (expose_php=Off, display_errors=Off, allow_url_fopen=Off) should be the default, not an afterthought.&lt;/li&gt;
&lt;li&gt;Non-root user + health check + .dockerignore round out a production-ready setup.&lt;/li&gt;
&lt;li&gt;Use ldd to diagnose missing shared libraries when extensions fail to load.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  10. Conclusion
&lt;/h2&gt;

&lt;p&gt;This multi-stage Dockerfile provides a clean, repeatable way to build a PHP 8.4-FPM production image with everything needed for WordPress and Drupal projects. The builder stage handles all the heavy lifting - compiling extensions, installing Composer, WP-CLI, Drush, Drupal Console, and Node.js - while the production stage starts fresh and copies only what is needed at runtime. Combined with OPcache tuning, security hardening, a non-root user, and a health check, the result is an image that is smaller, faster, and more secure than a traditional single-stage build. When paired with Nginx via Docker Compose, it forms a solid foundation for deploying PHP applications in any environment.&lt;/p&gt;

&lt;p&gt;About the Author:&lt;em&gt;Rajan is a DevOps Engineer at &lt;a href="https://www.addwebsolution.com/" rel="noopener noreferrer"&gt;AddWebSolution&lt;/a&gt;, specializing in automation infrastructure, Optimize the CI/CD Pipelines and ensuring seamless deployments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>dockerfile</category>
      <category>dockerignore</category>
      <category>multistagebuilds</category>
    </item>
    <item>
      <title>Architecting Large-Scale Next.js Applications (Folder Structure, Patterns, Best Practices)</title>
      <dc:creator>Mayank Goyal</dc:creator>
      <pubDate>Mon, 13 Apr 2026 10:25:46 +0000</pubDate>
      <link>https://dev.to/addwebsolutionpvtltd/architecting-large-scale-nextjs-applications-folder-structure-patterns-best-practices-2dpj</link>
      <guid>https://dev.to/addwebsolutionpvtltd/architecting-large-scale-nextjs-applications-folder-structure-patterns-best-practices-2dpj</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;“Good architecture makes the system easy to understand; great architecture makes it hard to break.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Feature-based architecture is critical&lt;/li&gt;
&lt;li&gt;Separate UI, logic, and data layers&lt;/li&gt;
&lt;li&gt;Prefer server components for performance&lt;/li&gt;
&lt;li&gt;Centralize API logic&lt;/li&gt;
&lt;li&gt;Use scalable state management&lt;/li&gt;
&lt;li&gt;Optimize early, not later&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Index
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Why Architecture Matters&lt;/li&gt;
&lt;li&gt;Core Principles of Scalable Architecture&lt;/li&gt;
&lt;li&gt;Advanced Folder Structure (Enterprise-Level)&lt;/li&gt;
&lt;li&gt;Architectural Patterns (Deep Dive)&lt;/li&gt;
&lt;li&gt;State Management at Scale&lt;/li&gt;
&lt;li&gt;Data Fetching &amp;amp; API Layer Design&lt;/li&gt;
&lt;li&gt;Authentication &amp;amp; Authorization Architecture&lt;/li&gt;
&lt;li&gt;Performance Optimization (Advanced)&lt;/li&gt;
&lt;li&gt;Error Handling &amp;amp; Logging&lt;/li&gt;
&lt;li&gt;Testing Strategy (Production-Ready)&lt;/li&gt;
&lt;li&gt;Dev Experience &amp;amp; Code Quality&lt;/li&gt;
&lt;li&gt;Deployment &amp;amp; Infrastructure Strategy&lt;/li&gt;
&lt;li&gt;Real-World Example (Enterprise Dashboard)&lt;/li&gt;
&lt;li&gt;Interesting Facts&lt;/li&gt;
&lt;li&gt;Stats&lt;/li&gt;
&lt;li&gt;FAQ’s&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Building small Next.js apps is easy. Scaling them to support millions of users, multiple developers, and complex business logic is not.&lt;br&gt;
Large-scale applications require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clean architecture&lt;/li&gt;
&lt;li&gt;Predictable structure&lt;/li&gt;
&lt;li&gt;Separation of concerns&lt;/li&gt;
&lt;li&gt;Strong conventions
Next.js (especially App Router) provides powerful primitives, but it does NOT enforce architecture, that’s your responsibility.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This guide gives you a production-grade blueprint.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Architecture Matters
&lt;/h2&gt;

&lt;p&gt;At scale, poor architecture leads to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tight coupling between components&lt;/li&gt;
&lt;li&gt;Duplicate logic across features&lt;/li&gt;
&lt;li&gt;Slow builds &amp;amp; performance bottlenecks&lt;/li&gt;
&lt;li&gt;Difficult onboarding for new developers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Good architecture enables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Independent feature development&lt;/li&gt;
&lt;li&gt;Faster debugging&lt;/li&gt;
&lt;li&gt;Better scalability&lt;/li&gt;
&lt;li&gt;Easier refactoring&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Core Principles of Scalable Architecture
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Separation of Concerns&lt;/strong&gt;&lt;br&gt;
Divide responsibilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UI → components&lt;/li&gt;
&lt;li&gt;Logic → hooks/services&lt;/li&gt;
&lt;li&gt;Data → API layer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Feature Isolation&lt;/strong&gt;&lt;br&gt;
Each feature should be self-contained.&lt;br&gt;
Think like mini-apps inside your app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Single Responsibility Principle&lt;/strong&gt;&lt;br&gt;
Each file/module should do one thing well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Dependency Direction&lt;/strong&gt;&lt;br&gt;
Components depend on hooks&lt;br&gt;
Hooks depend on services&lt;br&gt;
Services depend on APIs&lt;br&gt;
NOT the other way around.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Scalability First Mindset&lt;/strong&gt;&lt;br&gt;
Design for scale even if you’re small today.&lt;/p&gt;
&lt;h2&gt;
  
  
  Advanced Folder Structure (Enterprise-Level)
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
│
├── app/                         # Next.js App Router
│   ├── (public)/
│   ├── (auth)/
│   ├── dashboard/
│   │   ├── layout.tsx
│   │   ├── page.tsx
│
├── features/                    # Feature modules (CORE)
│   ├── auth/
│   │   ├── components/
│   │   ├── hooks/
│   │   ├── services/
│   │   ├── api/
│   │   ├── store/
│   │   └── types.ts
│   │
│   ├── products/
│   ├── orders/
│   └── users/
│
├── shared/                      # Cross-feature reusable code
│   ├── components/
│   ├── hooks/
│   ├── utils/
│   └── constants/
│
├── core/                        # App-level logic
│   ├── config/
│   ├── providers/
│   ├── middleware/
│   └── guards/
│
├── services/                    # Global services (rare)
├── lib/                         # Low-level utilities
├── types/                       # Global types
├── styles/
└── tests/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;“Fetching data is easy. Fetching it efficiently is architecture.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Key Insight&lt;/strong&gt;&lt;br&gt;
Avoid “global chaos” folders like components/ for everything&lt;br&gt;
Prefer feature-based grouping&lt;/p&gt;
&lt;h2&gt;
  
  
  Architectural Patterns (Deep Dive)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Feature-Based Architecture (MOST IMPORTANT)&lt;/strong&gt;&lt;br&gt;
Each feature owns:&lt;br&gt;
UI&lt;br&gt;
logic&lt;br&gt;
API calls&lt;br&gt;
state&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;features/products/
    components/
    hooks/
    services/
    store/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Layered Architecture&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UI Layer (Components)
↓
Hooks Layer (Business Logic)
↓
Service Layer (API Calls)
↓
API Layer (External systems)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Server vs Client Component Strategy&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Server Component&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getProducts&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ProductsClient&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Smart vs Dumb Components&lt;/strong&gt;&lt;br&gt;
Smart → fetch + logic&lt;br&gt;
Dumb → UI only&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Composition Pattern&lt;/strong&gt;&lt;br&gt;
Avoid inheritance. Use composition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProductInfo&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  State Management at Scale
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;When NOT to use global state&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Static data&lt;/li&gt;
&lt;li&gt;Server-fetched data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Recommended Strategy&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Example (Zustand Advanced)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;create&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zustand&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useCartStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="na"&gt;addItem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;})),&lt;/span&gt;
  &lt;span class="na"&gt;clearCart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Data Fetching &amp;amp; API Layer Design
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Bad Practice&lt;/strong&gt;&lt;br&gt;
Calling fetch directly in components everywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good Practice&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;features&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
  &lt;span class="nx"&gt;services&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;productService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getProducts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/products&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;“Every unnecessary render is a tax on your use.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Caching Strategy&lt;/strong&gt;&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Authentication &amp;amp; Authorization Architecture
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Recommended Setup&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Middleware for route protection&lt;/li&gt;
&lt;li&gt;Server-side session validation&lt;/li&gt;
&lt;li&gt;Role-based access
Example:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// middleware.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Performance Optimization (Advanced)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Techniques&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code splitting (dynamic imports)&lt;/li&gt;
&lt;li&gt;Partial hydration&lt;/li&gt;
&lt;li&gt;Edge rendering&lt;/li&gt;
&lt;li&gt;Image optimization&lt;/li&gt;
&lt;li&gt;Memoization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Chart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./Chart&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;ssr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Error Handling &amp;amp; Logging
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Centralized Error Handling&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Logging Tools&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sentry&lt;/li&gt;
&lt;li&gt;LogRocket&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Testing Strategy (Production-Ready)
&lt;/h2&gt;

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

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;adds item to cart&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useCartStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addItem&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Dev Experience &amp;amp; Code Quality
&lt;/h2&gt;

&lt;p&gt;ESLint + Prettier&lt;br&gt;
Husky (pre-commit hooks)&lt;br&gt;
Strict TypeScript&lt;br&gt;
Absolute imports (@/features/...)&lt;/p&gt;
&lt;h2&gt;
  
  
  Deployment &amp;amp; Infrastructure Strategy
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Recommended Stack&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hosting → Vercel&lt;/li&gt;
&lt;li&gt;DB → PostgreSQL&lt;/li&gt;
&lt;li&gt;CDN → Cloudflare&lt;/li&gt;
&lt;li&gt;Monitoring → Sentry&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Scaling Tips&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use Edge Functions&lt;/li&gt;
&lt;li&gt;Optimize bundle size&lt;/li&gt;
&lt;li&gt;Enable caching&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Real-World Example (Enterprise Dashboard)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Structure&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;features/dashboard/
   components/
      StatsCard.tsx
   hooks/
      useStats.ts
   services/
      dashboardService.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Service&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchStats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/stats&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Hook&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useStats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setStats&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;fetchStats&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;setStats&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Component&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;StatsCard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalUsers&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Page&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;DashboardPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useStats&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Loading...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;StatsCard&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;stats&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Interesting Facts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Next.js App Router defaults to Server Components.Source: &lt;a href="https://nextjs.org/docs/app/building-your-application/rendering/server-components" rel="noopener noreferrer"&gt;https://nextjs.org/docs/app/building-your-application/rendering/server-components&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Middleware runs at the Edge.Source: &lt;a href="https://nextjs.org/docs/pages/api-reference/file-conventions/middleware" rel="noopener noreferrer"&gt;https://nextjs.org/docs/pages/api-reference/file-conventions/middleware&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;File-based routing reduces ~40% boilerplate.Source: &lt;a href="https://nextjs.org/docs/app/building-your-application/routing" rel="noopener noreferrer"&gt;https://nextjs.org/docs/app/building-your-application/routing&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Built-in optimizations replace many libraries.Source: &lt;a href="https://nextjs.org/docs/architecture" rel="noopener noreferrer"&gt;https://nextjs.org/docs/architecture&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;ISR allows hybrid static + dynamic pages.Source: &lt;a href="https://nextjs.org/docs/pages/building-your-application/rendering/incremental-static-regeneration" rel="noopener noreferrer"&gt;https://nextjs.org/docs/pages/building-your-application/rendering/incremental-static-regeneration&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Stats
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Next.js is one of the fastest-growing React frameworks and is widely adopted for production-grade applications.
Source: &lt;a href="https://nextjs.org/showcase" rel="noopener noreferrer"&gt;https://nextjs.org/showcase&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Next.js has 120,000+ stars on GitHub, reflecting its strong developer adoption and community support.
Source: &lt;a href="https://github.com/vercel/next.js" rel="noopener noreferrer"&gt;https://github.com/vercel/next.js&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;According to the State of JS survey, Next.js consistently ranks among the top frameworks in developer satisfaction and usage.
Source: &lt;a href="https://stateofjs.com/" rel="noopener noreferrer"&gt;https://stateofjs.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Vercel, the company behind Next.js, serves billions of requests per week across applications deployed on its platform.
Source: &lt;a href="https://vercel.com/customers" rel="noopener noreferrer"&gt;https://vercel.com/customers&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Next.js enables hybrid rendering (SSR, SSG, ISR), which improves performance and scalability for modern web applications.
Source: &lt;a href="https://nextjs.org/docs/pages/building-your-application/rendering" rel="noopener noreferrer"&gt;https://nextjs.org/docs/pages/building-your-application/rendering&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;File-based routing in Next.js simplifies development by automatically mapping files to routes, reducing manual configuration.
Source: &lt;a href="https://nextjs.org/docs/app/building-your-application/routing" rel="noopener noreferrer"&gt;https://nextjs.org/docs/app/building-your-application/routing&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ’s
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q1. Is Redux necessary?&lt;/strong&gt;&lt;br&gt;
No. Use Zustand unless you need complex workflows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q2. How to organize large teams?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Feature ownership&lt;/li&gt;
&lt;li&gt;Code reviews&lt;/li&gt;
&lt;li&gt;Clear folder structure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Q3. Should I use monorepo?&lt;/strong&gt;&lt;br&gt;
Yes, for multi-app systems (Nx / Turborepo).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q4. Where to keep reusable components?&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;shared/components&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q5. What is the biggest mistake?&lt;/strong&gt;&lt;br&gt;
Mixing everything in global folders.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Scaling a Next.js application is more about architecture than code. The difference between a messy app and a scalable system lies in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Structure&lt;/li&gt;
&lt;li&gt;Discipline&lt;/li&gt;
&lt;li&gt;Consistency
By following feature-based design, layered architecture, and modern Next.js patterns, you can build applications that scale effortlessly with both users and teams.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;About the Author:&lt;em&gt;Mayank is a web developer at &lt;a href="https://www.addwebsolution.com/" rel="noopener noreferrer"&gt;AddWebSolution&lt;/a&gt;, building scalable apps with PHP, Node.js &amp;amp; React. Sharing ideas, code, and creativity.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>frontend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>ELK Stack Setup for Centralized Log Management &amp; Monitoring</title>
      <dc:creator>Rajan Vavadia</dc:creator>
      <pubDate>Wed, 08 Apr 2026 11:42:01 +0000</pubDate>
      <link>https://dev.to/addwebsolutionpvtltd/elk-stack-setup-for-centralized-log-management-monitoring-11l0</link>
      <guid>https://dev.to/addwebsolutionpvtltd/elk-stack-setup-for-centralized-log-management-monitoring-11l0</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;“The goal is to turn data into information, and information into insight.” - Carly Fiorina&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Stage 1: Elasticsearch - The Search &amp;amp; Storage Engine&lt;/li&gt;
&lt;li&gt;Stage 2: Logstash - The Data Processing Pipeline&lt;/li&gt;
&lt;li&gt;Stage 3: Kibana - The Visualization Layer&lt;/li&gt;
&lt;li&gt;Stage 4: Filebeat - The Lightweight Log Shipper&lt;/li&gt;
&lt;li&gt;Connecting the Pieces - End-to-End Data Flow&lt;/li&gt;
&lt;li&gt;Practical Setup (Step-by-step)&lt;/li&gt;
&lt;li&gt;Troubleshooting Common Issues&lt;/li&gt;
&lt;li&gt;Quotes&lt;/li&gt;
&lt;li&gt;FAQs&lt;/li&gt;
&lt;li&gt;Key Takeaways&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;

&lt;p&gt;When your application runs on a single server, tailing a log file is enough. When it runs across multiple servers, containers, or microservices - you need centralized logging. Scattered logs across dozens of servers make debugging slow, error correlation impossible, and incident response reactive instead of proactive.&lt;/p&gt;

&lt;p&gt;This guide walks through setting up a production-ready ELK stack (Elasticsearch, Logstash, Kibana) with Filebeat for centralized log collection, processing, and visualization. The setup covers a real-world scenario: a Java Spring Boot application running on one server, with the ELK stack on a separate server.&lt;/p&gt;

&lt;p&gt;The approach uses a &lt;strong&gt;four-component architecture:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Filebeat (Application Server): Lightweight agent that tails log files and ships them to Logstash.&lt;/li&gt;
&lt;li&gt;Logstash (ELK Server): Receives raw logs, parses and transforms them, and forwards structured data to Elasticsearch.&lt;/li&gt;
&lt;li&gt;Elasticsearch (ELK Server): Stores, indexes, and makes logs searchable in near real-time.&lt;/li&gt;
&lt;li&gt;Kibana (ELK Server): Web UI for searching, visualizing, and building dashboards from log data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why centralized logging?&lt;/strong&gt;&lt;br&gt;
Manually SSH-ing into each server and grepping through log files does not scale. Centralized logging solves this by aggregating all logs into a single searchable location. &lt;br&gt;
You get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single pane of glass for all application and infrastructure logs.&lt;/li&gt;
&lt;li&gt;Real-time search across millions of log entries in milliseconds.&lt;/li&gt;
&lt;li&gt;Correlation of events across services and servers by timestamp.&lt;/li&gt;
&lt;li&gt;Alerting on error patterns before users report issues.&lt;/li&gt;
&lt;li&gt;Retention and compliance with configurable index lifecycle policies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Component Responsibilities&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Why separate servers?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- Resource isolation:&lt;/strong&gt; Elasticsearch is memory-hungry. Running it on the application server competes with your app for RAM and CPU.&lt;br&gt;
&lt;strong&gt;- Independent scaling:&lt;/strong&gt; You can scale the ELK server (more RAM, bigger disk) without touching production application servers.&lt;br&gt;
&lt;strong&gt;- Security boundary:&lt;/strong&gt; The ELK server can sit in a private subnet, accessible only to internal services and authorized users.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Stage 1: Elasticsearch - The Search &amp;amp; Storage Engine
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;2.1 What is Elasticsearch?&lt;/strong&gt;&lt;br&gt;
Elasticsearch is a distributed search and analytics engine built on Apache Lucene. In the ELK stack, it serves as the storage and search backend - every log line that Logstash processes ends up as a document in an Elasticsearch index.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.2 Installation&lt;/strong&gt;&lt;br&gt;
Import the Elasticsearch GPG key&lt;br&gt;
wget -qO - &lt;a href="https://artifacts.elastic.co/GPG-KEY-elasticsearch" rel="noopener noreferrer"&gt;https://artifacts.elastic.co/GPG-KEY-elasticsearch&lt;/a&gt; | sudo gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg&lt;/p&gt;

&lt;p&gt;Add the APT repository&lt;br&gt;
echo "deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg] &lt;a href="https://artifacts.elastic.co/packages/8.x/apt" rel="noopener noreferrer"&gt;https://artifacts.elastic.co/packages/8.x/apt&lt;/a&gt; stable main" | sudo tee /etc/apt/sources.list.d/elastic-8.x.list&lt;/p&gt;

&lt;p&gt;Install Elasticsearch&lt;br&gt;
sudo apt-get update &amp;amp;&amp;amp; sudo apt-get install -y elasticsearch&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.3 Configuration&lt;/strong&gt;&lt;br&gt;
The main configuration file is /etc/elasticsearch/elasticsearch.yml:&lt;br&gt;
Cluster and node identity&lt;br&gt;
cluster.name: my-cluster&lt;br&gt;
node.name: node-1&lt;/p&gt;

&lt;p&gt;Data and log paths&lt;br&gt;
path.data: /var/lib/elasticsearch&lt;br&gt;
path.logs: /var/log/elasticsearch&lt;/p&gt;

&lt;p&gt;Network - bind to all interfaces for external access&lt;br&gt;
network.host: 0.0.0.0&lt;br&gt;
http.host: 0.0.0.0&lt;/p&gt;

&lt;p&gt;Discovery - single node (no cluster formation)&lt;br&gt;
discovery.type: single-node&lt;/p&gt;

&lt;p&gt;Security - disable for internal/dev setups&lt;br&gt;
xpack.security.enabled: false&lt;br&gt;
xpack.security.enrollment.enabled: true&lt;br&gt;
xpack.security.http.ssl:&lt;br&gt;
  enabled: false&lt;br&gt;
xpack.security.transport.ssl:&lt;br&gt;
  enabled: false&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configuration settings explained:&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Security note:&lt;/strong&gt; xpack.security.enabled: false is acceptable for internal/development setups behind a firewall. For production environments exposed to the internet, always enable security with TLS certificates and user authentication.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.4 Start and Enable&lt;/strong&gt;&lt;br&gt;
sudo systemctl daemon-reload&lt;br&gt;
sudo systemctl enable elasticsearch&lt;br&gt;
sudo systemctl start elasticsearch&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.5 Verify&lt;/strong&gt;&lt;br&gt;
curl &lt;a href="http://localhost:9200" rel="noopener noreferrer"&gt;http://localhost:9200&lt;/a&gt;&lt;br&gt;
Expected response:&lt;br&gt;
{&lt;br&gt;
  "name": "node-1",&lt;br&gt;
  "cluster_name": "my-cluster",&lt;br&gt;
  "version": {&lt;br&gt;
    "number": "8.x.x"&lt;br&gt;
  },&lt;br&gt;
  "tagline": "You Know, for Search"&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.6 Memory Considerations&lt;/strong&gt;&lt;br&gt;
Elasticsearch defaults to a 1GB heap (-Xms1g -Xmx1g). For production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set heap to 50% of available RAM, but never more than 31GB (to stay within compressed OOPs).&lt;/li&gt;
&lt;li&gt;Edit /etc/elasticsearch/jvm.options.d/heap.options:
-Xms2g
-Xmx2g&lt;/li&gt;
&lt;li&gt;Ensure the system has enough RAM for both the JVM heap and filesystem cache (Lucene relies heavily on OS page cache).&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h2&gt;
  
  
  3. Stage 2: Logstash - The Data Processing Pipeline
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;3.1 What is Logstash?&lt;/strong&gt;&lt;br&gt;
Logstash is a server-side data processing pipeline that ingests data from multiple sources, transforms it, and sends it to Elasticsearch. It sits between Filebeat and Elasticsearch, adding structure to raw log lines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.2 Installation&lt;/strong&gt;&lt;br&gt;
sudo apt-get install -y logstash&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.3 Pipeline Configuration&lt;/strong&gt;&lt;br&gt;
Logstash pipelines are defined in /etc/logstash/conf.d/. Each pipeline has three sections: input, filter, and output.&lt;br&gt;
Create /etc/logstash/conf.d/boardgame.conf:&lt;br&gt;
input {&lt;br&gt;
  beats {&lt;br&gt;
    port =&amp;gt; 5044&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;filter {&lt;br&gt;
  #Parse Spring Boot log format&lt;br&gt;
  grok {&lt;br&gt;
    match =&amp;gt; { "message" =&amp;gt; "%{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:loglevel} %{GREEDYDATA:logmessage}" }&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;output {&lt;br&gt;
  elasticsearch {&lt;br&gt;
    hosts =&amp;gt; ["localhost:9200"]&lt;br&gt;
    index =&amp;gt; "boardgame-logs-%{+YYYY.MM.dd}"&lt;br&gt;
  }&lt;br&gt;
}&lt;br&gt;
Pipeline sections explained:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Input - Where data comes from:&lt;/strong&gt;&lt;br&gt;
input {&lt;br&gt;
  beats {&lt;br&gt;
    port =&amp;gt; 5044&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Filter - How data is transformed:&lt;/strong&gt;&lt;br&gt;
filter {&lt;br&gt;
  grok {&lt;br&gt;
    match =&amp;gt; { "message" =&amp;gt; "%{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:loglevel} %{GREEDYDATA:logmessage}" }&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

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

&lt;p&gt;The grok filter parses unstructured log lines into structured fields (timestamp, loglevel, logmessage). This enables filtering by log level in Kibana (e.g., show only ERROR logs).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Output - Where data goes:&lt;/strong&gt;&lt;br&gt;
output {&lt;br&gt;
  elasticsearch {&lt;br&gt;
    hosts =&amp;gt; ["localhost:9200"]&lt;br&gt;
    index =&amp;gt; "boardgame-logs-%{+YYYY.MM.dd}"&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Why daily indices?&lt;/strong&gt; Daily indices make retention management simple - delete old indices by date. They also improve search performance because Elasticsearch can skip entire indices when querying a specific time range.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.4 Start and Enable&lt;/strong&gt;&lt;br&gt;
sudo systemctl daemon-reload&lt;br&gt;
sudo systemctl enable logstash&lt;br&gt;
sudo systemctl start logstash&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.5 Verify&lt;/strong&gt;&lt;br&gt;
Check if Logstash is listening on port 5044&lt;br&gt;
sudo ss -tlnp | grep 5044&lt;/p&gt;

&lt;p&gt;Check Logstash logs for pipeline startup&lt;br&gt;
sudo journalctl -u logstash --no-pager -n 20&lt;br&gt;
Look for: Pipeline started {"pipeline.id"=&amp;gt;"main"} in the logs.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Stage 3: Kibana - The Visualization Layer
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;4.1 What is Kibana?&lt;/strong&gt;&lt;br&gt;
Kibana is the web interface for the ELK stack. It connects to Elasticsearch and provides tools for searching logs (Discover), building visualizations (charts, graphs, maps), and creating dashboards for real-time monitoring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4.2 Installation&lt;/strong&gt;&lt;br&gt;
sudo apt-get install -y kibana&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4.3 Configuration&lt;/strong&gt;&lt;br&gt;
Edit /etc/kibana/kibana.yml:&lt;br&gt;
Bind to all interfaces for external access&lt;br&gt;
server.host: "0.0.0.0"&lt;/p&gt;

&lt;p&gt;Connect to Elasticsearch over plain HTTP&lt;br&gt;
elasticsearch.hosts: ["&lt;a href="http://localhost:9200%22" rel="noopener noreferrer"&gt;http://localhost:9200"&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;Critical configuration points:&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Common pitfall:&lt;/strong&gt; If Elasticsearch has xpack.security.http.ssl.enabled: false but Kibana is configured with https:// in elasticsearch.hosts, Kibana will fail to connect with Unable to retrieve version information from Elasticsearch. Always match the protocol.&lt;/p&gt;

&lt;p&gt;Settings to remove or comment out when SSL is disabled:&lt;br&gt;
Comment out or remove these lines&lt;br&gt;
elasticsearch.ssl.certificateAuthorities: [/path/to/ca.crt]&lt;br&gt;
elasticsearch.username: "kibana_system"&lt;br&gt;
elasticsearch.password: "pass"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4.4 Start and Enable&lt;/strong&gt;&lt;br&gt;
sudo systemctl daemon-reload&lt;br&gt;
sudo systemctl enable kibana&lt;br&gt;
sudo systemctl start kibana&lt;/p&gt;

&lt;p&gt;Kibana takes 30–60 seconds to fully initialize.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4.5 Verify&lt;/strong&gt;&lt;br&gt;
curl &lt;a href="http://localhost:5601/api/status" rel="noopener noreferrer"&gt;http://localhost:5601/api/status&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Expected: {"status":{"overall":{"level":"available"}}} - the level should be available, not unavailable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4.6 Create a Data View&lt;/strong&gt;&lt;br&gt;
Once data is flowing, create a data view so Kibana knows which indices to query:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open http://:5601 in a browser.&lt;/li&gt;
&lt;li&gt;Navigate to Stack Management &amp;gt; Data Views (under Kibana section).&lt;/li&gt;
&lt;li&gt;Click Create data view.&lt;/li&gt;
&lt;li&gt;Fill in the fields:&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;Now go to Discover (under Analytics) to search and explore your logs.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;4.7 AWS Security Group&lt;/strong&gt;&lt;br&gt;
If running on AWS EC2, ensure the security group allows inbound traffic:&lt;/p&gt;

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

&lt;p&gt;Never expose port 9200 to the public internet unless Elasticsearch security is enabled with TLS and authentication.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Logs are the immune system of your infrastructure - they tell you when something is wrong before it becomes a crisis.” - DevOps principle&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  5. Stage 4: Filebeat - The Lightweight Log Shipper
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;5.1 What is Filebeat?&lt;/strong&gt;&lt;br&gt;
Filebeat is a lightweight log shipper that runs on the application server. It tails log files, handles log rotation, tracks read positions (so it never sends duplicate lines), and ships logs to Logstash or Elasticsearch with minimal resource overhead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5.2 Why Filebeat instead of sending directly to Logstash?&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;Filebeat decouples your application from the logging pipeline. If Logstash or Elasticsearch goes down, Filebeat queues events and retries automatically. Your application keeps writing to its log file without interruption.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5.3 Installation (on the Application Server)&lt;/strong&gt;&lt;br&gt;
Use the same Elastic repository&lt;br&gt;
sudo apt-get update &amp;amp;&amp;amp; sudo apt-get install -y filebeat&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5.4 Configuration&lt;/strong&gt;&lt;br&gt;
Edit /etc/filebeat/filebeat.yml:&lt;br&gt;
filebeat.inputs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;type: log
enabled: true
paths:

&lt;ul&gt;
&lt;li&gt;/home/ubuntu/Boardgame/target/app.log
multiline.pattern: '^\d{4}-\d{2}-\d{2}'
multiline.negate: true
multiline.match: after&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;output.logstash:&lt;br&gt;
  hosts: [":5044"]&lt;/p&gt;

&lt;p&gt;processors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;add_host_metadata:
  when.not.contains.tags: forwarded&lt;/li&gt;
&lt;li&gt;add_cloud_metadata: ~&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;logging.level: info&lt;/p&gt;

&lt;p&gt;Configuration explained:&lt;br&gt;
Input section:&lt;/p&gt;

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

&lt;p&gt;Multiline settings (critical for Java/Spring Boot stack traces):&lt;/p&gt;

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

&lt;p&gt;This ensures that a multi-line Java stack trace is treated as a single log event, not dozens of separate lines:&lt;br&gt;
2026-03-11 06:37:55 ERROR Something went wrong&lt;br&gt;
java.lang.NullPointerException&lt;br&gt;
    at com.example.Service.process(Service.java:42)&lt;br&gt;
    at com.example.Controller.handle(Controller.java:15)&lt;br&gt;
Without multiline config, each line of the stack trace becomes a separate Elasticsearch document - making it impossible to correlate errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Output section:&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Processors:&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;YAML formatting rules (common source of errors):&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;5.5 Start and Enable&lt;/strong&gt;&lt;br&gt;
Clear old registry to re-read files from the beginning&lt;br&gt;
sudo rm -rf /var/lib/filebeat/registry&lt;/p&gt;

&lt;p&gt;sudo systemctl daemon-reload&lt;br&gt;
sudo systemctl enable filebeat&lt;br&gt;
sudo systemctl start filebeat&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5.6 Verify&lt;/strong&gt;&lt;br&gt;
Check Filebeat status&lt;br&gt;
sudo systemctl status filebeat&lt;/p&gt;

&lt;p&gt;Check logs - look for "Harvester started"&lt;br&gt;
sudo journalctl -u filebeat --no-pager -n 20&lt;br&gt;
Key indicators in the logs:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  6. Connecting the Pieces - End-to-End Data Flow
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;6.1 The Complete Pipeline&lt;/strong&gt;&lt;br&gt;
[Spring Boot App]&lt;br&gt;
       │&lt;br&gt;
       │ writes logs to disk&lt;br&gt;
       ▼&lt;br&gt;
[/home/ubuntu/Boardgame/target/app.log]&lt;br&gt;
       │&lt;br&gt;
       │ Filebeat tails the file&lt;br&gt;
       ▼&lt;br&gt;
[Filebeat] ──── port 5044 ────► [Logstash]&lt;br&gt;
                                     │&lt;br&gt;
                                     │ grok filter parses log lines&lt;br&gt;
                                     ▼&lt;br&gt;
                              [Elasticsearch]&lt;br&gt;
                              index: boardgame-logs-2026.03.11&lt;br&gt;
                                     │&lt;br&gt;
                                     │ Kibana queries the index&lt;br&gt;
                                     ▼&lt;br&gt;
                                 [Kibana]&lt;br&gt;
                              http://:5601&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;6.2 Verifying Each Link&lt;/strong&gt;&lt;br&gt;
Test each component in order, from bottom to top:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Elasticsearch is running and accessible&lt;br&gt;
curl &lt;a href="http://localhost:9200" rel="noopener noreferrer"&gt;http://localhost:9200&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Logstash is listening for Beats input&lt;br&gt;
sudo ss -tlnp | grep 5044&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Kibana can reach Elasticsearch&lt;br&gt;
curl &lt;a href="http://localhost:5601/api/status" rel="noopener noreferrer"&gt;http://localhost:5601/api/status&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Filebeat can reach Logstash (from application server)&lt;br&gt;
telnet  5044&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Data is actually in Elasticsearch&lt;br&gt;
curl &lt;a href="http://localhost:9200/_cat/indices?v" rel="noopener noreferrer"&gt;http://localhost:9200/_cat/indices?v&lt;/a&gt; | grep boardgame&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Check document count&lt;br&gt;
curl &lt;a href="http://localhost:9200/boardgame-logs-*/_count" rel="noopener noreferrer"&gt;http://localhost:9200/boardgame-logs-*/_count&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  7. Practical Setup (Step-by-step)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;7.1 Server Requirements&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;7.2 ELK Server Setup (in order)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Step 1: Add Elastic repository&lt;br&gt;
wget -qO - &lt;a href="https://artifacts.elastic.co/GPG-KEY-elasticsearch" rel="noopener noreferrer"&gt;https://artifacts.elastic.co/GPG-KEY-elasticsearch&lt;/a&gt; | sudo gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg&lt;br&gt;
echo "deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg] &lt;a href="https://artifacts.elastic.co/packages/8.x/apt" rel="noopener noreferrer"&gt;https://artifacts.elastic.co/packages/8.x/apt&lt;/a&gt; stable main" | sudo tee /etc/apt/sources.list.d/elastic-8.x.list&lt;br&gt;
sudo apt-get update&lt;/p&gt;

&lt;p&gt;Step 2: Install all three components&lt;br&gt;
sudo apt-get install -y elasticsearch logstash kibana&lt;/p&gt;

&lt;p&gt;Step 3: Configure Elasticsearch&lt;br&gt;
sudo nano /etc/elasticsearch/elasticsearch.yml&lt;br&gt;
Set: network.host: 0.0.0.0&lt;br&gt;
Set: discovery.type: single-node&lt;br&gt;
Set: xpack.security.enabled: false&lt;br&gt;
Set: xpack.security.http.ssl.enabled: false&lt;br&gt;
Set: xpack.security.transport.ssl.enabled: false&lt;/p&gt;

&lt;p&gt;Step 4: Configure Logstash pipeline&lt;br&gt;
sudo nano /etc/logstash/conf.d/boardgame.conf&lt;br&gt;
Add input (beats, port 5044), filter (grok), output (elasticsearch)&lt;/p&gt;

&lt;p&gt;Step 5: Configure Kibana&lt;br&gt;
sudo nano /etc/kibana/kibana.yml&lt;br&gt;
Set: server.host: "0.0.0.0"&lt;br&gt;
Set: elasticsearch.hosts: ["&lt;a href="http://localhost:9200%22" rel="noopener noreferrer"&gt;http://localhost:9200"&lt;/a&gt;]&lt;br&gt;
Remove/comment any SSL certificate lines&lt;/p&gt;

&lt;p&gt;Step 6: Start services&lt;br&gt;
sudo systemctl enable elasticsearch logstash kibana&lt;br&gt;
sudo systemctl start elasticsearch logstash kibana&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7.3 Application Server Setup&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Step 1: Install Filebeat&lt;br&gt;
sudo apt-get install -y filebeat&lt;/p&gt;

&lt;p&gt;Step 2: Configure Filebeat&lt;br&gt;
sudo nano /etc/filebeat/filebeat.yml&lt;br&gt;
Replace entire file with clean config (see Section 6.4)&lt;/p&gt;

&lt;p&gt;Step 3: Clear registry and start&lt;br&gt;
sudo rm -rf /var/lib/filebeat/registry&lt;br&gt;
sudo systemctl enable filebeat&lt;br&gt;
sudo systemctl start filebeat&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7.4 Kibana Data View Setup&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Verify data arrived in Elasticsearch&lt;br&gt;
curl http://:9200/_cat/indices?v | grep boardgame&lt;br&gt;
Then in the Kibana UI: 1. Stack Management &amp;gt; Data Views &amp;gt; Create data view 2. Name: Boardgame Logs, Index pattern: boardgame-logs-*, Timestamp: @timestamp 3. Save data view to Kibana 4. Go to Discover to explore your logs&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7.5 Optional: Index Lifecycle Management&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For production, configure automatic index cleanup to prevent disk from filling up:&lt;br&gt;
Create an ILM policy that deletes indices older than 30 days&lt;br&gt;
curl -X PUT "&lt;a href="http://localhost:9200/_ilm/policy/boardgame-logs-policy" rel="noopener noreferrer"&gt;http://localhost:9200/_ilm/policy/boardgame-logs-policy&lt;/a&gt;" -H 'Content-Type: application/json' -d'&lt;br&gt;
{&lt;br&gt;
  "policy": {&lt;br&gt;
    "phases": {&lt;br&gt;
      "hot": {&lt;br&gt;
        "actions": {&lt;br&gt;
          "rollover": {&lt;br&gt;
            "max_size": "5gb",&lt;br&gt;
            "max_age": "1d"&lt;br&gt;
          }&lt;br&gt;
        }&lt;br&gt;
      },&lt;br&gt;
      "delete": {&lt;br&gt;
        "min_age": "30d",&lt;br&gt;
        "actions": {&lt;br&gt;
          "delete": {}&lt;br&gt;
        }&lt;br&gt;
      }&lt;br&gt;
    }&lt;br&gt;
  }&lt;br&gt;
}'&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Troubleshooting Common Issues
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;8.1 Kibana shows “Unable to retrieve version information from Elasticsearch”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Cause: Protocol mismatch - Kibana is using https:// but Elasticsearch has SSL disabled.&lt;br&gt;
Fix:&lt;br&gt;
Check current Kibana config&lt;br&gt;
sudo grep "elasticsearch.hosts" /etc/kibana/kibana.yml&lt;/p&gt;

&lt;p&gt;Fix: change https to http&lt;br&gt;
elasticsearch.hosts: ["&lt;a href="http://localhost:9200%22" rel="noopener noreferrer"&gt;http://localhost:9200"&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;Comment out any SSL certificate lines&lt;br&gt;
elasticsearch.ssl.certificateAuthorities: [...]&lt;/p&gt;

&lt;p&gt;sudo systemctl restart kibana&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8.2 Filebeat shows harvester.open_files: 0&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Cause: Filebeat config is malformed (duplicate sections, wrong indentation, or double-quoted regex).&lt;br&gt;
Fix: - Ensure filebeat.yml has no duplicate top-level keys. - Use single quotes for multiline.pattern (YAML treats \d in double quotes as an escape sequence). - Top-level keys (filebeat.inputs:, output.logstash:, processors:) must start at column 0.&lt;br&gt;
Validate the config&lt;br&gt;
sudo filebeat test config -c /etc/filebeat/filebeat.yml&lt;/p&gt;

&lt;p&gt;Clear registry and restart&lt;br&gt;
sudo rm -rf /var/lib/filebeat/registry&lt;br&gt;
sudo systemctl restart filebeat&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8.3 No boardgame-logs-* indices in Elasticsearch&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Cause: Filebeat cannot reach Logstash on port 5044.&lt;br&gt;
Fix:&lt;br&gt;
From the application server, test connectivity&lt;br&gt;
telnet  5044&lt;/p&gt;

&lt;p&gt;If connection refused - open port 5044 in the ELK server's security group&lt;br&gt;
If connection times out - check if Logstash is running&lt;br&gt;
sudo systemctl status logstash&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/..." class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/..." alt="Uploading image" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8.4 Kibana Data View shows “No data streams, indices, or index aliases match”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Cause: No data has been ingested yet, or the index pattern is wrong.&lt;br&gt;
Fix:&lt;br&gt;
List all indices&lt;br&gt;
curl &lt;a href="http://localhost:9200/_cat/indices?v" rel="noopener noreferrer"&gt;http://localhost:9200/_cat/indices?v&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Check the exact index name and match your pattern accordingly&lt;br&gt;
If index is "boardgame-logs-2026.03.11", pattern should be "boardgame-logs-*"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8.5 Logstash is running but not receiving data&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Cause: Logstash pipeline failed to start, or the config file has syntax errors.&lt;br&gt;
Fix:&lt;br&gt;
Test the Logstash config&lt;br&gt;
sudo /usr/share/logstash/bin/logstash --config.test_and_exit -f /etc/logstash/conf.d/&lt;/p&gt;

&lt;p&gt;Check Logstash logs&lt;br&gt;
sudo journalctl -u logstash --no-pager -n 30&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“You can’t manage what you can’t measure.” - Peter Drucker&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  9. FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q1. Why use the ELK stack instead of cloud-native logging (CloudWatch, Stackdriver)?&lt;/strong&gt; &lt;br&gt;
ELK gives you full control over data retention, parsing rules, and costs. Cloud logging services charge per GB ingested, which becomes expensive at scale. ELK is free (open-source) - you only pay for the infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q2. Why Filebeat instead of sending logs directly from the application?&lt;/strong&gt; &lt;br&gt;
Filebeat decouples your application from the logging pipeline. If Logstash or Elasticsearch goes down, Filebeat queues events and retries. Your application keeps running without blocking on log delivery. Filebeat also uses ~10–30 MB RAM versus Logstash’s 500 MB+, making it ideal for application servers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q3. Can I skip Logstash and send Filebeat directly to Elasticsearch?&lt;/strong&gt; &lt;br&gt;
Yes. Set output.elasticsearch instead of output.logstash in Filebeat. However, you lose the ability to parse and transform logs with grok filters. For simple use cases (no parsing needed), direct shipping is fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q4. Why daily indices (boardgame-logs-2026.03.11) instead of a single index?&lt;/strong&gt; &lt;br&gt;
Daily indices enable simple retention management (delete indices older than N days), improve search performance (Elasticsearch skips irrelevant time ranges), and make index management operations (backup, restore) more granular.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q5. How much disk space does the ELK stack need?&lt;/strong&gt; &lt;br&gt;
Rough estimate: 1 GB of raw logs produces ~1.5–2 GB of Elasticsearch data (due to indexing overhead). For 100 MB/day of logs with 30-day retention, budget ~6 GB for Elasticsearch data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q6. Why must multiline.pattern use single quotes in YAML?&lt;/strong&gt; &lt;br&gt;
YAML interprets backslash sequences in double-quoted strings (\d becomes an invalid escape). Single quotes treat the content literally, preserving the regex pattern for Filebeat.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q7. How do I add more application servers?&lt;/strong&gt; &lt;br&gt;
Install Filebeat on each server, point it to the same Logstash endpoint. Add a fields section to distinguish servers:&lt;br&gt;
filebeat.inputs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;type: log
enabled: true
paths:

&lt;ul&gt;
&lt;li&gt;/var/log/myapp/*.log
fields:
server_name: web-02
environment: production&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Q8. Is this setup production-ready as described?&lt;/strong&gt; &lt;br&gt;
For internal use, yes. For public-facing production, enable Elasticsearch security (xpack.security.enabled: true), use TLS certificates for all inter-component communication, put Kibana behind a reverse proxy with authentication, and configure Index Lifecycle Management for automatic cleanup.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Centralized logging transforms debugging from “SSH into each server and grep” to “search once, find everywhere.”&lt;/li&gt;
&lt;li&gt;Filebeat belongs on the application server, not the ELK server. It is lightweight (~10–30 MB RAM) and handles backpressure gracefully.&lt;/li&gt;
&lt;li&gt;Logstash’s grok filter turns unstructured log lines into structured, searchable fields (timestamp, loglevel, logmessage).&lt;/li&gt;
&lt;li&gt;Protocol mismatch (https:// vs http://) between Kibana and Elasticsearch is the most common setup failure. Always match the protocol to Elasticsearch’s actual SSL configuration.&lt;/li&gt;
&lt;li&gt;YAML formatting causes most Filebeat config errors - use single quotes for regex, no duplicate keys, no leading spaces on top-level keys.&lt;/li&gt;
&lt;li&gt;Daily indices (boardgame-logs-%{+YYYY.MM.dd}) simplify retention management and improve query performance.&lt;/li&gt;
&lt;li&gt;Security is not optional in production - enable xpack.security, use TLS, and restrict port access via security groups.&lt;/li&gt;
&lt;li&gt;Test connectivity bottom-up: Elasticsearch first, then Logstash, then Kibana, then Filebeat.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  11. Conclusion
&lt;/h2&gt;

&lt;p&gt;Setting up the ELK stack is not just about installing four packages - it is about building a reliable data pipeline that turns scattered log files into searchable, visualizable insights. Each component has a clear role: Filebeat ships, Logstash transforms, Elasticsearch stores, and Kibana visualizes.&lt;/p&gt;

&lt;p&gt;The most common failures are not in the software itself, but in the configuration glue between components: protocol mismatches between Kibana and Elasticsearch, YAML formatting errors in Filebeat, unopened firewall ports between servers, and missing runtime dependencies.&lt;/p&gt;

&lt;p&gt;By following the step-by-step approach in this guide - installing bottom-up (Elasticsearch → Logstash → Kibana → Filebeat), verifying each component before moving to the next, and understanding why each configuration setting exists - you can set up a production-grade centralized logging system that scales from a single application to dozens of services.&lt;/p&gt;

&lt;p&gt;Once the pipeline is flowing, the real value begins: building dashboards for error rates, setting up alerts for anomalies, and turning your logs from an afterthought into your first line of defense.&lt;/p&gt;

&lt;p&gt;About the Author:&lt;em&gt;Rajan is a DevOps Engineer at &lt;a href="https://www.addwebsolution.com/" rel="noopener noreferrer"&gt;AddWebSolution&lt;/a&gt;, specializing in automation infrastructure, Optimize the CI/CD Pipelines and ensuring seamless deployments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>elk</category>
      <category>elasticsearch</category>
      <category>logstash</category>
      <category>kibana</category>
    </item>
    <item>
      <title>Nginx + PHP + MySQL Optimisations and Parameter Calculations</title>
      <dc:creator>Narendra Chauhan</dc:creator>
      <pubDate>Fri, 03 Apr 2026 06:38:00 +0000</pubDate>
      <link>https://dev.to/addwebsolutionpvtltd/nginx-php-mysql-optimisations-and-parameter-calculations-3min</link>
      <guid>https://dev.to/addwebsolutionpvtltd/nginx-php-mysql-optimisations-and-parameter-calculations-3min</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;“Premature optimization is the root of all evil but lack of optimisation is the root of outages.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Architecture Overview&lt;/li&gt;
&lt;li&gt;Why Optimisation Is Required&lt;/li&gt;
&lt;li&gt;Nginx Optimisations &amp;amp; Parameter Calculations&lt;/li&gt;
&lt;li&gt;PHP-FPM Optimisations &amp;amp; Parameter Calculations&lt;/li&gt;
&lt;li&gt;MySQL Optimisations &amp;amp; Parameter Calculations&lt;/li&gt;
&lt;li&gt;System-Level Optimisations (Linux)&lt;/li&gt;
&lt;li&gt;Practical Example: Small vs Medium vs Large Server&lt;/li&gt;
&lt;li&gt;Interesting Facts &amp;amp; Statistics&lt;/li&gt;
&lt;li&gt;FAQs&lt;/li&gt;
&lt;li&gt;Key Takeaways&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;

&lt;p&gt;Modern web applications rely heavily on the Nginx + PHP + MySQL (LEMP) stack. While default configurations work for testing, they are not suitable for production traffic.&lt;/p&gt;

&lt;p&gt;Optimisation ensures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Faster page load time&lt;/li&gt;
&lt;li&gt;Better concurrency handling&lt;/li&gt;
&lt;li&gt;Lower memory and CPU usage&lt;/li&gt;
&lt;li&gt;Higher stability under load&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This document explains what to optimise, why to optimise, and how to calculate parameters practically.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Architecture Overview
&lt;/h2&gt;

&lt;p&gt;A typical request flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Client sends HTTP request&lt;/li&gt;
&lt;li&gt;Nginx handles connection &amp;amp; static content&lt;/li&gt;
&lt;li&gt;PHP-FPM processes dynamic PHP requests&lt;/li&gt;
&lt;li&gt;MySQL serves data from database&lt;/li&gt;
&lt;li&gt;Response sent back to client&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each layer must be tuned together, not individually.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Why Optimisation Is Required
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Default settings:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Are conservative&lt;/li&gt;
&lt;li&gt;Waste available RAM&lt;/li&gt;
&lt;li&gt;Limit concurrency&lt;/li&gt;
&lt;li&gt;Cause slow response under load&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Common Problems Without Optimisation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;502 / 504 Gateway errors&lt;/li&gt;
&lt;li&gt;High CPU load&lt;/li&gt;
&lt;li&gt;PHP-FPM “server reached max_children”&lt;/li&gt;
&lt;li&gt;MySQL “Too many connections”&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Nginx Optimisations &amp;amp; Parameter Calculations
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Key Nginx Parameters&lt;/strong&gt;&lt;br&gt;
worker_processes auto;&lt;br&gt;
worker_connections 4096;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is worker_processes&lt;/strong&gt;&lt;br&gt;
Number of worker processes&lt;br&gt;
Best practice: match CPU cores&lt;/p&gt;

&lt;p&gt;&lt;code&gt;nproc&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;
4 CPU cores → worker_processes 4;&lt;br&gt;
What is worker_connections&lt;br&gt;
Maximum connections per worker&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Total Max Connections&lt;/strong&gt;&lt;br&gt;
worker_processes × worker_connections&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;4 × 4096 = 16,384 concurrent connections&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommended Extra Optimisations&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use epoll;
multi_accept on;

sendfile on;
tcp_nopush on;
tcp_nodelay on;

keepalive_timeout 65;
keepalive_requests 1000;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. PHP-FPM Optimisations &amp;amp; Parameter Calculations
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Key PHP-FPM Settings&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pm = dynamic
pm.max_children = 20
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;How to Calculate pm.max_children&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Find average PHP process memory:
ps -ylC php-fpm --sort:rss&lt;/li&gt;
&lt;li&gt;Formula:
Available RAM for PHP / Avg PHP process size&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Available RAM: 2 GB&lt;/li&gt;
&lt;li&gt;Avg PHP process: 100 MB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;2048 / 100 ≈ 20&lt;br&gt;
pm.max_children = 20&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Other Important PHP Optimisations&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;request_terminate_timeout = 60
max_execution_time = 60
memory_limit = 256M
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Enable OPcache&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. MySQL Optimisations &amp;amp; Parameter Calculations
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Key MySQL Parameters&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;innodb_buffer_pool_size = 2G
innodb_buffer_pool_instances = 2
max_connections = 200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;How to Calculate innodb_buffer_pool_size&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allocate 60–70% of total RAM (dedicated DB server)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Server RAM: 4 GB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;4 × 70% ≈ 2.8 GB&lt;br&gt;
Use 2G or 3G&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connection Calculation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Additional Recommended Settings&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;query_cache_type = 0
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  7. System-Level Optimisations (Linux)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;File Descriptors&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ulimit -n 100000&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kernel Tuning&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;net.core.somaxconn = 65535&lt;br&gt;
net.ipv4.tcp_max_syn_backlog = 65535&lt;br&gt;
vm.swappiness = 10&lt;/p&gt;
&lt;h2&gt;
  
  
  8. Practical Server Size Examples
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Small Server (2 CPU / 2 GB RAM)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nginx workers: 2&lt;/li&gt;
&lt;li&gt;worker_connections: 2048&lt;/li&gt;
&lt;li&gt;PHP max_children: 10&lt;/li&gt;
&lt;li&gt;MySQL buffer pool: 1G&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Medium Server (4 CPU / 8 GB RAM)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nginx workers: 4&lt;/li&gt;
&lt;li&gt;worker_connections: 4096&lt;/li&gt;
&lt;li&gt;PHP max_children: 30–40&lt;/li&gt;
&lt;li&gt;MySQL buffer pool: 4–5G&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Large Server (8 CPU / 16 GB RAM)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nginx workers: 8&lt;/li&gt;
&lt;li&gt;worker_connections: 8192&lt;/li&gt;
&lt;li&gt;PHP max_children: 60–80&lt;/li&gt;
&lt;li&gt;MySQL buffer pool: 10–12G&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Practical Demonstration (Images Explained)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1. NGINX OPTIMISATION&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parameter Calculations&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Step 1.1  Backup current nginx.conf&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 1.2  Check current config&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  cat /etc/nginx/nginx.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Screenshot: Current state before changes&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Step 1.3  Edit nginx.conf&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo nano /etc/nginx/nginx.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Replace/update with this optimized config:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user www-data;
  worker_processes 2;                    # = nproc (2 cores)
  worker_rlimit_nofile 65535;
  pid /run/nginx.pid;
  include /etc/nginx/modules-enabled/*.conf;

  events {
      worker_connections 1024;           # 2 x 1024 = 2048 total connections
      use epoll;                         # Linux best event model
      multi_accept on;                   # Accept multiple connections at once
  }

  http {

      # Basic Settings
      sendfile on;
      tcp_nopush on;
      tcp_nodelay on;
      keepalive_timeout 30;              # Reduced from default 75s
      keepalive_requests 100;
      types_hash_max_size 2048;
      server_tokens off;                 # Hide nginx version

      client_max_body_size 20m;
      client_body_buffer_size 128k;
      client_header_buffer_size 1k;
      large_client_header_buffers 4 8k;
      client_body_timeout 12;
      client_header_timeout 12;
      send_timeout 10;

      include /etc/nginx/mime.types;
      default_type application/octet-stream;

      # Logging Settings
      access_log /var/log/nginx/access.log;
      error_log /var/log/nginx/error.log warn;   # Only warn+ to reduce I/O

      # Gzip Settings
      gzip on;
      gzip_vary on;
      gzip_proxied any;
      gzip_comp_level 3;                 # Level 3 = good ratio, low CPU
      gzip_min_length 1024;              # Don't compress tiny files
      gzip_buffers 16 8k;
      gzip_http_version 1.1;
      gzip_types
          text/plain
          text/css
          text/javascript
          application/javascript
          application/json
          application/xml
          image/svg+xml
          font/woff2;


      # Open File Cache
      open_file_cache max=1000 inactive=20s;
      open_file_cache_valid 30s;
      open_file_cache_min_uses 2;
      open_file_cache_errors on;

      # FastCGI Cache (optional - enable per site)
      fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=PHPCACHE:10m
                         max_size=100m inactive=60m use_temp_path=off;

      include /etc/nginx/conf.d/*.conf;
      include /etc/nginx/sites-enabled/*;
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 1.4  Create FastCGI cache directory&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; sudo mkdir -p /var/cache/nginx
  sudo chown www-data:www-data /var/cache/nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 1.5  Test and reload Nginx&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo nginx -t
  sudo systemctl reload nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Screenshot: nginx -t showing syntax is ok and test is successful&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Step 1.6  Verify Nginx is running with new settings&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo nginx -T | grep -E "worker_processes|worker_connections|gzip|keepalive_timeout"
systemctl status nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Screenshot: Running status + key parameters&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Step 2. PHP-FPM OPTIMISATION&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parameter Calculations&lt;/strong&gt;&lt;br&gt;
Available RAM for PHP-FPM: ~150MB (conservative, leaving room for MySQL + Nginx)&lt;br&gt;
  Average PHP-FPM process size: ~30-40MB&lt;br&gt;
  Formula: pm.max_children = 150 / 35 ≈ 4&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PHP-FPM Parameters Calculation&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;pm&lt;/strong&gt;&lt;br&gt;
 Formula: Dynamic (best for variable traffic)&lt;br&gt;
 Value: dynamic&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;pm.max_children&lt;/strong&gt;&lt;br&gt;
 Formula: 150MB ÷ 35MB/process&lt;br&gt;
 Value: 4&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;pm.start_servers&lt;/strong&gt;&lt;br&gt;
 Formula: pm.max_children / 2&lt;br&gt;
 Value: 2&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;pm.min_spare_servers&lt;/strong&gt;&lt;br&gt;
 Formula: pm.start_servers / 2&lt;br&gt;
 Value: 1&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;pm.max_spare_servers&lt;/strong&gt;&lt;br&gt;
 Formula: pm.start_servers&lt;br&gt;
 Value: 2&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;pm.max_requests&lt;/strong&gt;&lt;br&gt;
 Formula: Prevent memory leaks&lt;br&gt;
 Value: 500&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2.1  Check PHP-FPM version path&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  php -v
  ls /etc/php/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2.2  Backup pool config&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo cp/etc/php/8.4/fpm/pool.d/www.conf/etc /php/8.4/fpm/pool.d/www.conf.bak
sudo cp /etc/php/8.4/fpm/php.ini /etc/php/8.4/fpm/php.ini.bak
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2.3  Check current PHP process memory usage&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run this to see actual PHP-FPM process sizes
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ps aux | grep php-fpm | grep -v grep | awk '{sum += $6} END {print "Total RSS:", sum/1024, "MB"; print "Count:", NR; print "Avg per process:", sum/NR/1024, "MB"}'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Screenshot:&lt;/strong&gt; Current process sizes.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Step 2.4  Edit PHP-FPM pool config&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo nano /etc/php/8.4/fpm/pool.d/www.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Find and update these values (search with Ctrl+W in nano):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[www]
user = www-data
group = www-data

listen = /run/php/php8.4-fpm.sock
listen.owner = www-data
listen.group = www-data

pm = dynamic
pm.max_children = 4
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 2
pm.max_requests = 500     ;Restart workers after 500 requests (prevents memory leaks)
pm.process_idle_timeout = 10s

pm.status_path = /status  ; Enable FPM status page
ping.path = /ping

slowlog = /var/log/php8.4-fpm-slow.log
request_slowlog_timeout = 5s     ; Log requests taking &amp;gt; 5 seconds
security.limit_extensions = .php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2.5  Tune PHP OPcache&lt;/strong&gt;&lt;br&gt;
  &lt;strong&gt;sudo nano /etc/php/8.4/mods-available/opcache.ini&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

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

  ; Enable OPcache
  opcache.enable=1
  opcache.enable_cli=0

  ; Memory: 64MB for low-RAM server
  opcache.memory_consumption=64
  opcache.interned_strings_buffer=8
  opcache.max_accelerated_files=10000

  ; Production settings (set validate_timestamps=0 in prod)
  opcache.validate_timestamps=1
  opcache.revalidate_freq=60

  opcache.save_comments=1
  opcache.max_wasted_percentage=10
  opcache.use_cwd=1

  ; JIT (PHP 8.x feature)
  opcache.jit_buffer_size=32M
  opcache.jit=1255
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2.6  Tune PHP.ini key values&lt;/strong&gt;&lt;br&gt;
  &lt;strong&gt;sudo nano /etc/php/8.4/fpm/php.ini&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Find and update:
  ; Memory limit per PHP process
  memory_limit = 128M

  ; Upload/POST limits
  upload_max_filesize = 20M
  post_max_size = 25M
  max_execution_time = 60
  max_input_time = 60

  ; Error handling (production)
  display_errors = Off
  log_errors = On
  error_log = /var/log/php_errors.log

  ; Session handling
  session.gc_maxlifetime = 1440
  session.cookie_httponly = 1
  session.cookie_secure = 1

  ; Disable dangerous functions
  disable_functions = exec,passthru,shell_exec,system,proc_open,popen
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2.7  Restart PHP-FPM and verify&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo php-fpm8.4 -t
  sudo systemctl restart php8.4-fpm
  sudo systemctl status php8.4-fpm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Screenshot: PHP-FPM status showing active (running)&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Step 2.8  Verify OPcache is active&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php -r "var_dump(opcache_get_status());" | head -30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;- or check via CLI&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php -i | grep -E "opcache|OPcache"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Screenshot: OPcache enabled status&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Step 2.9  Check PHP-FPM processes after restart&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  ps aux | grep php-fpm | grep -v grep
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;- Should show master + 2 worker processes (pm.start_servers=2)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Screenshot:&lt;/strong&gt; FPM process list&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Step 3.  MYSQL OPTIMISATION&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parameter Calculations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;RAM budget for MySQL: ~200MB (out of 914MB total)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;innodb_buffer_pool_size&lt;/strong&gt;&lt;br&gt;
Formula: ~20% of RAM (shared server)&lt;br&gt;
Value: 192M&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;innodb_buffer_pool_instances&lt;/strong&gt;&lt;br&gt;
Formula: buffer_pool ÷ 128M&lt;br&gt;
Value: 1&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;max_connections&lt;/strong&gt;&lt;br&gt;
Formula: Low RAM = conservative&lt;br&gt;
Value: 50&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;innodb_log_file_size&lt;/strong&gt;&lt;br&gt;
Formula: 25% of buffer pool&lt;br&gt;
Value: 48M&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;tmp_table_size&lt;/strong&gt;&lt;br&gt;
Formula: Memory temporary tables&lt;br&gt;
Value: 16M&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;max_heap_table_size&lt;/strong&gt;&lt;br&gt;
 Formula: Same as tmp_table_size&lt;br&gt;
 Value: 16M&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;thread_cache_size&lt;/strong&gt;&lt;br&gt;
 Formula: Reuse threads&lt;br&gt;
 Value: 8&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;table_open_cache&lt;/strong&gt;&lt;br&gt;
 Formula: Open tables cache&lt;br&gt;
 Value: 400&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3.1  Check current MySQL config and status&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo cp /etc/mysql/mysql.conf.d/mysqld.cnf /etc/mysql/mysql.conf.d/mysqld.cnf.bak
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;- Check current variables&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mysql -u root -p -e "SHOW VARIABLES LIKE 'innodb_buffer_pool%';"
  mysql -u root -p -e "SHOW VARIABLES LIKE 'max_connections';"
  mysql -u root -p -e "SHOW STATUS LIKE 'Threads_connected';"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Screenshot:&lt;/strong&gt; Current MySQL variables&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Step 3.2  Check actual MySQL memory usage&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mysql -u root -p -e "
  SELECT
    ROUND(@@innodb_buffer_pool_size/1024/1024, 0) AS 'Buffer Pool MB',
    ROUND(@@key_buffer_size/1024/1024, 0) AS 'Key Buffer MB',
    @@max_connections AS 'Max Connections',
    @@thread_stack/1024 AS 'Thread Stack KB';
  "
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Screenshot:&lt;/strong&gt; Current MySQL memory config&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Step 3.3  Edit MySQL config&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Add/update under [mysqld]:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[mysqld]
  pid-file        = /var/run/mysqld/mysqld.pid
  socket          = /var/run/mysqld/mysqld.sock
  datadir         = /var/lib/mysql
  log-error       = /var/log/mysql/error.log

  #
  # ===== MEMORY SETTINGS =====
  # Server has 914MB RAM - allocate ~200MB for MySQL
  #
  innodb_buffer_pool_size         = 192M    # Main InnoDB cache (most important!)
  innodb_buffer_pool_instances    = 1       # 1 instance (&amp;lt; 1GB pool)
  innodb_log_file_size            = 48M     # ~25% of buffer pool
  innodb_log_buffer_size          = 8M
  innodb_flush_log_at_trx_commit  = 2       # Slight risk, big perf gain (use 1 for strict ACID)

  #
  # ===== CONNECTION SETTINGS =====
  #
  max_connections         = 50             # Low RAM = keep connections limited
  thread_cache_size       = 8             # Reuse threads, avoid creation overhead
  wait_timeout            = 120           # Kill idle connections after 2 min
  interactive_timeout     = 120

  #
  # ===== QUERY CACHE (removed in MySQL 8.0, skip) =====
  # MySQL 8.0 removed query_cache - use ProxySQL or app-level cache

  #
  # ===== TABLE CACHE =====
  #
  table_open_cache        = 400
  table_definition_cache  = 400

  #
  # ===== TEMP TABLES =====
  #
  tmp_table_size          = 16M
  max_heap_table_size     = 16M

  #
  # ===== InnoDB I/O =====
  #
  innodb_file_per_table           = 1
  innodb_flush_method             = O_DIRECT  # Avoid double buffering with OS cache
  innodb_read_io_threads          = 2         # = CPU cores
  innodb_write_io_threads         = 2         # = CPU cores

  #
  # ===== SLOW QUERY LOG =====
  #
  slow_query_log          = 1
  slow_query_log_file     = /var/log/mysql/slow.log
  long_query_time         = 2             # Log queries &amp;gt; 2 seconds
  log_queries_not_using_indexes = 1

  #
  # ===== BINARY LOG (disable if not using replication) =====
  #
  # skip-log-bin                           # Uncomment if no replication needed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3.4  Validate and restart MySQL&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo mysqld --validate-config
  sudo systemctl restart mysql
  sudo systemctl status mysql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Screenshot:&lt;/strong&gt; MySQL status active&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Step 3.5  Verify new MySQL variables&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mysql -u root -p -e "
  SELECT 'innodb_buffer_pool_size' AS Variable,
         ROUND(@@innodb_buffer_pool_size/1024/1024,0) AS 'Value (MB)'
  UNION SELECT 'max_connections', @@max_connections
  UNION SELECT 'innodb_log_file_size MB', ROUND(@@innodb_log_file_size/1024/1024,0)
  UNION SELECT 'tmp_table_size MB', ROUND(@@tmp_table_size/1024/1024,0)
  UNION SELECT 'thread_cache_size', @@thread_cache_size;
  "
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Screenshot:&lt;/strong&gt; New values confirmed&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Step 3.6  Check MySQL memory usage post-restart&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  free -h
  ps aux | sort -k6 -rn | head -10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Screenshot:&lt;/strong&gt; Overall memory after all services tuned&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;“Measure first, tune second, and monitor always.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Step 4. FINAL VERIFICATION&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4.1  Check all services running&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  systemctl status nginx php8.4-fpm mysql --no-pager
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4.2  Full memory picture&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;free -h
  ps aux --sort=-%mem | head -15
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4.3  Check nginx + PHP working together&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Test nginx config is still valid
  sudo nginx -t

 Test PHP-FPM socket exists
  ls -la /run/php/php8.4-fpm.sock

 Check FPM status (if you added status page to your site config)
  curl http://localhost/status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4.4  Check MySQL slow log is active&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mysql -u root -p -e "SHOW VARIABLES LIKE 'slow_query%';"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4.5  Final health summary&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo "=== NGINX ===" &amp;amp;&amp;amp; nginx -v &amp;amp;&amp;amp; systemctl is-active nginx
  echo "=== PHP-FPM ===" &amp;amp;&amp;amp; php -v | head -1 &amp;amp;&amp;amp; systemctl is-active php8.4-fpm
  echo "=== MYSQL ===" &amp;amp;&amp;amp; mysql --version &amp;amp;&amp;amp; systemctl is-active mysql
  echo "=== MEMORY ===" &amp;amp;&amp;amp; free -h
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Screenshot:&lt;/strong&gt; Final health check&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Your server is fully optimised. All phases verified:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Phase 0  Swap 2GB active&lt;/li&gt;
&lt;li&gt;  Phase 1  Nginx tuned&lt;/li&gt;
&lt;li&gt;  Phase 2  PHP-FPM + OPcache + JIT enabled&lt;/li&gt;
&lt;li&gt;  Phase 3  MySQL InnoDB / connections / slow query log active&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  9. Interesting Facts &amp;amp; Statistics
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;1 second delay can reduce conversions by 7%&lt;/li&gt;
&lt;li&gt;OPcache can improve PHP performance by 2–3×&lt;/li&gt;
&lt;li&gt;MySQL buffer pool cache hit ratio above 99% is ideal&lt;/li&gt;
&lt;li&gt;Nginx handles 10× more concurrent connections than traditional servers&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  11. FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q1. Should I optimise Nginx, PHP, or MySQL first?&lt;/strong&gt;&lt;br&gt;
Start with PHP-FPM and MySQL, then tune Nginx.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q2. Can wrong tuning crash the server?&lt;/strong&gt;&lt;br&gt;
Yes. Over-allocating RAM causes OOM kills.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q3. Are these values fixed forever?&lt;/strong&gt;&lt;br&gt;
No. Recalculate after traffic growth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q4. Do I need load testing?&lt;/strong&gt;&lt;br&gt;
Yes. Use tools like ab, wrk, or k6.&lt;/p&gt;

&lt;h2&gt;
  
  
  12. Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Optimisation is calculation-based, not guesswork&lt;/li&gt;
&lt;li&gt;PHP-FPM memory calculation is critical&lt;/li&gt;
&lt;li&gt;MySQL buffer pool has the biggest performance impact&lt;/li&gt;
&lt;li&gt;Nginx handles concurrency, not application logic&lt;/li&gt;
&lt;li&gt;Monitoring is mandatory after tuning&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  13. Conclusion
&lt;/h2&gt;

&lt;p&gt;Optimising Nginx + PHP + MySQL is not about copying configs from the internet—it is about understanding server resources, calculating limits, and balancing load across layers.&lt;br&gt;
&lt;strong&gt;A well-optimised stack:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Handles higher traffic&lt;/li&gt;
&lt;li&gt;Reduces downtime&lt;/li&gt;
&lt;li&gt;Improves user experience&lt;/li&gt;
&lt;li&gt;Saves infrastructure cost&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;About the Author:&lt;em&gt;Narendra is a DevOps Engineer at &lt;a href="https://www.addwebsolution.com/" rel="noopener noreferrer"&gt;AddWebSolution&lt;/a&gt;, specializing in automating infrastructure to improve efficiency and reliability.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>serveroptimization</category>
      <category>lempstack</category>
      <category>webperf</category>
      <category>nginxphpmysql</category>
    </item>
    <item>
      <title>Testing Node.js APIs: Jest, Supertest, and Best Practices</title>
      <dc:creator>Zemichael Mehretu</dc:creator>
      <pubDate>Tue, 31 Mar 2026 10:21:20 +0000</pubDate>
      <link>https://dev.to/addwebsolutionpvtltd/testing-nodejs-apis-jest-supertest-and-best-practices-3ddp</link>
      <guid>https://dev.to/addwebsolutionpvtltd/testing-nodejs-apis-jest-supertest-and-best-practices-3ddp</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;“Fast tests are a productivity feature; reliable tests are a business feature.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Reliable API tests are less about tool choice and more about test boundaries, data control, and deterministic execution.&lt;/li&gt;
&lt;li&gt;Jest + Supertest remains a practical default stack for HTTP API testing across Express, Fastify, and Nest-based services.&lt;/li&gt;
&lt;li&gt;The biggest gains come from balancing fast unit tests with focused integration and contract tests.&lt;/li&gt;
&lt;li&gt;Flaky tests usually indicate architecture or environment issues, not just “test instability.”&lt;/li&gt;
&lt;li&gt;A staged migration strategy outperforms rewriting the entire test suite at once.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Index
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Why Node.js API Test Suites Become Fragile Over Time&lt;/li&gt;
&lt;li&gt;Testing APIs in Practical Terms&lt;/li&gt;
&lt;li&gt;Core Testing Principles (The Guardrails)&lt;/li&gt;
&lt;li&gt;The Four Testing Layers for Node.js APIs&lt;/li&gt;
&lt;li&gt;Implementation Strategy for Engineering Teams&lt;/li&gt;
&lt;li&gt;Minimal Example: Create User Endpoint with Jest + Supertest&lt;/li&gt;
&lt;li&gt;Databases, Queues, and External Integrations&lt;/li&gt;
&lt;li&gt;Testing Strategy by Risk and Feedback Speed&lt;/li&gt;
&lt;li&gt;Migration Roadmap for Existing Node.js APIs&lt;/li&gt;
&lt;li&gt;Common Mistakes and How to Avoid Themdevf nf.f/fk&lt;/li&gt;
&lt;li&gt;When to Go Deep (and When to Keep It Lean)&lt;/li&gt;
&lt;li&gt;FAQs&lt;/li&gt;
&lt;li&gt;References&lt;/li&gt;
&lt;li&gt;Interesting facts&lt;/li&gt;
&lt;li&gt;Stats&lt;/li&gt;
&lt;li&gt;Conclution&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1) Introduction
&lt;/h2&gt;

&lt;p&gt;Most Node.js teams start with a handful of endpoint tests and feel productive quickly. The challenge appears later: as endpoints multiply, integrations grow, and multiple teams contribute, test suites get slower, noisier, and harder to trust.&lt;/p&gt;

&lt;p&gt;At that point, the question changes from “Do we have tests?” to “Can we safely change behavior without fear?”&lt;/p&gt;

&lt;p&gt;Jest and Supertest are still a strong pair for this problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Jest provides a mature runner, mocking, snapshots (when used carefully), parallelism controls, and strong TypeScript support.&lt;/li&gt;
&lt;li&gt;Supertest makes HTTP assertions straightforward by testing your app server boundary with minimal setup.
This guide focuses on practical, production-grade API testing: clear test architecture, reliable CI execution, and patterns that hold up as systems grow.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2) Why Node.js API Test Suites Become Fragile Over Time
&lt;/h2&gt;

&lt;p&gt;Most API testing pain comes from boundary confusion, not from JavaScript itself.&lt;/p&gt;

&lt;p&gt;Common symptoms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Endpoint tests that also validate unrelated business rules, persistence details, and third-party behavior in one place.&lt;/li&gt;
&lt;li&gt;Excessive mocks that make tests pass while production behavior fails.&lt;/li&gt;
&lt;li&gt;Slow suites due to global setup, shared mutable state, or non-isolated databases.&lt;/li&gt;
&lt;li&gt;Flaky tests caused by timing assumptions, network dependency, or race conditions.&lt;/li&gt;
&lt;li&gt;Hard-to-debug failures because assertions are too broad (“status should be 200”) and not intent-focused.
As these issues accumulate, teams lose confidence. Developers re-run tests repeatedly, CI cycles grow, and delivery speed drops.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3) Testing APIs in Practical Terms
&lt;/h2&gt;

&lt;p&gt;Effective API testing separates policy from transport detail.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Policy: validation rules, authorization decisions, business invariants, idempotency behavior.&lt;/li&gt;
&lt;li&gt;Detail: HTTP framework wiring, DB driver specifics, queue providers, external SDK mechanics.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Test business behavior close to the domain/service layer where feedback is fast.&lt;/li&gt;
&lt;li&gt;Test HTTP contracts at the API boundary (status codes, schema shape, headers).&lt;/li&gt;
&lt;li&gt;Test integration seams (database, message broker, external APIs) explicitly and intentionally.&lt;/li&gt;
&lt;li&gt;Keep each test focused on one intent so failures explain what broke.
A useful test heuristic: if your storage engine changes, most behavioral tests should remain valid.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4) Core Testing Principles (The Guardrails)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Principle 1: Test Behavior, Not Implementation&lt;/strong&gt;&lt;br&gt;
Prefer assertions on outcomes (response payload, side effects, emitted events) over internal call counts unless interaction itself is the behavior.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Principle 2: Keep Tests Deterministic&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Control time, randomness, and external I/O. Non-determinism is the fastest path to flaky pipelines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Principle 3: Isolate by Layer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use unit tests for logic branches, integration tests for boundaries, and endpoint tests for HTTP contract confidence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Principle 4: Keep Test Data Intentional&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use builders/factories with explicit defaults. Hidden fixture coupling creates accidental test dependencies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Principle 5: Make Failures Actionable&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every failure should quickly answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What business behavior regressed?&lt;/li&gt;
&lt;li&gt;Which boundary failed?&lt;/li&gt;
&lt;li&gt;Is this deterministic or flaky?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Principle 6: Optimize for Feedback Loop&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Developers need fast local tests and reliable CI gates. Prioritize speed for high-frequency paths.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Contract tests protect teams from integration surprises better than shared assumptions.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  5) The Four Testing Layers for Node.js APIs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A) Unit Tests (Core Behavior)&lt;/strong&gt;&lt;br&gt;
Purpose: verify pure or near-pure logic quickly.&lt;br&gt;
Test here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validation utilities&lt;/li&gt;
&lt;li&gt;Domain services&lt;/li&gt;
&lt;li&gt;Policy evaluators (authz/eligibility)&lt;/li&gt;
&lt;li&gt;Data transformation helpers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No network I/O&lt;/li&gt;
&lt;li&gt;Minimal mocking&lt;/li&gt;
&lt;li&gt;Millisecond-level runtime&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;B) Service/Use-Case Tests (Application Behavior)&lt;/strong&gt;&lt;br&gt;
Purpose: validate business workflows with mocked boundaries.&lt;br&gt;
Test here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use-case orchestration&lt;/li&gt;
&lt;li&gt;Branching by role/plan/state&lt;/li&gt;
&lt;li&gt;Error mapping from domain to application outcomes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mock repository/gateway interfaces&lt;/li&gt;
&lt;li&gt;Clear input-output assertions&lt;/li&gt;
&lt;li&gt;Strong signal for business regressions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;C) API Integration Tests (HTTP Boundary)&lt;/strong&gt;&lt;br&gt;
Purpose: verify endpoint contracts through real routing/middleware.&lt;br&gt;
Test here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Status and payload schema&lt;/li&gt;
&lt;li&gt;Auth middleware effects&lt;/li&gt;
&lt;li&gt;Validation and error format&lt;/li&gt;
&lt;li&gt;Idempotency and headers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run through supertest(app)&lt;/li&gt;
&lt;li&gt;Use real request pipeline&lt;/li&gt;
&lt;li&gt;Keep counts limited to meaningful scenarios&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;D) Infrastructure Integration Tests (Real Dependencies)&lt;/strong&gt;&lt;br&gt;
Purpose: validate adapter correctness with real systems.&lt;br&gt;
Test here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repository against real DB (often containerized)&lt;/li&gt;
&lt;li&gt;Outbound gateway behavior against sandbox/stub&lt;/li&gt;
&lt;li&gt;Queue publish/consume adapter correctness&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slower and fewer&lt;/li&gt;
&lt;li&gt;High confidence at critical seams&lt;/li&gt;
&lt;li&gt;Isolated environments required&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  6) Implementation Strategy for Engineering Teams
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Strategy 1: Start with High-Risk Endpoints&lt;/strong&gt;&lt;br&gt;
Prioritize money movement, authentication, billing, entitlement, and state-transition APIs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strategy 2: Define a Test Matrix per Endpoint&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For each endpoint, define:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;happy path,&lt;/li&gt;
&lt;li&gt;validation failures,&lt;/li&gt;
&lt;li&gt;authorization failures,&lt;/li&gt;
&lt;li&gt;business rule failures,&lt;/li&gt;
&lt;li&gt;idempotency/concurrency behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Strategy 3: Standardize Test Utilities&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create shared helpers for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;app bootstrap,&lt;/li&gt;
&lt;li&gt;authenticated request context,&lt;/li&gt;
&lt;li&gt;test data factories,&lt;/li&gt;
&lt;li&gt;DB reset/seed.&lt;/li&gt;
&lt;li&gt;This reduces duplicated setup and inconsistent patterns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Strategy 4: Separate Fast vs Slow Pipelines&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Run fast tests on every push; run heavier integration suites on merge or scheduled jobs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strategy 5: Enforce Test Ownership in Reviews&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Review checklist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the behavior covered at the right layer?&lt;/li&gt;
&lt;li&gt;Are assertions business-meaningful?&lt;/li&gt;
&lt;li&gt;Is the test deterministic?&lt;/li&gt;
&lt;li&gt;Does this duplicate lower-layer coverage?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Strategy 6: Track Quality Metrics&lt;/strong&gt;&lt;br&gt;
Monitor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;flaky test rate,&lt;/li&gt;
&lt;li&gt;mean CI duration,&lt;/li&gt;
&lt;li&gt;re-run frequency,&lt;/li&gt;
&lt;li&gt;escaped defect categories.
Good testing architecture should improve these over time.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  7) Minimal Example: Create User Endpoint with Jest + Supertest
&lt;/h2&gt;

&lt;p&gt;The goal is clarity of boundaries, not framework-specific depth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;src/app.ts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;422&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;VALIDATION_ERROR&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;email and name are required&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;usr_123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;test/users.create.spec.ts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;supertest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../src/app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST /users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;creates a user when payload is valid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sam@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sam&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sam@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sam&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;returns 422 for invalid payload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sam@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;422&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;VALIDATION_ERROR&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;package.json (relevant scripts)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jest --runInBand"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test:watch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jest --watch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test:ci"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jest --coverage --maxWorkers=50%"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In real systems, route handlers should delegate to use cases/services. Tests then become cleaner: unit tests cover rules, while Supertest verifies HTTP contract and middleware behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  8) Databases, Queues, and External Integrations
&lt;/h2&gt;

&lt;p&gt;These boundaries create most false confidence if not tested deliberately.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database: prefer isolated test DB instances, deterministic seeders, and teardown per test or per suite.&lt;/li&gt;
&lt;li&gt;Queues/events: test enqueue intent at service level, then test adapter correctness separately.&lt;/li&gt;
&lt;li&gt;External APIs: avoid live dependencies in regular CI; use stable stubs, contract checks, or sandbox environments.&lt;/li&gt;
&lt;li&gt;Retries/timeouts: assert observable outcomes (e.g., mapped errors, retry caps), not private implementation internals.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A practical split:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;regular CI: no real internet dependency,&lt;/li&gt;
&lt;li&gt;nightly/extended pipeline: sandbox integration checks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  9) Testing Strategy by Risk and Feedback Speed
&lt;/h2&gt;

&lt;p&gt;Treat tests as a portfolio.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fast lane (minutes): unit + selected service tests.&lt;/li&gt;
&lt;li&gt;Core lane (merge gate): API integration for critical endpoints.&lt;/li&gt;
&lt;li&gt;Extended lane (scheduled): infra/sandbox checks, broader resilience scenarios.
This balances confidence and developer velocity.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Recommended safeguards:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fail build on flaky test detection thresholds.&lt;/li&gt;
&lt;li&gt;Quarantine unstable tests with owner + fix-by date.&lt;/li&gt;
&lt;li&gt;Keep coverage goals directional, not vanity metrics.&lt;/li&gt;
&lt;li&gt;Prefer mutation testing selectively on high-value modules.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  10) Migration Roadmap for Existing Node.js APIs
&lt;/h2&gt;

&lt;p&gt;Avoid all-at-once rewrites.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Identify top 5 risky endpoints by incident history or business criticality.&lt;/li&gt;
&lt;li&gt;Add baseline Supertest contract tests for current behavior.&lt;/li&gt;
&lt;li&gt;Extract business logic from route handlers into testable services/use cases.&lt;/li&gt;
&lt;li&gt;Add unit/service tests around extracted rules.&lt;/li&gt;
&lt;li&gt;Introduce integration tests for critical DB/external adapters.&lt;/li&gt;
&lt;li&gt;Reduce over-mocked endpoint tests and remove duplicate coverage.&lt;/li&gt;
&lt;li&gt;Enforce testing checklist in pull requests.&lt;/li&gt;
&lt;li&gt;Repeat by vertical slice.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This path allows continuous delivery while improving reliability incrementally.&lt;/p&gt;

&lt;h2&gt;
  
  
  11) Common Mistakes and How to Avoid Them
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Over-mocking everything: tests become fiction; keep meaningful boundaries real.&lt;/li&gt;
&lt;li&gt;Only endpoint tests: slow suites and weak failure localization.&lt;/li&gt;
&lt;li&gt;Shared mutable fixtures: hidden coupling and random failures.&lt;/li&gt;
&lt;li&gt;Ignoring time/state transitions: retries, TTLs, and async workflows go untested.&lt;/li&gt;
&lt;li&gt;Snapshot overuse: brittle assertions with low business signal.&lt;/li&gt;
&lt;li&gt;No ownership model: flaky tests linger and trust decays.
Fixes are process + architecture: test at the right layer, isolate state, and treat flaky tests as production bugs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  12) When to Go Deep (and When to Keep It Lean)
&lt;/h2&gt;

&lt;p&gt;Go deeper when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;APIs control payments, access, compliance, or irreversible workflows.&lt;/li&gt;
&lt;li&gt;multiple teams ship to the same service.&lt;/li&gt;
&lt;li&gt;incident cost is high and regressions are expensive.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep it leaner when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;service is internal and low-risk,&lt;/li&gt;
&lt;li&gt;behavior is straightforward CRUD,&lt;/li&gt;
&lt;li&gt;change frequency and impact are low.&lt;/li&gt;
&lt;li&gt;Testing depth should match business risk, not trend pressure.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;“A stable test suite is not a luxury in API teams; it is delivery infrastructure.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  13) FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is Jest still a good default for Node.js API testing?&lt;/strong&gt;&lt;br&gt;
Yes. It remains a practical default for most teams due to ecosystem maturity, tooling support, and straightforward CI integration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do we need Supertest if we already unit test services?&lt;/strong&gt;&lt;br&gt;
Yes, for HTTP contract confidence. Unit tests do not validate routing, middleware, serialization, and error envelope behavior.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much mocking is too much?&lt;/strong&gt;&lt;br&gt;
If tests pass while real integration repeatedly fails, mocking is likely hiding boundary problems. Mock at external seams, not core behavior.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should we test against real third-party APIs in CI?&lt;/strong&gt;&lt;br&gt;
Usually not in the main pipeline. Prefer deterministic stubs/contracts in regular CI and schedule sandbox verification separately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is a healthy target for coverage?&lt;/strong&gt;&lt;br&gt;
There is no universal number. Prioritize meaningful coverage of critical flows and failure modes over headline percentages.&lt;/p&gt;

&lt;h2&gt;
  
  
  14) References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Jest Documentation: &lt;a href="https://jestjs.io" rel="noopener noreferrer"&gt;https://jestjs.io&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Supertest (GitHub): &lt;a href="https://github.com/ladjs/supertest" rel="noopener noreferrer"&gt;https://github.com/ladjs/supertest&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Node.js Documentation (Testing &amp;amp; Runtime): &lt;a href="https://nodejs.org" rel="noopener noreferrer"&gt;https://nodejs.org&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Martin Fowler   Test Pyramid: &lt;a href="https://martinfowler.com/articles/practical-test-pyramid.html" rel="noopener noreferrer"&gt;https://martinfowler.com/articles/practical-test-pyramid.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Google SRE Workbook (Testing in Production Concepts): &lt;a href="https://sre.google/workbook/" rel="noopener noreferrer"&gt;https://sre.google/workbook/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  15) Interesting Facts
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;JavaScript continues to be one of the most widely used languages globally, and Node.js remains one of the most commonly used web technologies among professional developers. Source: &lt;a href="https://survey.stackoverflow.co/2024/" rel="noopener noreferrer"&gt;https://survey.stackoverflow.co/2024/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Testing has become a first-class topic in modern JavaScript ecosystems, with dedicated community tracking for framework usage, satisfaction, and retention trends. Source: &lt;a href="https://stateofjs.com/" rel="noopener noreferrer"&gt;https://stateofjs.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Node.js now includes an official built-in test runner, which reflects how central testing has become in backend JavaScript workflows. Source: &lt;a href="https://nodejs.org/api/test.html" rel="noopener noreferrer"&gt;https://nodejs.org/api/test.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Jest provides native support for parallel execution and coverage reporting, which is why many teams keep it as a CI-friendly default for API and service testing. Source: &lt;a href="https://jestjs.io/docs/cli" rel="noopener noreferrer"&gt;https://jestjs.io/docs/cli&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Supertest is designed to test HTTP servers without opening a real network port, making endpoint tests faster and more deterministic in local and CI environments. Source: &lt;a href="https://github.com/ladjs/supertest" rel="noopener noreferrer"&gt;https://github.com/ladjs/supertest&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Jest is an all-in-one testing solution that includes a test runner, assertion library, and mocking capabilities out of the box.&lt;/li&gt;
&lt;li&gt;Supertest can simulate HTTP requests directly against an Express app, making tests faster and more reliable.&lt;/li&gt;
&lt;li&gt;Jest runs tests in parallel by default, significantly improving execution speed for large test suites.&lt;/li&gt;
&lt;li&gt;API testing in Node.js often focuses more on integration tests rather than pure unit tests due to the nature of backend systems.&lt;/li&gt;
&lt;li&gt;Best practice emphasizes testing API behavior (status codes, responses) rather than internal implementation details.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  16) Stats
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;According to the Stack Overflow Developer Survey, a majority of Node.js developers use automated testing in production environments.Source: &lt;a href="https://survey.stackoverflow.co/" rel="noopener noreferrer"&gt;https://survey.stackoverflow.co/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Jest has 40k+ stars on GitHub and millions of weekly npm downloads, making it one of the most widely used JavaScript testing frameworks.Source: &lt;a href="https://github.com/facebook/jest" rel="noopener noreferrer"&gt;https://github.com/facebook/jest&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;Tools like Supertest are commonly used with Express.js for API testing due to their ability to test endpoints without starting a live server.Source: &lt;a href="https://github.com/visionmedia/supertest" rel="noopener noreferrer"&gt;https://github.com/visionmedia/supertest&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  17) Conclusion
&lt;/h2&gt;

&lt;p&gt;Testing Node.js APIs effectively is not about maximizing test count; it is about placing the right tests at the right boundaries. With Jest and Supertest, teams can build a fast, trustworthy feedback loop by combining deterministic unit/service tests, focused API contract tests, and intentional integration checks. Over time, this reduces incident risk, shortens debugging cycles, and makes API evolution safer.&lt;/p&gt;

&lt;p&gt;About the Author: &lt;em&gt;Zemichael is a fullstackDeveloper at &lt;a href="https://www.addwebsolution.com/" rel="noopener noreferrer"&gt;AddWeb Solution&lt;/a&gt;.Crafting web experiences with robust architecture, performance focus, and design thinking.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>node</category>
      <category>jest</category>
      <category>supertest</category>
      <category>apitesting</category>
    </item>
    <item>
      <title>Common Mistakes Laravel Developers Make (And How to Avoid Them)</title>
      <dc:creator>Lakashya Upadhyay</dc:creator>
      <pubDate>Fri, 27 Mar 2026 08:23:53 +0000</pubDate>
      <link>https://dev.to/addwebsolutionpvtltd/common-mistakes-laravel-developers-make-and-how-to-avoid-them-3oem</link>
      <guid>https://dev.to/addwebsolutionpvtltd/common-mistakes-laravel-developers-make-and-how-to-avoid-them-3oem</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;“Controllers should coordinate, not decide.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every Laravel developer, from juniors to seasoned engineers, has written code that felt “good enough” but created performance bottlenecks, deep technical debt, or hard‑to‑debug bugs months later. Laravel’s expressive API makes it easy to ship fast, but it also makes it easy to fall into common anti‑patterns.&lt;/p&gt;

&lt;p&gt;This guide walks through the most frequent mistakes Laravel developers make in production, explains why they’re harmful, and shows how to refactor them into clean, maintainable structures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Avoid putting business logic in controllers; move it into services, actions, or domain classes.&lt;/li&gt;
&lt;li&gt;Use Eloquent relationships, eager loading, and proper indexing to prevent N+1 queries.&lt;/li&gt;
&lt;li&gt;Organize your code with proper folders, route groups, and naming conventions.&lt;/li&gt;
&lt;li&gt;Separate validation from controllers and reuse it via Form Requests or validation rules.&lt;/li&gt;
&lt;li&gt;Use migrations, tests, and documentation to keep your app scalable and on‑team‑friendly.&lt;/li&gt;
&lt;li&gt;Treat database design, authentication, and security as first‑class concerns, not afterthoughts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Index
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Why This Matters&lt;/li&gt;
&lt;li&gt;Mistake 1: Putting Too Much Logic in Controllers&lt;/li&gt;
&lt;li&gt;Mistake 2: Ignoring Eloquent Relationships and N+1 Queries&lt;/li&gt;
&lt;li&gt;Mistake 3: Poor Database Design and Indexing&lt;/li&gt;
&lt;li&gt;Mistake 4: Not Using Form Requests or Validation Properly&lt;/li&gt;
&lt;li&gt;Mistake 5: Ignoring Migrations and Model Structure&lt;/li&gt;
&lt;li&gt;Mistake 6: Messy Routes and Poor Naming Conventions&lt;/li&gt;
&lt;li&gt;Mistake 7: Overusing Helper/Static Methods and Global Helpers&lt;/li&gt;
&lt;li&gt;Mistake 8: Skipping Tests and Documentation&lt;/li&gt;
&lt;li&gt;Mistake 9: Hard‑coding Logic in Migrations or Seeders&lt;/li&gt;
&lt;li&gt;Mistake 10: Poor Error Handling and Logging&lt;/li&gt;
&lt;li&gt;Frequently Asked Questions (FAQs)&lt;/li&gt;
&lt;li&gt;Interesting Facts &amp;amp; Stats&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. Why This Matters
&lt;/h2&gt;

&lt;p&gt;Laravel is designed to help you ship fast while keeping your codebase sane. But if you ignore structure, performance, and maintainability, your app can quickly become a “Laravel monolith” with tangled controllers, slow queries, and brittle validation.&lt;/p&gt;

&lt;p&gt;Common mistakes Laravel developers make often look harmless at first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A controller method that grows from 10 to 100 lines.&lt;/li&gt;
&lt;li&gt;A route file that crosses 500 lines.&lt;/li&gt;
&lt;li&gt;A model that eagerly loads every relation on every query.
These small decisions compound over time, leading to slower performance, harder debugging, and more expensive refactoring later.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This guide helps you avoid the most common pitfalls and build Laravel apps that are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Readable to other developers&lt;/li&gt;
&lt;li&gt;Testable with clear separation of concerns&lt;/li&gt;
&lt;li&gt;Scalable as the user base and feature set grow&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Mistake 1: Putting Too Much Logic in Controllers
&lt;/h2&gt;

&lt;p&gt;Controllers are meant to handle HTTP concerns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accepting requests&lt;/li&gt;
&lt;li&gt;Running validation&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Returning responses&lt;br&gt;
When you put business logic directly in controllers, you end up with:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fat controllers anyone is afraid to touch&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Duplicated logic across multiple controller methods&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hard‑to‑test code that depends on the HTTP request&lt;br&gt;
Example of the mistake:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SubscriptionController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;upgrade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Auth&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isPremium&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;back&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withErrors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'You are already premium.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'plan'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'annual'&lt;/span&gt;
            &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nv"&gt;$gateway&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StripeGateway&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$gateway&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$price&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'premium'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;premium_until&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addYear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nf"&gt;event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UserUpgraded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/dashboard'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How to fix it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Move charge and subscription logic into a &lt;code&gt;SubscriptionService&lt;/code&gt; or &lt;code&gt;SubscriptionAction&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Keep the controller action thin and focused on the request/response pipeline.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SubscriptionController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;upgrade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;UpgradeSubscriptionAction&lt;/span&gt; &lt;span class="nv"&gt;$action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$action&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Auth&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'plan'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;back&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withErrors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/dashboard'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Business logic is reusable across APIs, commands, and jobs.&lt;/li&gt;
&lt;li&gt;Easier to unit test the subscription logic without touching HTTP.&lt;/li&gt;
&lt;li&gt;Cleaner diffs and easier on‑boarding for new team members.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Mistake 2: Ignoring Eloquent Relationships and N+1 Queries
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;“Eloquent is powerful, but misuse kills performance.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Many Laravel developers fetch data manually instead of using Eloquent relationships. This leads to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;N+1 queries (one query per related record)&lt;/li&gt;
&lt;li&gt;Slow page loads&lt;/li&gt;
&lt;li&gt;Database bottlenecks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example of the mistake:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Each access triggers a separate query&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How to fix it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Define relationships in your models (User belongsToMany Posts, etc.).&lt;/li&gt;
&lt;li&gt;Use with() to eager load related data.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// No extra query&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additional tips:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use select() to limit columns when you don’t need the full model.&lt;/li&gt;
&lt;li&gt;Avoid all() on large tables; paginate instead with paginate().&lt;/li&gt;
&lt;li&gt;Use tools like laravel-debugbar or clockwork to detect N+1 queries early.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Mistake 3: Poor Database Design and Indexing
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;“Bad database design will always be your bottleneck.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Many Laravel apps start with poorly normalized tables, missing foreign keys, or no indexes on frequently queried columns. This leads to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slow queries&lt;/li&gt;
&lt;li&gt;Complex joins and filters in PHP instead of SQL&lt;/li&gt;
&lt;li&gt;Inconsistent or denormalized data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Common anti‑patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Storing JSON in a single column instead of proper relationships&lt;/li&gt;
&lt;li&gt;Using integers for IDs but not defining foreign‑key constraints&lt;/li&gt;
&lt;li&gt;Not indexing columns used in where, order, or join clauses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How to fix it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use Laravel migrations to define tables, indexes, and foreign keys.&lt;/li&gt;
&lt;li&gt;Normalize your data where it makes sense (e.g., users → posts → comments).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Index frequently filtered columns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;foreignId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;constrained&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Faster queries as datasets grow&lt;/li&gt;
&lt;li&gt;Easier to maintain data consistency&lt;/li&gt;
&lt;li&gt;Cleaner, more predictable Eloquent relationships&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Mistake 4: Not Using Form Requests or Validation Properly
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;“Validation should be reusable, not copy‑pasted.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Laravel’s validation is powerful, but it’s often embedded directly in controllers as inline arrays or repeated in multiple methods.&lt;/p&gt;

&lt;p&gt;Example of the mistake:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'required'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'required|email'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'age'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'nullable|integer|min:18'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'required'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'required|email'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'age'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'nullable|integer|min:18'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How to fix it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create Form Request classes:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:request UserStoreRequest
php artisan make:request UserUpdateRequest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserStoreRequest&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;FormRequest&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'required'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'required|unique:users,email'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'age'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'nullable|integer|min:18'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then inject the form request into your controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;UserStoreRequest&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$validated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validated&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validation rules are reusable and centralized&lt;/li&gt;
&lt;li&gt;You can add custom validation logic or authorization inside the form request&lt;/li&gt;
&lt;li&gt;Controllers stay lean and focused&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Mistake 5: Ignoring Migrations and Model Structure
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;“They removed the model directory after the first migration.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Migrations are Laravel’s way to version‑control your database. Ignoring them or using them incorrectly leads to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Divergent schemas across environments&lt;/li&gt;
&lt;li&gt;Manual SQL changes in production&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Broken deployments&lt;br&gt;
Common mistakes:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Not using down() methods in migrations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Writing business logic inside migration files (e.g., looping through users and updating them)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Skipping foreign keys or using unsigned() on integer foreign keys&lt;br&gt;
How to fix it:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Always define proper foreign keys:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;foreignId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;constrained&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Use rollback‑safe migrations with clear up() and down() methods.&lt;/li&gt;
&lt;li&gt;Avoid complex business logic in migrations; move it to seeders or jobs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Best practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Treat migrations as “immutable” after they’re pushed to production.&lt;/li&gt;
&lt;li&gt;Use Schema::table() for alterations, not Schema::dropIfExists() for simple changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. Mistake 6: Messy Routes and Poor Naming Conventions
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;“Routes are the contract of your app.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Laravel’s routing system is powerful, but it’s easy to end up with a monolithic routes/web.php file and inconsistently named routes.&lt;/p&gt;

&lt;p&gt;Common issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mixing API and web routes in one file&lt;/li&gt;
&lt;li&gt;Not using route groups (prefix, middleware, namespace)&lt;/li&gt;
&lt;li&gt;Using inconsistent route names like userShow, showUser, show_user&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How to fix it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Split routes into:

&lt;ul&gt;
&lt;li&gt;routes/web.php (HTML pages)&lt;/li&gt;
&lt;li&gt;routes/api.php (REST or JSON endpoints)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Use route groups to apply middleware and prefixes:
&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'auth'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'admin'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'users'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;UserController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'index'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'admin.users.index'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'users'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;UserController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'store'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'admin.users.store'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Follow consistent naming conventions (e.g.,
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="n"&gt;resource_route_name&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;snake_case&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;

&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'users.'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/users'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;UserController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'index'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'index'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/users/{user}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;UserController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'show'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'show'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Routes are easier to scan and maintain&lt;/li&gt;
&lt;li&gt;Route names are predictable for other developers&lt;/li&gt;
&lt;li&gt;Auth and middleware configuration is centralized&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  8. Mistake 7: Overusing Helper/Static Methods and Global Helpers
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;“Helper methods are helpers, not your business logic.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Many Laravel apps accumulate global helper functions in &lt;code&gt;app/Helpers&lt;/code&gt; or &lt;code&gt;config/helpers.php.&lt;/code&gt; This leads to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hidden dependencies and hard‑to‑trace behavior&lt;/li&gt;
&lt;li&gt;Testing nightmare (no dependency injection)&lt;/li&gt;
&lt;li&gt;Spaghetti‑style code scattered across functions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How to fix it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use services, actions, or managers instead of global helpers.&lt;/li&gt;
&lt;li&gt;Use dependency injection where possible.
For example, instead of:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/Helpers/general.php&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;calculateDiscount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use a service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DiscountService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;User&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inject it into your controller or action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;DiscountService&lt;/span&gt; &lt;span class="nv"&gt;$discount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'price'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$discounted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$discount&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clear dependencies and easier testing&lt;/li&gt;
&lt;li&gt;Reusable across services, jobs, and controllers&lt;/li&gt;
&lt;li&gt;Easier to refactor or replace logic&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  9. Mistake 8: Skipping Tests and Documentation
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;“Tests are your safety net.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Skipping tests leads to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fear of refactoring&lt;/li&gt;
&lt;li&gt;More bugs in production&lt;/li&gt;
&lt;li&gt;Longer on‑boarding for new developers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How to fix it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Write feature tests for critical user flows.&lt;/li&gt;
&lt;li&gt;Write unit tests for business logic and services.
Use Laravel’s built‑in testing helpers:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;test_user_can_upgrade_subscription&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;actingAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'subscriptions.upgrade'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'plan'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'annual'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertRedirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/dashboard'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;fresh&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isPremium&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Best practices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use expectsDatabaseTransactions() for database‑intensive tests.&lt;/li&gt;
&lt;li&gt;Keep your tests fast and focused.&lt;/li&gt;
&lt;li&gt;Add high‑level documentation for architecture, routes, and key classes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  10. Mistake 9: Hard‑coding Logic in Migrations or Seeders
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;“Migrations are not meant to run business logic.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;People sometimes write loops, call services, or send notifications inside migrations or seeders. This is dangerous because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Migrations are meant to be repeatable and safe to roll back&lt;/li&gt;
&lt;li&gt;Running business logic here can break future deployments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How to fix it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use migrations only for:

&lt;ul&gt;
&lt;li&gt;Schema changes&lt;/li&gt;
&lt;li&gt;Simple data seeding (if needed)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Move business logic into:

&lt;ul&gt;
&lt;li&gt;Artisan commands&lt;/li&gt;
&lt;li&gt;Jobs&lt;/li&gt;
&lt;li&gt;Seeders with proper rollback support&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:command MigrateOldUserSubscriptions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run it explicitly instead of hiding it in a migration.&lt;/p&gt;

&lt;p&gt;Benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Migrations stay predictable and safe&lt;/li&gt;
&lt;li&gt;Business logic can be retried or rolled back cleanly&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  11. Mistake 10: Poor Error Handling and Logging
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;“Exceptions are information, not noise.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ignoring error handling and logging in Laravel apps leads to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Silent failures&lt;/li&gt;
&lt;li&gt;Difficult debugging in production&lt;/li&gt;
&lt;li&gt;Unpredictable behavior after exceptions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How to fix it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use try/catch blocks where appropriate.&lt;/li&gt;
&lt;li&gt;Use Log::error(), Log::info(), or report() to log errors.&lt;/li&gt;
&lt;li&gt;Configure app/Exceptions/Handler.php to:

&lt;ul&gt;
&lt;li&gt;Report critical errors to services like Sentry, Bugsnag, or LogRocket&lt;/li&gt;
&lt;li&gt;Transform exceptions into user‑friendly messages&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Throwable&lt;/span&gt; &lt;span class="nv"&gt;$exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$exception&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nc"&gt;CustomException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Log or notify specific teams&lt;/span&gt;
        &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'slack'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$exception&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getMessage&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$exception&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easier debugging and faster incident response&lt;/li&gt;
&lt;li&gt;Better visibility into production issues&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  12. Frequently Asked Questions (FAQs)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q. Should I always move logic from controllers to services?&lt;/strong&gt;&lt;br&gt;
A. Yes, if it’s reusable business logic. Keep controllers focused on HTTP concerns and use services, actions, or domain classes for the rest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q. Is it okay to keep small apps without tests?&lt;/strong&gt;&lt;br&gt;
A. For small prototypes, maybe. But even tiny apps benefit from a basic test suite once they go to production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q. Can I mix web and API routes in one file?&lt;/strong&gt;&lt;br&gt;
A. Technically yes, but it’s cleaner to separate them into web.php and api.php with proper prefixing and middleware.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q. Are migrations really necessary for small changes?&lt;/strong&gt;&lt;br&gt;
A. Yes, especially after going live. Migrations help keep your team and environment in sync.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q. How do I know if my app has N+1 queries?&lt;/strong&gt;&lt;br&gt;
A. Use tools like laravel-debugbar or clockwork to analyse queries or enable Laravel’s built‑in query logging in development.&lt;/p&gt;

&lt;h2&gt;
  
  
  13. Interesting Facts &amp;amp; Stats
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Well-structured Laravel apps with services, form requests, and proper Eloquent usage can reduce controller code by 40-60% in larger projects. Source: &lt;a href="https://laravel.com/docs/eloquent-resources" rel="noopener noreferrer"&gt;https://laravel.com/docs/eloquent-resources&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Teams that consistently use migrations and tests spend 25-35% less time debugging in production compared to teams that don’t.
Source: &lt;a href="https://martinfowler.com/articles/practical-test-pyramid.html" rel="noopener noreferrer"&gt;https://martinfowler.com/articles/practical-test-pyramid.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Avoiding N+1 queries often reduces page load time by 2-10x for list-style pages with relationships.Source: &lt;a href="https://laravel.com/docs/eloquent-relationships#eager-loading" rel="noopener noreferrer"&gt;https://laravel.com/docs/eloquent-relationships#eager-loading&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Laravel’s Form Request system and built-in validation rules have become industry standards for clean, maintainable validation logic in web frameworks.Source: &lt;a href="https://laravel.com/docs/validation" rel="noopener noreferrer"&gt;https://laravel.com/docs/validation&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  14. Conclusion
&lt;/h2&gt;

&lt;p&gt;Laravel is a powerful framework that encourages fast development, but it also amplifies the impact of bad habits. Common mistakes like fat controllers, poor database design, and ignoring migrations can slow your app and your team over time.&lt;/p&gt;

&lt;p&gt;By consistently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Moving business logic out of controllers&lt;/li&gt;
&lt;li&gt;Using Eloquent properly and avoiding N+1 queries&lt;/li&gt;
&lt;li&gt;Organizing routes, migrations, and helpers&lt;/li&gt;
&lt;li&gt;Writing tests and proper logging&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You turn Laravel’s “easy to start” advantage into a long‑term maintenance win.&lt;/p&gt;

&lt;p&gt;Clean, maintainable Laravel code is not about syntax alone; it’s about structure, separation of concerns, and respecting the framework’s conventions. Stick to these patterns, and you’ll avoid most of the common pitfalls Laravel developers face in production.&lt;/p&gt;

&lt;p&gt;About the Author: &lt;em&gt;Lakashya is a full‑stack Laravel developer at &lt;a href="https://www.addwebsolution.com/our-capabilities/laravel-development-agency" rel="noopener noreferrer"&gt;AddWeb Solution&lt;/a&gt; specializing in scalable, real‑time applications with PHP and modern frontends.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Laravel Documentation. (2025). Best Practices. 
&lt;a href="https://laravel.com/docs/12.x%E2%80%8B" rel="noopener noreferrer"&gt;https://laravel.com/docs/12.x​&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Laravel.io. (2026). Common Laravel Mistakes I See in Production. &lt;a href="https://laravel.io/articles/common-laravel-mistakes-i-see-in-production-and-how-to-avoid-them" rel="noopener noreferrer"&gt;https://laravel.io/articles/common-laravel-mistakes-i-see-in-production-and-how-to-avoid-them&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Laravel Daily. (2025). Laravel: 9 Typical Mistakes Juniors Make. &lt;a href="https://laraveldaily.com/post/laravel-typical-mistakes-juniors-make" rel="noopener noreferrer"&gt;https://laraveldaily.com/post/laravel-typical-mistakes-juniors-make&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>laravelbestpractices</category>
      <category>laravelmistakes</category>
      <category>phpdevelopment</category>
      <category>cleancode</category>
    </item>
    <item>
      <title>Practical Techniques for Optimizing React Performance in Production: A Developer’s Guide</title>
      <dc:creator>Mayank Goyal</dc:creator>
      <pubDate>Thu, 26 Mar 2026 10:34:57 +0000</pubDate>
      <link>https://dev.to/addwebsolutionpvtltd/practical-techniques-for-optimizing-react-performance-in-production-a-developers-guide-ho0</link>
      <guid>https://dev.to/addwebsolutionpvtltd/practical-techniques-for-optimizing-react-performance-in-production-a-developers-guide-ho0</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;"Premature optimization is the root of all evil." - Donald Knuth&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;As modern web applications grow in complexity, maintaining high performance becomes increasingly important. React is known for its efficient rendering and component-based architecture, but poorly optimized applications can still suffer from issues such as slow rendering, unnecessary re-renders, large bundle sizes, and delayed user interactions.&lt;/p&gt;

&lt;p&gt;Optimizing React applications in production environments requires a combination of architectural decisions, efficient state management, and performance monitoring.&lt;/p&gt;

&lt;p&gt;This guide explores practical techniques developers can use to optimize React performance in production, helping applications remain fast, scalable, and responsive even as they grow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Avoid unnecessary component re-renders using React.memo, useMemo, and useCallback.&lt;/li&gt;
&lt;li&gt;Use code splitting and lazy loading to reduce initial bundle size.&lt;/li&gt;
&lt;li&gt;Implement list virtualization when rendering large datasets.&lt;/li&gt;
&lt;li&gt;Optimize state management to prevent unnecessary updates.&lt;/li&gt;
&lt;li&gt;Use debouncing and throttling for expensive operations like search or scroll events.&lt;/li&gt;
&lt;li&gt;Regularly monitor performance using React DevTools and browser profiling tools.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Index
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Understanding React Performance Optimization&lt;/li&gt;
&lt;li&gt;Detailed Comparison of Optimization Techniques&lt;/li&gt;
&lt;li&gt;Implementation Overview&lt;/li&gt;
&lt;li&gt;When to Use Each Optimization Strategy&lt;/li&gt;
&lt;li&gt;Developer Recommendations&lt;/li&gt;
&lt;li&gt;Code Examples&lt;/li&gt;
&lt;li&gt;FAQ&lt;/li&gt;
&lt;li&gt;Interesting Facts&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. Understanding React Performance Optimization
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1.1 Preventing Unnecessary Re-Renders&lt;/strong&gt;&lt;br&gt;
React re-renders components whenever their state or props change. While React's Virtual DOM minimizes DOM updates, unnecessary re-renders can still affect performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Techniques&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;React.memo&lt;/strong&gt;&lt;br&gt;
Prevents functional components from re-rendering when props have not changed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;useMemo&lt;/strong&gt;&lt;br&gt;
Caches the result of expensive computations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;useCallback&lt;/strong&gt;&lt;br&gt;
Prevents functions from being recreated on every render.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best For&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Components that render frequently&lt;/li&gt;
&lt;li&gt;Expensive calculations inside components&lt;/li&gt;
&lt;li&gt;Preventing unnecessary child component updates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;1.2 Code Splitting and Lazy Loading&lt;/strong&gt;&lt;br&gt;
Large JavaScript bundles increase page load time and negatively impact performance.&lt;br&gt;
Code splitting allows loading only the code required for the current view.&lt;br&gt;
React provides built-in support through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React.lazy&lt;/li&gt;
&lt;li&gt;Suspense&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Benefits&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Faster initial page load&lt;/li&gt;
&lt;li&gt;Reduced bundle size&lt;/li&gt;
&lt;li&gt;Improved performance on slower networks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best For&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Large applications&lt;/li&gt;
&lt;li&gt;Route-based components&lt;/li&gt;
&lt;li&gt;Feature-heavy dashboards&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;1.3 Virtualizing Large Lists&lt;/strong&gt;&lt;br&gt;
Rendering thousands of DOM elements simultaneously can slow down the browser.&lt;br&gt;
List virtualization renders only the items visible in the viewport.&lt;br&gt;
Popular libraries include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;react-window&lt;/li&gt;
&lt;li&gt;react-virtualized&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best For&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Large tables&lt;/li&gt;
&lt;li&gt;Infinite scrolling lists&lt;/li&gt;
&lt;li&gt;Analytics dashboards&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  2. Detailed Comparison
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;2.1 Rendering Optimization&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;2.2 Bundle Optimization&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;2.3 Event Optimization&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbv8fa13queapqzubiqum.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbv8fa13queapqzubiqum.png" alt=" " width="539" height="149"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Implementation Overview
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;3.1 Memoizing Components&lt;/strong&gt;&lt;br&gt;
Flow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Component receives props&lt;/li&gt;
&lt;li&gt;React compares previous and new props&lt;/li&gt;
&lt;li&gt;If props are unchanged, rendering is skipped&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pros&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduces unnecessary rendering&lt;/li&gt;
&lt;li&gt;Improves UI responsiveness&lt;/li&gt;
&lt;li&gt;Useful in component-heavy applications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3.2 Code Splitting in React&lt;/strong&gt;&lt;br&gt;
Flow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User visits application&lt;/li&gt;
&lt;li&gt;Initial bundle loads&lt;/li&gt;
&lt;li&gt;Additional components load on demand&lt;/li&gt;
&lt;li&gt;Suspense displays fallback UI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pros&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Faster application startup&lt;/li&gt;
&lt;li&gt;Reduced network usage&lt;/li&gt;
&lt;li&gt;Better scalability for large apps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3.3 Virtualizing Lists&lt;/strong&gt;&lt;br&gt;
Flow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Large dataset fetched&lt;/li&gt;
&lt;li&gt;Only visible rows rendered&lt;/li&gt;
&lt;li&gt;Rows dynamically mounted/unmounted during scroll&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pros&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduces DOM nodes&lt;/li&gt;
&lt;li&gt;Smooth scrolling&lt;/li&gt;
&lt;li&gt;Improves memory usage&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  4. When to Use Each Optimization Technique
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use Memoization If&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Components render frequently&lt;/li&gt;
&lt;li&gt;Parent components trigger unnecessary updates&lt;/li&gt;
&lt;li&gt;Expensive calculations occur during rendering&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Use Code Splitting If&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your bundle size is large&lt;/li&gt;
&lt;li&gt;Your app has multiple pages&lt;/li&gt;
&lt;li&gt;You want faster initial load time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Use Virtualization If&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rendering thousands of items&lt;/li&gt;
&lt;li&gt;Working with large datasets&lt;/li&gt;
&lt;li&gt;Building analytics dashboards or tables&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  5. Developer Recommendation
&lt;/h2&gt;

&lt;p&gt;For most production React applications, the following performance practices provide the biggest impact:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Memoize components when necessary&lt;/li&gt;
&lt;li&gt;Implement route-based code splitting&lt;/li&gt;
&lt;li&gt;Use virtualization for large lists&lt;/li&gt;
&lt;li&gt;Debounce expensive API requests&lt;/li&gt;
&lt;li&gt;Continuously monitor performance using profiling tools&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Optimizing too early can introduce unnecessary complexity, so focus first on identifying performance bottlenecks before applying optimizations.&lt;/p&gt;
&lt;h2&gt;
  
  
  6. Code Examples
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;6.1 Preventing Re-renders with React.memo&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;UserCard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Rendering UserCard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;UserCard&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6.2 Using useMemo for Expensive Calculations&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useMemo&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ExpensiveComponent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sortedItems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;sortedItems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6.3 Lazy Loading Components&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Suspense&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Dashboard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lazy&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./Dashboard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Suspense&lt;/span&gt; &lt;span class="na"&gt;fallback&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Loading...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Dashboard&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Suspense&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6.4 Debouncing API Calls&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;debounce&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lodash/debounce&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;searchUsers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;debounce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/users?q=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  7. FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. What causes poor React performance?&lt;/strong&gt;&lt;br&gt;
Common causes include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unnecessary re-renders&lt;/li&gt;
&lt;li&gt;Large bundle sizes&lt;/li&gt;
&lt;li&gt;Rendering large lists&lt;/li&gt;
&lt;li&gt;Heavy computations during rendering&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Should every component use React.memo?&lt;/strong&gt;&lt;br&gt;
No. Memoization adds comparison overhead. Use it only when performance issues exist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. What tool helps identify slow components?&lt;/strong&gt;&lt;br&gt;
React DevTools Profiler helps identify slow rendering components.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Is lazy loading good for SEO?&lt;/strong&gt;&lt;br&gt;
It can be, but server-side rendering (SSR) may be required for SEO-critical pages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. What is the biggest performance win in React apps?&lt;/strong&gt;&lt;br&gt;
Reducing bundle size and unnecessary re-renders typically provides the largest improvements.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Interesting Facts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;React’s Virtual DOM reduces direct DOM manipulation by updating only the elements that changed.Source:&lt;a href="https://react.dev/learn/render-and-commit" rel="noopener noreferrer"&gt;https://react.dev/learn/render-and-commit&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The React Profiler tool allows developers to measure rendering performance and detect unnecessary updates.Source:&lt;a href="https://react.dev/reference/react/Profiler" rel="noopener noreferrer"&gt;https://react.dev/reference/react/Profiler&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Libraries like react-window can reduce thousands of DOM nodes to just a few dozen visible elements.Source:&lt;a href="https://github.com/bvaughn/react-window" rel="noopener noreferrer"&gt;https://github.com/bvaughn/react-window&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;"Programs must be written for people to read, and only incidentally for machines to execute." – Harold Abelson&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  9. Conclusion
&lt;/h2&gt;

&lt;p&gt;Optimizing React performance in production requires thoughtful architecture and strategic use of optimization techniques. While React already provides efficient rendering through the Virtual DOM, developers must still manage component updates, bundle sizes, and expensive computations carefully.&lt;/p&gt;

&lt;p&gt;By applying techniques such as memoization, code splitting, virtualization, and event optimization, developers can significantly improve application responsiveness and scalability.&lt;br&gt;
The key is to identify real performance bottlenecks first and apply targeted optimizations, ensuring the application remains both maintainable and performant.&lt;/p&gt;

&lt;p&gt;About the Author: &lt;em&gt;Mayank is a web developer at &lt;a href="https://www.addwebsolution.com/" rel="noopener noreferrer"&gt;AddWebSolution&lt;/a&gt;, building scalable apps with PHP, Node.js &amp;amp; React. Sharing ideas, code, and creativity.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>reactjsdevelopment</category>
      <category>performance</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Secure File Uploads in Laravel: Validation, Storage &amp; Basic Virus Protection</title>
      <dc:creator>Abodh Kumar</dc:creator>
      <pubDate>Fri, 20 Mar 2026 07:54:18 +0000</pubDate>
      <link>https://dev.to/addwebsolutionpvtltd/secure-file-uploads-in-laravel-validation-storage-basic-virus-protection-1hb4</link>
      <guid>https://dev.to/addwebsolutionpvtltd/secure-file-uploads-in-laravel-validation-storage-basic-virus-protection-1hb4</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Security is not a product, but a process.- Bruce Schneier, Security Technologist&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;File uploads are a common requirement in modern web applications - whether it's profile pictures, documents, invoices, or media files. However, insecure file uploads can expose your application to serious threats such as malware injection, remote code execution, and data breaches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaway
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Validate both file extension (mimes) and content-based MIME type (mimetypes) - never trust one alone.&lt;/li&gt;
&lt;li&gt;Always generate a random UUID-based filename - never persist client-supplied filenames to disk.&lt;/li&gt;
&lt;li&gt;Store files outside the public directory on a private disk; serve through authenticated controllers.&lt;/li&gt;
&lt;li&gt;Use temporary signed URLs for file downloads to prevent unauthorized access and link sharing.&lt;/li&gt;
&lt;li&gt;Integrate ClamAV or another AV engine; fail closed if the scanner is unavailable.&lt;/li&gt;
&lt;li&gt;Strip EXIF metadata from images to protect user privacy and prevent location data leaks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Understanding File Upload Risks&lt;/li&gt;
&lt;li&gt;Laravel File Validation - A Deep Dive&lt;/li&gt;
&lt;li&gt;Secure Storage Strategies&lt;/li&gt;
&lt;li&gt;Basic Virus Protection with ClamAV&lt;/li&gt;
&lt;li&gt;Advanced Security Techniques&lt;/li&gt;
&lt;li&gt;Stats &amp;amp; Interesting Facts&lt;/li&gt;
&lt;li&gt;FAQ&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;

&lt;p&gt;document managers to medical portals and e-commerce platforms. Yet despite their ubiquity, secure file handling remains one of the most misunderstood areas of web development.&lt;/p&gt;

&lt;p&gt;Laravel, PHP's premier web framework, equips developers with an elegant and expressive set of tools for handling file uploads. However, leveraging those tools securely requires a deliberate, layered approach: validating the incoming file, storing it safely, and scanning it for malicious content before it ever reaches your users or your system.&lt;/p&gt;

&lt;p&gt;This article walks you through a complete, production-ready strategy for secure file uploads in Laravel - covering everything from MIME type validation and file size limits to randomized storage paths and ClamAV antivirus integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Understanding File Upload Risks
&lt;/h2&gt;

&lt;p&gt;Before writing a single line of code, it is essential to understand why file uploads are dangerous. Attackers routinely exploit careless upload implementations to compromise servers and steal data. The most common attack vectors include:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.1  Malicious File Execution&lt;/strong&gt;&lt;br&gt;
An attacker uploads a PHP file disguised as an image (e.g., evil.php.jpg). If the server is misconfigured or the MIME type is not properly validated, the file may be executed as a script, granting the attacker remote code execution (RCE).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.2  Denial of Service via Large Uploads&lt;/strong&gt;&lt;br&gt;
Without file size restrictions, an attacker can upload multi-gigabyte files to exhaust disk space or memory, effectively taking down the server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.3  Path Traversal Attacks&lt;/strong&gt;&lt;br&gt;
If user-supplied filenames are stored directly, an attacker might submit a filename like ../../etc/passwd to write or overwrite sensitive files on the server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.4  Virus &amp;amp; Malware Distribution&lt;/strong&gt;&lt;br&gt;
Even with correct validation, a seemingly valid PDF or DOCX file might contain embedded malware. If your application serves these files to other users, you inadvertently become a malware distribution vector.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.5  Metadata &amp;amp; Privacy Leaks&lt;/strong&gt;&lt;br&gt;
Uploaded images may contain EXIF metadata including GPS coordinates, device information, or personally identifiable information - a significant privacy risk if files are served publicly without stripping metadata.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Laravel File Validation - A Deep Dive
&lt;/h2&gt;

&lt;p&gt;Laravel's validation system is remarkably expressive. The file() and image() validation rules, combined with additional constraints, let you define exactly what constitutes an acceptable upload.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.1  Basic Validation Setup&lt;/strong&gt;&lt;br&gt;
Start with a dedicated Form Request class to keep your controller lean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;    php artisan make:request SecureFileUploadRequest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In your SecureFileUploadRequest class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Http\Requests&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Foundation\Http\FormRequest&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SecureFileUploadRequest&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;FormRequest&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
   &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
   &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
           &lt;span class="s1"&gt;'document'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
               &lt;span class="s1"&gt;'required'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="s1"&gt;'file'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="s1"&gt;'mimes:pdf,docx,xlsx,png,jpg,jpeg'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="s1"&gt;'mimetypes:application/pdf,image/png,image/jpeg,
                          application/vnd.openxmlformats-officedocument
                          .wordprocessingml.document'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="s1"&gt;'max:10240'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
               &lt;span class="s1"&gt;'min:1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
           &lt;span class="p"&gt;],&lt;/span&gt;
       &lt;span class="p"&gt;];&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
   &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
           &lt;span class="s1"&gt;'document.mimes'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Only PDF, DOCX, XLSX, PNG, and JPG                   files are allowed.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="s1"&gt;'document.max'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'File size must not exceed 10 MB.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="s1"&gt;'document.mimetypes'&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'The file type does not match the expected MIME type.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="p"&gt;];&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3.2  Understanding mimes vs. mimetypes&lt;/strong&gt;&lt;br&gt;
This is a common source of confusion. The mimes rule validates using the file extension, while mimetypes validates the actual MIME type detected by PHP's Fileinfo extension. Using both together is a stronger approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;mimes:pdf,png - Laravel maps the extension to an expected MIME type and checks it.&lt;/li&gt;
&lt;li&gt;mimetypes:application/pdf - Directly checks the MIME type detected from file content.&lt;/li&gt;
&lt;li&gt;Using both ensures neither the extension nor the MIME type is spoofed independently.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3.3  Validating Dimensions for Images&lt;/strong&gt;&lt;br&gt;
For image uploads specifically, Laravel allows you to enforce dimension constraints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="s1"&gt;'avatar'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
   &lt;span class="s1"&gt;'required'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s1"&gt;'image'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s1"&gt;'mimes:jpeg,png,webp'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s1"&gt;'max:2048'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s1"&gt;'dimensions:min_width=100,min_height=100,max_width=3000,max_height=3000'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Secure Storage Strategies
&lt;/h2&gt;

&lt;p&gt;Validating a file is only half the battle. Where and how you store it is equally critical to your application's security posture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4.1  Never Store in the Public Directory&lt;/strong&gt;&lt;br&gt;
A common beginner mistake is storing uploads directly in public/uploads. This makes files web-accessible by default, which is dangerous. Use the storage/app/private directory instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Storage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Str&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;SecureFileUploadRequest&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nv"&gt;$file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'document'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

   &lt;span class="nv"&gt;$filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Str&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'.'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getClientOriginalExtension&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

   &lt;span class="nv"&gt;$path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;storeAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
       &lt;span class="s1"&gt;'uploads/'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nf"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
       &lt;span class="nv"&gt;$filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="s1"&gt;'private'&lt;/span&gt; 
   &lt;span class="p"&gt;);&lt;/span&gt;

   &lt;span class="nc"&gt;Document&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
       &lt;span class="s1"&gt;'user_id'&lt;/span&gt;        &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
       &lt;span class="s1"&gt;'original_name'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getClientOriginalName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
       &lt;span class="s1"&gt;'stored_path'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="s1"&gt;'mime_type'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getMimeType&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
       &lt;span class="s1"&gt;'file_size'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getSize&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
   &lt;span class="p"&gt;]);&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'message'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'File uploaded successfully.'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4.2  Configuring a Private Disk&lt;/strong&gt;&lt;br&gt;
In config/filesystems.php, define a private disk that lives outside the public web root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="s1"&gt;'disks'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
   &lt;span class="s1"&gt;'private'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
       &lt;span class="s1"&gt;'driver'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'local'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="s1"&gt;'root'&lt;/span&gt;       &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;storage_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'app/private'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
       &lt;span class="s1"&gt;'visibility'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'private'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="p"&gt;],&lt;/span&gt;
   &lt;span class="s1"&gt;'s3_private'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
       &lt;span class="s1"&gt;'driver'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'s3'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="s1"&gt;'key'&lt;/span&gt;        &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'AWS_ACCESS_KEY_ID'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
       &lt;span class="s1"&gt;'secret'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'AWS_SECRET_ACCESS_KEY'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
       &lt;span class="s1"&gt;'region'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'AWS_DEFAULT_REGION'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
       &lt;span class="s1"&gt;'bucket'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'AWS_BUCKET'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
       &lt;span class="s1"&gt;'visibility'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'private'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Never trust user input when handling uploaded files. Always validate file types, sizes, and storage locations on the server side.- Stephen Rees-Carter&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;4.3  Serving Files Securely via Signed URLs&lt;/strong&gt;&lt;br&gt;
Since files are not publicly accessible, you need a controller endpoint to serve them. Use signed URLs to prevent unauthorized access:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Illuminate\Support\Facades\URL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$signedUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;temporarySignedRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="s1"&gt;'file.download'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'document'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$document&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/files/{document}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Document&lt;/span&gt; &lt;span class="nv"&gt;$document&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nf"&gt;abort_unless&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
       &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;hasValidSignature&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
       &lt;span class="nv"&gt;$document&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nf"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
       &lt;span class="mi"&gt;403&lt;/span&gt;
   &lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Storage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;disk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'private'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;download&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$document&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;stored_path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'file.download'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'auth'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'signed'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Basic Virus Protection with ClamAV
&lt;/h2&gt;

&lt;p&gt;Even perfectly validated files can harbor malicious payloads. A PDF can contain JavaScript exploits; a DOCX can embed macros. Integrating an antivirus scanner is the professional-grade answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5.1  Installing ClamAV&lt;/strong&gt;&lt;br&gt;
On Ubuntu/Debian servers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; clamav clamav-daemon
&lt;span class="nb"&gt;sudo &lt;/span&gt;freshclam   &lt;span class="c"&gt;# Update virus definitions&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;clamav-daemon
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start clamav-daemon
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5.2  Using the Laravel ClamAV Package&lt;/strong&gt;&lt;br&gt;
The most popular Laravel integration is via the laravel-clamav package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require clamav/clamav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or use a custom wrapper with the PHP socket connection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require sunspikes/clamav-validator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5.3  Building a ClamAV Service&lt;/strong&gt;&lt;br&gt;
Here is a clean, reusable ClamAV service class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Services&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Http\UploadedFile&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Log&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VirusScanService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$clamdHost&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$clamdPort&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

   &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
   &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;clamdHost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'services.clamav.host'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'127.0.0.1'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
       &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;clamdPort&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'services.clamav.port'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3310&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;UploadedFile&lt;/span&gt; &lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
   &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="nv"&gt;$socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;fsockopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
           &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;clamdHost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;clamdPort&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="nv"&gt;$errno&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="nv"&gt;$errstr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="mi"&gt;5&lt;/span&gt;
       &lt;span class="p"&gt;);&lt;/span&gt;

       &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
           &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ClamAV unavailable'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'error'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$errstr&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
           &lt;span class="c1"&gt;// Fail closed: reject upload if scanner unavailable&lt;/span&gt;
           &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       &lt;span class="p"&gt;}&lt;/span&gt;

       &lt;span class="nv"&gt;$fileContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;file_get_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getRealPath&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
       &lt;span class="nv"&gt;$length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;strlen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fileContent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

       &lt;span class="nb"&gt;fwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"nINSTREAM&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
       &lt;span class="nb"&gt;fwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;pack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'N'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$fileContent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
       &lt;span class="nb"&gt;fwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;pack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'N'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

       &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;fgets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$socket&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
       &lt;span class="nb"&gt;fclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$socket&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
       &lt;span class="c1"&gt;// 'stream: OK' means clean; anything else is infected&lt;/span&gt;
       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;str_contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'OK'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5.4  Integrating the Scanner in Your Controller&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Services\VirusScanService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;SecureFileUploadRequest&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;VirusScanService&lt;/span&gt; &lt;span class="nv"&gt;$scanner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nv"&gt;$file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'document'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$scanner&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
           &lt;span class="s1"&gt;'message'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'File rejected: security scan failed.'&lt;/span&gt;
       &lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;422&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Allowing unrestricted file uploads can lead to remote code execution if attackers manage to upload executable scripts to the server.- OWASP Security Guidelines&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  6. Advanced Security Techniques
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;6.1  Strip EXIF Metadata from Images&lt;/strong&gt;&lt;br&gt;
Use the intervention/image package to remove sensitive metadata from uploaded images before storage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require intervention/image
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Intervention\Image\Facades\Image&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getRealPath&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="c1"&gt;// Strip all EXIF data by re-encoding&lt;/span&gt;
&lt;span class="nv"&gt;$cleanImage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$image&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'jpg'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;Storage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;disk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'private'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$cleanImage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6.2  Implement Rate Limiting on Upload Endpoints&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In routes/api.php&lt;/span&gt;
&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'auth'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'throttle:10,1'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="c1"&gt;// 10 uploads per minute&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/upload'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;FileController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'store'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6.3  Queue Virus Scans for Large Files&lt;/strong&gt;&lt;br&gt;
For large files, run the virus scan asynchronously to avoid request timeouts:&lt;/p&gt;

&lt;p&gt;// Create a job&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:job ScanUploadedFile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Dispatch after initial upload&lt;/span&gt;
&lt;span class="nc"&gt;ScanUploadedFile&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$document&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;onQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'scans'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6.4  Content Security Headers&lt;/strong&gt;&lt;br&gt;
Even with secure storage, add these HTTP headers when serving files to prevent browsers from executing served content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;download&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withHeaders&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
   &lt;span class="s1"&gt;'X-Content-Type-Options'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'nosniff'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s1"&gt;'Content-Disposition'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'attachment; filename="'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$filename&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'"'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="s1"&gt;'X-Frame-Options'&lt;/span&gt;        &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'DENY'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  7.Stats &amp;amp; Interesting Facts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;According to the Veracode State of Software Security Report, file upload vulnerabilities are responsible for a significant portion of web application security flaws, with around 12% of critical vulnerabilities related to improper file handling.Source: &lt;a href="https://www.veracode.com/resources/reports/state-of-software-security-report" rel="noopener noreferrer"&gt;https://www.veracode.com/resources/reports/state-of-software-security-report&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The OWASP Top 10 highlights insecure file upload mechanisms as a common cause of Remote Code Execution (RCE) attacks when applications fail to properly validate file types and storage paths.
Source: &lt;a href="https://owasp.org/www-project-top-ten/" rel="noopener noreferrer"&gt;https://owasp.org/www-project-top-ten/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Research shows that attackers often bypass validation by disguising malicious scripts as image or PDF files, such as renaming a .php file to .jpg or .png.Source: &lt;a href="https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload" rel="noopener noreferrer"&gt;https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Malware scanning systems like ClamAV can detect up to 98% of known malware signatures, making antivirus scanning a useful additional layer for file upload security.Source: &lt;a href="https://www.clamav.net/documents/clamav-user-manual" rel="noopener noreferrer"&gt;https://www.clamav.net/documents/clamav-user-manual&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;According to security studies, allowing unrestricted file sizes can lead to Denial-of-Service (DoS) attacks, where attackers intentionally upload large files to exhaust server disk space.Source: &lt;a href="https://owasp.org/www-community/attacks/Denial_of_Service" rel="noopener noreferrer"&gt;https://owasp.org/www-community/attacks/Denial_of_Service&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;According to cybersecurity research, over 90% of successful web attacks exploit known vulnerabilities that could have been prevented through proper validation and secure configuration practices.Source: &lt;a href="https://www.verizon.com/business/resources/reports/dbir/" rel="noopener noreferrer"&gt;https://www.verizon.com/business/resources/reports/dbir/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;According to the SANS Institute, improper file upload validation is one of the most common ways attackers upload web shells, which can allow them to execute commands on the server and gain full control of the application.Source: &lt;a href="https://www.sans.org/white-papers/" rel="noopener noreferrer"&gt;https://www.sans.org/white-papers/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  8. FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Why is file upload security important in Laravel?&lt;/strong&gt;&lt;br&gt;
Ans: File upload security is important because attackers can upload malicious files such as scripts or malware. If the application does not properly validate and store files, it may lead to serious vulnerabilities like Remote Code Execution (RCE), data theft, or server compromise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. How can Laravel validate uploaded files?&lt;/strong&gt;&lt;br&gt;
Ans: Laravel provides built-in validation rules such as mimes, mimetypes, max, and file. These rules help ensure that only allowed file types and sizes are uploaded, reducing the risk of malicious files entering the system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Where should uploaded files be stored in Laravel?&lt;/strong&gt;&lt;br&gt;
Ans: Uploaded files should ideally be stored in the storage directory instead of the public directory. Laravel’s Storage system helps manage files securely and prevents direct execution of uploaded files on the server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. How can I limit the file size in Laravel uploads?&lt;/strong&gt;&lt;br&gt;
Ans: You can limit file size using the max validation rule in Laravel. For example:&lt;br&gt;
'file' =&amp;gt; 'required|mimes:jpg,png,pdf|max:2048'&lt;br&gt;
 This restricts uploads to specific file types and a maximum size of 2MB.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Can Laravel scan uploaded files for viruses?&lt;/strong&gt;&lt;br&gt;
Ans: Laravel does not include built-in antivirus scanning, but you can integrate tools like ClamAV or third-party security services to scan uploaded files before storing or processing them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. What are some best practices for secure file uploads?&lt;/strong&gt;&lt;br&gt;
Ans: Best practices include validating file types, restricting file sizes, renaming uploaded files, storing them outside the public directory, scanning for malware, and setting proper file permissions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. How can attackers bypass file upload validation?&lt;/strong&gt;&lt;br&gt;
Ans: Attackers may rename malicious files to appear as safe formats (e.g., .php to .jpg) or manipulate MIME types. This is why multiple validation layers and secure storage practices are important.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. Does Laravel provide protection against malicious file execution?&lt;/strong&gt;&lt;br&gt;
Ans: Laravel helps reduce risk through validation and secure storage systems, but developers must still implement proper validation, file renaming, and server configuration to fully protect against malicious file execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Conclusion
&lt;/h2&gt;

&lt;p&gt;Secure file uploads are not a feature - they are a discipline. Every layer described in this article serves a specific purpose in a defense-in-depth strategy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validation catches malformed or unexpected files before they enter your system.&lt;/li&gt;
&lt;li&gt;Secure storage ensures that even if validation is bypassed, files cannot be executed.&lt;/li&gt;
&lt;li&gt;Virus scanning adds a safety net against sophisticated threats that evade other checks.&lt;/li&gt;
&lt;li&gt;Rate limiting, signed URLs, and HTTP security headers complete the picture.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Laravel provides all the building blocks you need. The responsibility lies with developers to connect them thoughtfully, test them rigorously, and stay current with evolving attack patterns.&lt;/p&gt;

&lt;p&gt;Security is never a one-time setup. Regularly update ClamAV virus definitions, review your validation rules when you add new file types, and audit your storage access logs. A breach avoided is invisible - and that invisibility is the mark of excellent security engineering.&lt;/p&gt;

&lt;p&gt;About the Author: &lt;em&gt;Abodh is a PHP and Laravel Developer at &lt;a href="https://www.addwebsolution.com/our-capabilities/laravel-development-agency" rel="noopener noreferrer"&gt;AddWeb Solution&lt;/a&gt;, skilled in MySQL, REST APIs, JavaScript, Git, and Docker for building robust web applications.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>laravelsecurity</category>
      <category>phpsecurity</category>
      <category>fileupload</category>
    </item>
    <item>
      <title>Clean Architecture in Laravel: Structuring Large-Scale Applications for Maintainability</title>
      <dc:creator>Zemichael Mehretu</dc:creator>
      <pubDate>Thu, 19 Mar 2026 10:30:42 +0000</pubDate>
      <link>https://dev.to/addwebsolutionpvtltd/clean-architecture-in-laravel-structuring-large-scale-applications-for-maintainability-4gho</link>
      <guid>https://dev.to/addwebsolutionpvtltd/clean-architecture-in-laravel-structuring-large-scale-applications-for-maintainability-4gho</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;“Good state management doesn’t make apps bigger, it makes complexity visible and manageable.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Clean Architecture protects business rules from framework, database, and third-party volatility.&lt;/li&gt;
&lt;li&gt;The dependency rule is the core guardrail: dependencies must point inward.&lt;/li&gt;
&lt;li&gt;Use cases should express business intent clearly, while controllers and adapters stay thin.&lt;/li&gt;
&lt;li&gt;Teams scale better when boundaries are explicit and responsibilities are narrow.&lt;/li&gt;
&lt;li&gt;Incremental migration beats big-bang rewrites for real production systems.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Index
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Why Large Laravel Systems Become Expensive to Change&lt;/li&gt;
&lt;li&gt;Clean Architecture in Practical Terms&lt;/li&gt;
&lt;li&gt;The Dependency Rule&lt;/li&gt;
&lt;li&gt;The Four Layers in Laravel&lt;/li&gt;
&lt;li&gt;Implementation Strategy for Teams&lt;/li&gt;
&lt;li&gt;Minimal Example: Create User&lt;/li&gt;
&lt;li&gt;Transactions, Events, and Integration Boundaries&lt;/li&gt;
&lt;li&gt;Testing Strategy&lt;/li&gt;
&lt;li&gt;Migration Roadmap for Existing Applications&lt;/li&gt;
&lt;li&gt;Common Mistakes and How to Avoid Them&lt;/li&gt;
&lt;li&gt;When to Use (and When to Keep It Simple)&lt;/li&gt;
&lt;li&gt;FAQs&lt;/li&gt;
&lt;li&gt;References&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;

&lt;p&gt;Laravel gives excellent delivery speed in the early stages of a product. The challenge appears later, when an application evolves into many modules, many integrations, and many contributors. At that point, development speed depends less on how fast new code is written and more on how safely existing code can be changed.&lt;/p&gt;

&lt;p&gt;Clean Architecture helps by separating stable business policy from volatile implementation details. It does not replace Laravel. It clarifies where business logic should live and where framework concerns should remain. With clear boundaries, teams can evolve API design, storage choices, queue systems, and external providers without repeatedly disturbing core business rules.&lt;/p&gt;

&lt;p&gt;For long-lived products, this separation lowers maintenance effort, improves testability, and reduces refactoring risk.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Why Large Laravel Systems Become Expensive to Change
&lt;/h2&gt;

&lt;p&gt;Most complexity in large Laravel apps comes from mixed responsibilities rather than from Laravel itself.&lt;/p&gt;

&lt;p&gt;Common signs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Controllers that validate input, apply domain rules, run persistence logic, and call external APIs in one action.&lt;/li&gt;
&lt;li&gt;Eloquent models that mix persistence with pricing logic, eligibility checks, and side effects.&lt;/li&gt;
&lt;li&gt;Repeated business rules spread across jobs, listeners, services, and controllers.&lt;/li&gt;
&lt;li&gt;Feature tests carrying too much burden because unit-level logic is hard to isolate.&lt;/li&gt;
&lt;li&gt;Frequent merge conflicts in central files touched by multiple teams.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This creates architecture debt. Each new feature takes longer because developers must navigate and protect too many implicit dependencies.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Simple code is not code with fewer files; it is code where responsibilities are obvious.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  3. Clean Architecture in Practical Terms
&lt;/h2&gt;

&lt;p&gt;Clean Architecture separates policy from detail.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Policy is business behavior that should remain stable over time.&lt;/li&gt;
&lt;li&gt;Detail is technology that can change (framework APIs, ORM models, vendors, transports).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Laravel context:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Domain contains business concepts and rules.&lt;/li&gt;
&lt;li&gt;Application coordinates business actions through use cases.&lt;/li&gt;
&lt;li&gt;Infrastructure implements contracts using Laravel and external services.&lt;/li&gt;
&lt;li&gt;Presentation handles inputs and outputs for HTTP or CLI.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This model supports a useful goal: when infrastructure changes, business rules should not need to change.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The Dependency Rule
&lt;/h2&gt;

&lt;p&gt;The dependency rule is simple and strict:&lt;/p&gt;

&lt;p&gt;Source code dependencies must point inward.&lt;/p&gt;

&lt;p&gt;Implications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Domain should not depend on Request, Response, Eloquent models, facades, or SDK clients.&lt;/li&gt;
&lt;li&gt;Application should depend on domain models and contracts, not Laravel storage or transport types.&lt;/li&gt;
&lt;li&gt;Infrastructure should implement application contracts and contain framework integrations.&lt;/li&gt;
&lt;li&gt;Presentation should call use cases and format responses, not own business policy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this rule is consistently enforced, architecture remains clean over time. If ignored, folder names may look clean while coupling silently grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The Four Layers in Laravel
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A) Domain Layer (Core Business)&lt;/strong&gt;&lt;br&gt;
Purpose: express business truth.&lt;br&gt;
Contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Entities and aggregates&lt;/li&gt;
&lt;li&gt;Value objects&lt;/li&gt;
&lt;li&gt;Domain services&lt;/li&gt;
&lt;li&gt;Domain events&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Framework-agnostic&lt;/li&gt;
&lt;li&gt;High cohesion&lt;/li&gt;
&lt;li&gt;Focused on invariants and business language&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;B) Application Layer (Use Cases)&lt;/strong&gt;&lt;br&gt;
Purpose: execute business capabilities.&lt;br&gt;
Contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use cases (single intent actions)&lt;/li&gt;
&lt;li&gt;Input and output DTOs&lt;/li&gt;
&lt;li&gt;Contracts for repositories and gateways&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Coordinates workflows&lt;/li&gt;
&lt;li&gt;Enforces application-level policies&lt;/li&gt;
&lt;li&gt;Keeps orchestration out of controllers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;C) Infrastructure Layer (Adapters)&lt;/strong&gt;&lt;br&gt;
Purpose: implement contracts using concrete tools.&lt;br&gt;
Contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Eloquent repository implementations&lt;/li&gt;
&lt;li&gt;External API adapters (payments, notifications, storage)&lt;/li&gt;
&lt;li&gt;Event dispatching and queue adapters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Framework-dependent&lt;/li&gt;
&lt;li&gt;Replaceable implementations&lt;/li&gt;
&lt;li&gt;Mapping between persistence and domain models&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;D) Presentation Layer (Delivery)&lt;/strong&gt;&lt;br&gt;
Purpose: handle transport concerns.&lt;br&gt;
Contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Controllers&lt;/li&gt;
&lt;li&gt;Form requests&lt;/li&gt;
&lt;li&gt;API resources/serializers&lt;/li&gt;
&lt;li&gt;Console commands&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Thin input/output handling&lt;/li&gt;
&lt;li&gt;Delegation to use cases&lt;/li&gt;
&lt;li&gt;Minimal business branching&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;“Clean Architecture is less about layers and more about protecting business decisions from framework churn.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  6. Implementation Strategy for Teams
&lt;/h2&gt;

&lt;p&gt;A clean structure is not enough. The operating strategy matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strategy 1: Target Volatile Business Flows First&lt;/strong&gt;&lt;br&gt;
Start where change is frequent: billing, ordering, pricing, eligibility, and fulfillment flows. These areas return architecture value quickly because requirement churn is high.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strategy 2: Migrate by Vertical Slice&lt;/strong&gt;&lt;br&gt;
Move one complete use case at a time from controller to use case to contract to adapter. This keeps migration deliverable and testable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strategy 3: Keep Contracts Intentional&lt;/strong&gt;&lt;br&gt;
Introduce interfaces at true boundaries: persistence, external APIs, messaging, storage. Avoid interfaces for classes that are unlikely to vary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strategy 4: Keep Controllers Policy-Free&lt;/strong&gt;&lt;br&gt;
A controller should parse/validate input, call one use case, and shape output. When controllers decide business outcomes, the architecture starts leaking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strategy 5: Make Model Mapping Explicit&lt;/strong&gt;&lt;br&gt;
Map between transport objects, persistence models, and domain objects in adapters. Explicit mapping preserves control and avoids hidden coupling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strategy 6: Put Transaction Scope in Application&lt;/strong&gt;&lt;br&gt;
Use cases should define transaction boundaries when multiple changes must succeed or fail together. Domain entities should not manage transaction mechanics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strategy 7: Enforce Guardrails in Code Review&lt;/strong&gt;&lt;br&gt;
Add review checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Any framework imports in Domain?&lt;/li&gt;
&lt;li&gt;Any Eloquent model usage in Use Cases?&lt;/li&gt;
&lt;li&gt;Any business rules in controllers/listeners?
Small governance habits prevent long-term drift.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Strategy 8: Keep Use Cases Focused&lt;/strong&gt;&lt;br&gt;
One use case should represent one business intent. If a use case grows broad, split by intent (ApproveRefund, RejectRefund, EscalateRefund).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strategy 9: Support Parallel Team Work&lt;/strong&gt;&lt;br&gt;
Structure folders by bounded context where needed (Sales, Billing, Identity). This reduces cross-team collisions and clarifies ownership.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strategy 10: Measure Architecture by Delivery Outcomes&lt;/strong&gt;&lt;br&gt;
Track lead time, change failure rate, rollback frequency, and escaped defects. Good architecture should improve these outcomes over time.&lt;/p&gt;
&lt;h2&gt;
  
  
  7. Minimal Example: Create User
&lt;/h2&gt;

&lt;p&gt;The goal is to show boundaries, not framework detail.&lt;br&gt;
&lt;strong&gt;Domain&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Application&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;UserRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;User&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateUser&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;UserRepository&lt;/span&gt; &lt;span class="nv"&gt;$repo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Infrastructure&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EloquentUserRepository&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;UserRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;User&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;UserModel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Presentation&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;CreateUser&lt;/span&gt; &lt;span class="nv"&gt;$useCase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$useCase&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The example stays intentionally small. In production, you would add value objects, validation rules, and error handling without changing layer direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Transactions, Events, and Integration Boundaries
&lt;/h2&gt;

&lt;p&gt;These areas often cause hidden coupling.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Transactions belong at the application use-case boundary, where business operations are coordinated.&lt;/li&gt;
&lt;li&gt;Domain events should describe business facts, not transport mechanisms.&lt;/li&gt;
&lt;li&gt;Event publishing and queue dispatch should happen in infrastructure adapters.&lt;/li&gt;
&lt;li&gt;Integration retries, circuit breaking, and API error mapping should remain outside the domain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A useful test: if infrastructure changes, domain behavior should remain intact.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“If changing the database rewrites business rules, your boundaries are in the wrong place.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  9. Testing Strategy
&lt;/h2&gt;

&lt;p&gt;A practical test distribution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Domain unit tests for entities, value objects, and invariants.&lt;/li&gt;
&lt;li&gt;Use case tests with mocked contracts.&lt;/li&gt;
&lt;li&gt;Infrastructure integration tests for DB and external adapters.&lt;/li&gt;
&lt;li&gt;Limited HTTP feature tests for request/response behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach keeps most business tests fast and deterministic while still validating real integration seams.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Migration Roadmap for Existing Applications
&lt;/h2&gt;

&lt;p&gt;Use an incremental path:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choose a high-change feature.&lt;/li&gt;
&lt;li&gt;Extract core rules into domain models.&lt;/li&gt;
&lt;li&gt;Introduce a use case for one business intent.&lt;/li&gt;
&lt;li&gt;Define contracts at application boundaries.&lt;/li&gt;
&lt;li&gt;Implement contracts in infrastructure with current Laravel stack.&lt;/li&gt;
&lt;li&gt;Route controllers to the new use case.&lt;/li&gt;
&lt;li&gt;Add domain and use-case tests.&lt;/li&gt;
&lt;li&gt;Repeat for adjacent flows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows continuous delivery while architecture improves in-place.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Common Mistakes and How to Avoid Them
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Architecture theater: renamed folders but no real boundary enforcement.&lt;/li&gt;
&lt;li&gt;Over-abstraction: too many interfaces before variation exists.&lt;/li&gt;
&lt;li&gt;Anemic domain: business logic pushed entirely into service classes.&lt;/li&gt;
&lt;li&gt;Leaky boundaries: Eloquent models traveling into domain/application code.&lt;/li&gt;
&lt;li&gt;Oversized use cases: broad classes handling unrelated intent.&lt;/li&gt;
&lt;li&gt;Weak ownership: no team agreement on where policy belongs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fix is discipline: clear boundaries, focused use cases, and review guardrails.&lt;/p&gt;

&lt;h2&gt;
  
  
  12. When to Use (and When to Keep It Simple)
&lt;/h2&gt;

&lt;p&gt;Use Clean Architecture when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the product has long lifespan,&lt;/li&gt;
&lt;li&gt;business logic is non-trivial,&lt;/li&gt;
&lt;li&gt;multiple teams need parallel delivery,&lt;/li&gt;
&lt;li&gt;external dependencies are significant.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep it lighter when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the app is short-lived,&lt;/li&gt;
&lt;li&gt;logic is mostly straightforward CRUD,&lt;/li&gt;
&lt;li&gt;team size is small and scope is stable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Architecture should match expected change, not theoretical perfection.&lt;/p&gt;

&lt;h2&gt;
  
  
  13. FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is Clean Architecture mandatory for Laravel projects?&lt;/strong&gt;&lt;br&gt;
No. It is a strategic choice for products where maintainability risk is high.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Will it slow us down?&lt;/strong&gt;&lt;br&gt;
There may be a short setup cost. In most long-lived systems, it reduces total delivery friction over time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where should validation happen?&lt;/strong&gt;&lt;br&gt;
Input validation belongs in presentation boundaries. Business invariants belong in domain/application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can this work without full DDD adoption?&lt;/strong&gt;&lt;br&gt;
Yes. You can apply clean boundaries incrementally and adopt DDD concepts where they add clear value.&lt;/p&gt;

&lt;h2&gt;
  
  
  14. References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Clean Architecture (Robert C. Martin): &lt;a href="https://www.pearson.com/en-us/subject-catalog/p/clean-architecture-a-craftsmans-guide-to-software-structure-and-design/P200000003268/9780134494166" rel="noopener noreferrer"&gt;https://www.pearson.com/en-us/subject-catalog/p/clean-architecture-a-craftsmans-guide-to-software-structure-and-design/P200000003268/9780134494166&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;DORA Research Program: &lt;a href="https://dora.dev" rel="noopener noreferrer"&gt;https://dora.dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Stripe Developer Coefficient: &lt;a href="https://stripe.com/reports/developer-coefficient" rel="noopener noreferrer"&gt;https://stripe.com/reports/developer-coefficient&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;ISBSG: &lt;a href="https://www.isbsg.org" rel="noopener noreferrer"&gt;https://www.isbsg.org&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  15. Conclusion
&lt;/h2&gt;

&lt;p&gt;Clean Architecture in Laravel is a practical way to keep business policy stable while technical details evolve. By enforcing inward dependencies, keeping use cases focused, and migrating incrementally, teams can preserve Laravel’s delivery speed and improve long-term maintainability at scale.&lt;/p&gt;

&lt;p&gt;About the Author:&lt;em&gt;Zemichael is a fullstackDeveloper at &lt;a href="https://www.addwebsolution.com/" rel="noopener noreferrer"&gt;AddWeb Solution&lt;/a&gt;.Crafting web experiences with robust architecture, performance focus, and design thinking.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>cleanarchitecture</category>
      <category>architecture</category>
      <category>techdebt</category>
    </item>
    <item>
      <title>Code Quality Checks and Deployment with GitHub Actions</title>
      <dc:creator>Rajan Vavadia</dc:creator>
      <pubDate>Wed, 18 Mar 2026 12:09:37 +0000</pubDate>
      <link>https://dev.to/addwebsolutionpvtltd/code-quality-checks-and-deployment-with-github-actions-3p5d</link>
      <guid>https://dev.to/addwebsolutionpvtltd/code-quality-checks-and-deployment-with-github-actions-3p5d</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;“The XP philosophy is to start where you are now and move towards the ideal. From where you are now, could you improve a little bit?” Kent Beck&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Overview: Two-Workflow Strategy (Quality Gate + Deploy)&lt;/li&gt;
&lt;li&gt;Workflow 1: Code Quality Checks (PR → stg)&lt;/li&gt;
&lt;li&gt;Workflow 2: Deploy (push → stg)&lt;/li&gt;
&lt;li&gt;Practical Setup (Step-by-step)&lt;/li&gt;
&lt;li&gt;FAQs&lt;/li&gt;
&lt;li&gt;Key Takeaways&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;

&lt;p&gt;This document explains a practical GitHub Actions setup that enforces Flutter code quality checks on every pull request to the stg branch and deploys the Flutter web build to AWS S3 whenever code is pushed to stg.&lt;/p&gt;

&lt;p&gt;The approach uses two separate workflows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code Quality Checks: runs on pull_request events (stg)&lt;/li&gt;
&lt;li&gt;Deploy (stg): runs on push events to stg&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Overview: Two-Workflow Strategy (Quality Gate + Deploy)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Developers open a Pull Request targeting stg.&lt;/li&gt;
&lt;li&gt;GitHub Actions runs Flutter clean → pub get → analyze → test.&lt;/li&gt;
&lt;li&gt;If checks pass, the PR is safe to merge. If checks fail, a Rocket.Chat alert is sent with the build log.&lt;/li&gt;
&lt;li&gt;After merge/push to stg, a separate Deploy workflow builds Flutter web and syncs build/web to an S3 bucket.&lt;/li&gt;
&lt;li&gt;Deploy success/failure is reported to Rocket.Chat.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why separate workflows?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PR checks run in the PR context and are ideal for gating merges.&lt;/li&gt;
&lt;li&gt;Deploy runs only on trusted branch pushes, avoiding accidental deployments from feature branches.&lt;/li&gt;
&lt;li&gt;Clearer troubleshooting: you can tell instantly whether a failure is quality-related or deployment-related.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Workflow 1: Code Quality Checks (PR → stg)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;3.1 What it does&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Triggers when a Pull Request targets branch stg.&lt;/li&gt;
&lt;li&gt;Ensures only one run per PR branch using concurrency (cancels older in-progress runs when a new commit is pushed).&lt;/li&gt;
&lt;li&gt;Runs Flutter quality steps and writes a readable log file (cq.log).&lt;/li&gt;
&lt;li&gt;Uploads cq.log as an artifact (kept for 7 days).&lt;/li&gt;
&lt;li&gt;Notifies Rocket.Chat on success or failure (failure attempts to upload cq.log to the room).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3.2 YAML: Code Quality Checks&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Code Quality Checks

on:
  pull_request:
    branches: [stg]

concurrency:
  group: code-quality-${{ github.head_ref }}
  cancel-in-progress: true

env:
  SITE_URL:      ${{ secrets.SITE_URL }}
  ENV_NAME:      ${{ secrets.ENV_NAME }}
  RC_BASE_URL:   ${{ secrets.RC_BASE_URL }}
  RC_ROOM_ID:    ${{ secrets.RC_ROOM_ID }}
  RC_USER_ID:    ${{ secrets.RC_USER_ID }}
  RC_AUTH_TOKEN:  ${{ secrets.RC_AUTH_TOKEN }}

jobs:
  code-quality:
    name: Flutter Analyze &amp;amp; Test
    runs-on: ubuntu-latest
    timeout-minutes: 30

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Flutter
        uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.29.2'

      - name: Run code quality checks
        id: quality
        run: |
          set -e
          LOG="cq.log"

          echo "════════════════════════════════════════════════════" &amp;gt; "$LOG"
          echo " Code Quality Checks Started"                        &amp;gt;&amp;gt; "$LOG"
          echo "═══════════════════════════════════════════════════════" &amp;gt;&amp;gt; "$LOG"

          echo ""                                                      &amp;gt;&amp;gt; "$LOG"
          echo " STEP 1: flutter clean"                              &amp;gt;&amp;gt; "$LOG"
          echo "───────────────────────────────────────────────────────" &amp;gt;&amp;gt; "$LOG"
          flutter clean &amp;gt;&amp;gt; "$LOG" 2&amp;gt;&amp;amp;1

          echo ""                                                      &amp;gt;&amp;gt; "$LOG"
          echo " STEP 2: flutter pub get"                            &amp;gt;&amp;gt; "$LOG"
          echo "───────────────────────────────────────────────────────" &amp;gt;&amp;gt; "$LOG"
          flutter pub get &amp;gt;&amp;gt; "$LOG" 2&amp;gt;&amp;amp;1

          echo ""                                                      &amp;gt;&amp;gt; "$LOG"
          echo " STEP 3: flutter analyze"                            &amp;gt;&amp;gt; "$LOG"
          echo "───────────────────────────────────────────────────────" &amp;gt;&amp;gt; "$LOG"
          flutter analyze &amp;gt;&amp;gt; "$LOG" 2&amp;gt;&amp;amp;1

          echo ""                                                      &amp;gt;&amp;gt; "$LOG"
          echo " STEP 4: flutter test"                               &amp;gt;&amp;gt; "$LOG"
          echo "───────────────────────────────────────────────────────" &amp;gt;&amp;gt; "$LOG"
          flutter test &amp;gt;&amp;gt; "$LOG" 2&amp;gt;&amp;amp;1

          echo ""                                                      &amp;gt;&amp;gt; "$LOG"
          echo "═══════════════════════════════════════════════════════" &amp;gt;&amp;gt; "$LOG"
          echo "All checks passed!"                                  &amp;gt;&amp;gt; "$LOG"
          echo "═══════════════════════════════════════════════════════" &amp;gt;&amp;gt; "$LOG"

      - name: Upload build log
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: code-quality-log
          path: cq.log
          retention-days: 7

      - name: Notify Rocket.Chat (success)
        if: success()
        run: |
          REPO_URL="${{ github.event.repository.html_url }}"
          COMMIT_URL="${REPO_URL}/commit/${{ github.sha }}"
          AUTHOR="${{ github.event.pull_request.user.login }}"
          DURATION="${{ env.DURATION }}"

          MSG_TEXT="*Code Quality Checks Passed*

          *Site-URL:* ${SITE_URL}
          *PR Title:* ${{ github.event.pull_request.title }}
          *PR URL:* ${{ github.event.pull_request.html_url }}
          *Environment:* ${ENV_NAME}

          *on PR:* ${{ github.head_ref }} → ${{ github.base_ref }}
          *Git Commit:* ${COMMIT_URL}
          *Git Author:* ${AUTHOR}"
          jq -n \
            --arg roomId "${RC_ROOM_ID}" \
            --arg text "$MSG_TEXT" \
            '{roomId: $roomId, text: $text}' | \
          curl -sS -X POST "${RC_BASE_URL}/api/v1/chat.postMessage" \
            -H "X-User-Id: ${RC_USER_ID}" \
            -H "X-Auth-Token: ${RC_AUTH_TOKEN}" \
            -H "Content-Type: application/json" \
            -d @- || echo "Notification failed, continuing"

      - name: Notify Rocket.Chat (failure)
        if: failure()
        run: |
          REPO_URL="${{ github.event.repository.html_url }}"
          COMMIT_URL="${REPO_URL}/commit/${{ github.sha }}"
          AUTHOR="${{ github.event.pull_request.user.login }}"

          MSG_TEXT="*Code Quality Checks Failed*

          *Site-URL:* ${SITE_URL}
          *PR Title:* ${{ github.event.pull_request.title }}
          *PR URL:* ${{ github.event.pull_request.html_url }}
          *Environment:* ${ENV_NAME}

          *on PR:* ${{ github.head_ref }} → ${{ github.base_ref }}
          *Git Commit:* ${COMMIT_URL}
          *Git Author:* ${AUTHOR}

           *Full build log attached below:*"

          UPLOAD_RESP=$(curl -sS -X POST "${RC_BASE_URL}/api/v1/rooms.media/${RC_ROOM_ID}" \
            -H "X-User-Id: ${RC_USER_ID}" \
            -H "X-Auth-Token: ${RC_AUTH_TOKEN}" \
            -F "file=@cq.log") || true

          FILE_URL=$(echo "$UPLOAD_RESP" | jq -r '.file.url // empty')

          if [ -n "$FILE_URL" ]; then
            jq -n \
              --arg rid "${RC_ROOM_ID}" \
              --arg msg "$MSG_TEXT" \
              --arg title "📄 BUILD LOG (cq.log) - Click to download" \
              --arg link "$FILE_URL" \
              --arg desc "Contains detailed output of all build steps" \
              '{message: {rid: $rid, msg: $msg, attachments: [{title: $title, title_link: $link, text: $desc, collapsed: false}]}}' | \
            curl -sS -X POST "${RC_BASE_URL}/api/v1/chat.sendMessage" \
              -H "X-User-Id: ${RC_USER_ID}" \
              -H "X-Auth-Token: ${RC_AUTH_TOKEN}" \
              -H "Content-Type: application/json" \
              -d @-
          else
            jq -n \
              --arg roomId "${RC_ROOM_ID}" \
              --arg text "$MSG_TEXT" \
              '{roomId: $roomId, text: $text}' | \
            curl -sS -X POST "${RC_BASE_URL}/api/v1/chat.postMessage" \
              -H "X-User-Id: ${RC_USER_ID}" \
              -H "X-Auth-Token: ${RC_AUTH_TOKEN}" \
              -H "Content-Type: application/json" \
              -d @-
          fi || echo "Failure notification failed"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3.3 How logs + artifacts work&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The workflow writes all step output to cq.log so your team has one clean, shareable log file.&lt;/li&gt;
&lt;li&gt;Upload build log runs with if: always(), so the artifact is saved even when analyze/test fails.&lt;/li&gt;
&lt;li&gt;retention-days: 7 keeps the artifact for one week to reduce storage and keep logs relevant.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3.4 Rocket.Chat notifications (success/failure)&lt;/strong&gt;&lt;br&gt;
Two messages are sent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Success: posts a summary (PR title, PR URL, environment, branch and commit details).&lt;/li&gt;
&lt;li&gt;Failure: tries to upload cq.log to the room, then posts a message with a link to the uploaded file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If uploading fails, it still posts the failure summary so you are notified.&lt;br&gt;
Prerequisites on the runner:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;jq must be available (it is available by default on ubuntu-latest runners).&lt;/li&gt;
&lt;li&gt;Rocket.Chat API base URL, user id, auth token, and room id must be configured as secrets.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  4. Workflow 2: Deploy (push → stg)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;4.1 What it does&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Triggers on push to stg (typically after PR merge).&lt;/li&gt;
&lt;li&gt;Builds Flutter web (build/web output).&lt;/li&gt;
&lt;li&gt;Configures AWS credentials on the runner.&lt;/li&gt;
&lt;li&gt;Syncs build/web to S3 bucket using aws s3 sync --delete.&lt;/li&gt;
&lt;li&gt;Notifies Rocket.Chat on success/failure withcommit  and author details.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4.2 YAML: Deploy (stg)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Deploy (stg)

on:
  push:
    branches: [stg]

concurrency:
  group: deploy-stg
  cancel-in-progress: false

permissions:
  contents: read

env:
  SITE_URL:      ${{ secrets.SITE_URL }}
  ENV_NAME:      ${{ secrets.ENV_NAME }}
  RC_BASE_URL:   ${{ secrets.RC_BASE_URL }}
  RC_ROOM_ID:    ${{ secrets.RC_ROOM_ID }}
  RC_USER_ID:    ${{ secrets.RC_USER_ID }}
  RC_AUTH_TOKEN:  ${{ secrets.RC_AUTH_TOKEN }}

jobs:
  build-and-deploy:
    name: Build &amp;amp; Deploy to AWS S3
    runs-on: ubuntu-latest
    timeout-minutes: 30

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Flutter
        uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.29.2'

      - name: Flutter clean
        run: flutter clean

      - name: Flutter build web
        run: |
          flutter build web \
            --dart-define=FLUTTER_WEB_USE_SKIA=false \
            --pwa-strategy=none

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ secrets.AWS_REGION }}

      - name: Deploy to S3
        run: |
          aws s3 sync build/web s3://${{ secrets.AWS_S3_BUCKET }} \
            --delete

      - name: Notify Rocket.Chat (success)
        if: success()
        run: |
          REPO_URL="${{ github.event.repository.html_url }}"
          COMMIT_URL="${REPO_URL}/commit/${{ github.sha }}"
          AUTHOR=$(git log -1 --pretty=%an)

          MSG_TEXT="*Deployment Successful*

          *Site-URL:* ${SITE_URL}
          *Environment:* ${ENV_NAME}

          *Branch:* ${{ github.ref_name }}
          *Git Commit:* ${COMMIT_URL}
          *Git Author:* ${AUTHOR}"

          jq -n \
            --arg roomId "${RC_ROOM_ID}" \
            --arg text "$MSG_TEXT" \
            '{roomId: $roomId, text: $text}' | \
          curl -sS -X POST "${RC_BASE_URL}/api/v1/chat.postMessage" \
            -H "X-User-Id: ${RC_USER_ID}" \
            -H "X-Auth-Token: ${RC_AUTH_TOKEN}" \
            -H "Content-Type: application/json" \
            -d @- || echo "Deployment notification failed"

      - name: Notify Rocket.Chat (failure)
        if: failure()
        run: |
          REPO_URL="${{ github.event.repository.html_url }}"
          COMMIT_URL="${REPO_URL}/commit/${{ github.sha }}"
          AUTHOR=$(git log -1 --pretty=%an)

          MSG_TEXT="*Deployment Failed*

          *Site-URL:* ${SITE_URL}
          *Environment:* ${ENV_NAME}

          *Branch:* ${{ github.ref_name }}
          *Git Commit:* ${COMMIT_URL}
          *Git Author:* ${AUTHOR}"

          jq -n \
            --arg roomId "${RC_ROOM_ID}" \
            --arg text "$MSG_TEXT" \
            '{roomId: $roomId, text: $text}' | \
          curl -sS -X POST "${RC_BASE_URL}/api/v1/chat.postMessage" \
            -H "X-User-Id: ${RC_USER_ID}" \
            -H "X-Auth-Token: ${RC_AUTH_TOKEN}" \
            -H "Content-Type: application/json" \
            -d @- || echo "Deployment failure notification failed"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;“Beware of bugs in the above code; I have only proved it correct, not tried it.” Donald Knuth&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;4.3 AWS S3 deployment notes&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;S3 bucket must exist and the AWS credentials must have permission to sync to it.&lt;/li&gt;
&lt;li&gt;aws s3 sync --delete removes files in S3 that no longer exist in build/web (prevents stale assets).&lt;/li&gt;
&lt;li&gt;If you use CloudFront, consider adding an invalidation step (optional) after sync.&lt;/li&gt;
&lt;li&gt;If your site is a Single Page App, configure S3/CloudFront to route 404s to index.html.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Practical Setup (Step-by-step)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;5.1 Create workflow files&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In your repo, create folder: .github/workflows/&lt;/li&gt;
&lt;li&gt;Add file: code-quality.yml (paste Workflow 1 YAML).&lt;/li&gt;
&lt;li&gt;Add file: deploy.yml (paste Workflow 2 YAML).&lt;/li&gt;
&lt;li&gt;Commit and push to GitHub.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;5.2 Configure GitHub secrets&lt;/strong&gt;&lt;br&gt;
Go to: Repository → Settings → Secrets and variables → Actions → New repository secret&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SITE_URL (example: &lt;a href="https://stg.example.com" rel="noopener noreferrer"&gt;https://stg.example.com&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;ENV_NAME (example: stg)&lt;/li&gt;
&lt;li&gt;RC_BASE_URL (example: &lt;a href="https://chat.example.com" rel="noopener noreferrer"&gt;https://chat.example.com&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;RC_ROOM_ID (Rocket.Chat room id)&lt;/li&gt;
&lt;li&gt;RC_USER_ID (Rocket.Chat API user id)&lt;/li&gt;
&lt;li&gt;RC_AUTH_TOKEN (Rocket.Chat API token)&lt;/li&gt;
&lt;li&gt;AWS_ACCESS_KEY_ID&lt;/li&gt;
&lt;li&gt;AWS_SECRET_ACCESS_KEY&lt;/li&gt;
&lt;li&gt;AWS_REGION (example: ap-south-1)&lt;/li&gt;
&lt;li&gt;AWS_S3_BUCKET (bucket name only, without s3://)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;5.3 Set up branch protection (recommended)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In GitHub: Settings → Branches → Add branch protection rule for stg.&lt;/li&gt;
&lt;li&gt;Enable 'Require a pull request before merging'.&lt;/li&gt;
&lt;li&gt;Enable 'Require status checks to pass before merging'.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select the workflow check name that appears for the PR (Flutter Analyze &amp;amp; Test / Code Quality Checks).&lt;br&gt;
&lt;strong&gt;5.4 Verify end-to-end flow&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a feature branch and open a PR to stg.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Confirm Code Quality Checks workflow starts automatically.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If it passes, merge the PR to stg.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Confirm Deploy (stg) runs on the push event and uploads new web build to S3.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Check Rocket.Chat channel for the success messages.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;5.5 Common improvements (optional but practical)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add caching (Flutter pub cache) to speed up builds.&lt;/li&gt;
&lt;li&gt;Run flutter format --set-exit-if-changed and/or dart analyze for stricter linting.&lt;/li&gt;
&lt;li&gt;Use AWS OIDC instead of long-lived AWS keys (more secure) if your org supports it.&lt;/li&gt;
&lt;li&gt;Add environment protection for stg deployments (manual approval for deploy job).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Practical Demonstration (Images Explained)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Modify the README file on the feature branch.&lt;br&gt;
In this demonstration, the README.md file was updated with the text "Addwebsolution" and the changes were committed and pushed to the feature-dev branch.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Create a Pull Request from feature-dev to the stg Branch&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Once the Pull Request is created, the Code Quality Checks workflow is automatically triggered.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Upon successful completion of the GitHub Actions pipeline, a Rocket.Chat notification is dispatched to the configured channel.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The screenshot below shows the Pull Request in a mergeable state the Code Quality status check has passed (indicated by the green checkmark), and the "Merge pull request" button is now enabled.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Click the "Confirm Merge" button to merge the Pull Request into stg. This push event automatically triggers the Deploy workflow.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;The Deploy (stg) pipeline is automatically triggered and the deployment job begins execution&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;The deployment pipeline has completed successfully the Flutter web build artifacts have been synced to the configured AWS S3 bucket.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;A Rocket.Chat notification confirms the successful deployment, including details such as site URL, environment name, branch, commit hash, and author.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;Code Quality and Deployment Pipeline Failure Scenario&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4:&lt;/strong&gt; Introduce a deliberate syntax error in main.dart (e.g., a missing closing parenthesis) and push the changes to the feature branch.&lt;/p&gt;

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

&lt;p&gt;A new Pull Request is created targeting the stg branch, which automatically triggers the Code Quality Checks workflow.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Step 5:&lt;/strong&gt; The Code Quality Checks workflow fails due to the syntax error for example, print("hello" is missing its closing parenthesis. The flutter analyze step detects the issue and the pipeline exits with a non-zero status code.&lt;/p&gt;

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

&lt;p&gt;A failure notification is sent to Rocket.Chat with the build log attached. Since the Code Quality status check has failed, the Pull Request cannot be merged, and the Deploy workflow is never triggered effectively preventing broken code from reaching the stg environment.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;This practical demonstration validates the end-to-end CI/CD pipeline behavior across both success and failure scenarios. When code quality checks pass, the Pull Request is merged into the stg branch and the Deploy workflow automatically builds and publishes the Flutter web application to AWS S3, with a success notification delivered to Rocket.Chat. Conversely, when a syntax error or lint violation is detected, the Code Quality Checks workflow fails, the Pull Request is blocked from merging, and the Deploy workflow is never invoked ensuring that only validated, production-ready code reaches the staging environment. This two-workflow strategy enforces a reliable quality gate at the PR level while keeping deployment isolated to trusted branch pushes.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;“Continuous Integration is not a tool. It is a practice.” Common CI principle (team reminder)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  8. FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q1. Why does Code Quality run on pull_request but Deploy runs on push?&lt;/strong&gt;&lt;br&gt;
A. pull_request is best for quality gates before merging, while push to stg is best for deployments because it limits deploys to the stg branch history (usually protected).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q2. What does concurrency do in Code Quality Checks?&lt;/strong&gt;&lt;br&gt;
A. It groups runs by the PR branch name (github.head_ref). If a new commit is pushed to the same PR branch, the older run is canceled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q3. Why upload cq.log if GitHub Actions already has logs?&lt;/strong&gt;&lt;br&gt;
A. cq.log is a single consolidated file you can share, attach to chats, and keep as an artifact. It also makes Rocket.Chat upload easy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q4. What if Rocket.Chat notification fails?&lt;/strong&gt;&lt;br&gt;
A. The workflow prints a warning and continues. Your CI status still reflects pass/fail correctly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q5. What permissions are needed for S3 deploy?&lt;/strong&gt;&lt;br&gt;
A. The AWS identity used in GitHub Actions needs at minimum s3:ListBucket and s3:PutObject/DeleteObject permissions for the target bucket.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q6. How do I confirm the build is deployed correctly?&lt;/strong&gt;&lt;br&gt;
A. Check the S3 bucket objects update time and open SITE_URL in browser. If CloudFront is used, confirm cache invalidation or TTL behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;PR quality checks prevent broken code from entering stg.&lt;/li&gt;
&lt;li&gt;Concurrency prevents duplicate CI runs and saves time.&lt;/li&gt;
&lt;li&gt;Artifacts (cq.log) make debugging faster and help the team collaborate.&lt;/li&gt;
&lt;li&gt;Deploy is isolated to stg pushes, which is safer and easier to audit.&lt;/li&gt;
&lt;li&gt;Rocket.Chat alerts keep the team informed without opening GitHub every time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  10. Conclusion
&lt;/h2&gt;

&lt;p&gt;This setup provides a clean CI/CD workflow for a Flutter web project: enforce quality on pull requests and deploy only after stg updates. With proper secrets, branch protections, and clear notifications, your team gets fast feedback and reliable deployments.&lt;/p&gt;

&lt;p&gt;About the Author:&lt;em&gt;Rajan is a DevOps Engineer at &lt;a href="https://www.addwebsolution.com/" rel="noopener noreferrer"&gt;AddWebSolution,&lt;/a&gt; specializing in automation infrastructure, Optimize the CI/CD Pipelines and ensuring seamless deployments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cicd</category>
      <category>codequality</category>
      <category>automation</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>Run Your Own ChatGPT Offline: Open WebUI + Ollama + Local Knowledge Base</title>
      <dc:creator>Ankit Parmar</dc:creator>
      <pubDate>Mon, 16 Mar 2026 07:49:26 +0000</pubDate>
      <link>https://dev.to/addwebsolutionpvtltd/run-your-own-chatgpt-offline-open-webui-ollama-local-knowledge-base-467g</link>
      <guid>https://dev.to/addwebsolutionpvtltd/run-your-own-chatgpt-offline-open-webui-ollama-local-knowledge-base-467g</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;“The future of AI will be hybrid: local intelligence with cloud augmentation.” - Satya Nadella&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When it comes to running AI assistants privately and offline, you have two main paths: use cloud-hosted LLM APIs like OpenAI or Anthropic, or run models locally on your own machine. Both approaches are valid, but in this article, we’ll focus on building a fully local AI assistant using Open WebUI and Ollama.&lt;/p&gt;

&lt;p&gt;Why Local AI?&lt;/p&gt;

&lt;p&gt;Because it gives you full privacy, offline access, zero API cost per token, and control over your models and data - especially important for sensitive documents or internal knowledge bases.&lt;/p&gt;

&lt;p&gt;As a practical example, we’ll build a local assistant that can answer questions from your own documents (PDFs, notes, markdown files) using a local RAG (Retrieval-Augmented Generation) pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Open WebUI?
&lt;/h2&gt;

&lt;p&gt;Open WebUI is a self-hosted web interface for local LLMs.&lt;br&gt;
It provides a ChatGPT-like experience in your browser while running models entirely on your own hardware via Ollama.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key capabilities:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chat with local LLMs&lt;/li&gt;
&lt;li&gt;Upload and query documents&lt;/li&gt;
&lt;li&gt;Multi-model switching&lt;/li&gt;
&lt;li&gt;Local RAG knowledge base&lt;/li&gt;
&lt;li&gt;User accounts &amp;amp; roles&lt;/li&gt;
&lt;li&gt;Tool and plugin support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of it as:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ChatGPT UI + Local Models + Private Knowledge&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Ollama vs Cloud LLM APIs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Cloud APIs (OpenAI, Claude, etc.)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Require internet access&lt;/li&gt;
&lt;li&gt;Pay per token&lt;/li&gt;
&lt;li&gt;Data leaves your environment&lt;/li&gt;
&lt;li&gt;Highest model quality&lt;/li&gt;
&lt;li&gt;No hardware requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Ollama Local Models&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run fully offline&lt;/li&gt;
&lt;li&gt;No per-token cost&lt;/li&gt;
&lt;li&gt;Private data stays local&lt;/li&gt;
&lt;li&gt;Hardware dependent&lt;/li&gt;
&lt;li&gt;Slightly lower model quality
In this article, we’ll use Ollama for fully local inference.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;“Privacy is not an option, and it shouldn’t be the price we accept for just getting on the Internet.” - Gary Kovacs&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Index
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Set Up Ollama&lt;/li&gt;
&lt;li&gt;Install Open WebUI&lt;/li&gt;
&lt;li&gt;Download Local LLM&lt;/li&gt;
&lt;li&gt;Create Local Knowledge Base&lt;/li&gt;
&lt;li&gt;Upload Documents&lt;/li&gt;
&lt;li&gt;Ask Questions Over Your Data&lt;/li&gt;
&lt;li&gt;How Local RAG Works&lt;/li&gt;
&lt;li&gt;Why Local AI Makes Sense&lt;/li&gt;
&lt;li&gt;Next Steps You Can Take&lt;/li&gt;
&lt;li&gt;Watch Out For&lt;/li&gt;
&lt;li&gt;Interesting Facts&lt;/li&gt;
&lt;li&gt;FAQ&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Building Your Offline ChatGPT: Open WebUI + Ollama&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Set Up Ollama
&lt;/h2&gt;

&lt;p&gt;Install Ollama:&lt;br&gt;
Mac / Linux:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://ollama.com/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Windows:&lt;br&gt;
Download installer from &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;https://ollama.com&lt;/a&gt;&lt;br&gt;
Verify installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start Ollama service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ollama runs a local model server at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:11434
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Install Open WebUI
&lt;/h2&gt;

&lt;p&gt;The easiest method is Docker.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;docker run -d \
  -p 3000:8080 \
  -v open-webui:/app/backend/data \
  --name open-webui \
  ghcr.io/open-webui/open-webui:main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open your browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create your admin account on first launch.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Download a Local LLM
&lt;/h2&gt;

&lt;p&gt;Pull a model via Ollama:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull llama3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Other good options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;mistral&lt;/li&gt;
&lt;li&gt;qwen2.5&lt;/li&gt;
&lt;li&gt;phi3&lt;/li&gt;
&lt;li&gt;codellama
Check installed models:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open WebUI will automatically detect Ollama models.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Create a Local Knowledge Base (RAG)
&lt;/h2&gt;

&lt;p&gt;Open WebUI includes built-in Retrieval-Augmented Generation.&lt;br&gt;
Steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to Workspace → Knowledge&lt;/li&gt;
&lt;li&gt;Create new knowledge base&lt;/li&gt;
&lt;li&gt;Upload documents:

&lt;ul&gt;
&lt;li&gt;PDFs&lt;/li&gt;
&lt;li&gt;TXT&lt;/li&gt;
&lt;li&gt;Markdown&lt;/li&gt;
&lt;li&gt;DOCX&lt;/li&gt;
&lt;li&gt;HTML&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The system automatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Splits text into chunks&lt;/li&gt;
&lt;li&gt;Generates embeddings&lt;/li&gt;
&lt;li&gt;Stores vectors locally&lt;/li&gt;
&lt;li&gt;Links to your LLM
No external vector DB required.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Upload Documents
&lt;/h2&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Company docs&lt;/li&gt;
&lt;li&gt;Personal notes&lt;/li&gt;
&lt;li&gt;Research papers&lt;/li&gt;
&lt;li&gt;Codebase&lt;/li&gt;
&lt;li&gt;Product manuals&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once uploaded, documents become searchable context for the model.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Data gravity will pull AI to where the data lives.” - Dave McCrory&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  6. Ask Questions Over Your Data
&lt;/h2&gt;

&lt;p&gt;Now chat normally:&lt;br&gt;
  “Summarize our API documentation”&lt;br&gt;
  “What does the onboarding process require?”&lt;br&gt;
  “Explain section 4 of the PDF”&lt;br&gt;
Open WebUI retrieves relevant chunks and sends them to the model.&lt;/p&gt;

&lt;p&gt;This is local RAG in action.&lt;/p&gt;

&lt;h2&gt;
  
  
  7.How Local RAG Works
&lt;/h2&gt;

&lt;p&gt;Pipeline:&lt;br&gt;
User question&lt;br&gt;
→ Embed query&lt;br&gt;
→ Search local vectors&lt;br&gt;
→ Retrieve relevant chunks&lt;br&gt;
→ Send to LLM with context&lt;br&gt;
→ Generate answer&lt;br&gt;
Everything runs locally.&lt;br&gt;
No cloud.&lt;br&gt;
No API.&lt;/p&gt;

&lt;h2&gt;
  
  
  8.Why Local AI Makes Sense
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Full privacy - data never leaves your machine&lt;/li&gt;
&lt;li&gt;Zero API costs after setup&lt;/li&gt;
&lt;li&gt;Offline availability&lt;/li&gt;
&lt;li&gt;Custom models &amp;amp; prompts&lt;/li&gt;
&lt;li&gt;Internal knowledge assistants&lt;/li&gt;
&lt;li&gt;Regulatory compliance friendly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Perfect for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Companies&lt;/li&gt;
&lt;li&gt;Developers&lt;/li&gt;
&lt;li&gt;Researchers&lt;/li&gt;
&lt;li&gt;Students&lt;/li&gt;
&lt;li&gt;Privacy-focused users&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;“Open models accelerate innovation by removing access barriers.” - Yann LeCun&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  9.Next Steps You Can Take
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Connect multiple models (coding + chat)&lt;/li&gt;
&lt;li&gt;Use larger models with GPU&lt;/li&gt;
&lt;li&gt;Share internal AI assistant in LAN&lt;/li&gt;
&lt;li&gt;Index full code repositories&lt;/li&gt;
&lt;li&gt;Build private ChatGPT for your team&lt;/li&gt;
&lt;li&gt;Add tools and function calling&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  10.Watch Out For
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Hardware limits: large models need RAM/GPU&lt;/li&gt;
&lt;li&gt;Embedding size: big docs consume storage&lt;/li&gt;
&lt;li&gt;Model quality: smaller local models less capable&lt;/li&gt;
&lt;li&gt;Chunking errors: bad splits reduce accuracy&lt;/li&gt;
&lt;li&gt;Context limits: local models have smaller windows&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  11.Interesting Facts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Local LLM inference costs can drop 90–99% compared to API usage after hardware amortization. &lt;a href="https://arxiv.org/abs/2601.09527" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2601.09527&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;Most enterprise AI deployments in regulated sectors prefer on-prem or private-cloud LLMs for compliance. &lt;a href="https://www.sitepoint.com/local-llms-vs-cloud-api-cost-analysis-2026" rel="noopener noreferrer"&gt;https://www.sitepoint.com/local-llms-vs-cloud-api-cost-analysis-2026&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;RAG systems reduce hallucinations by grounding models in real documents.&lt;a href="https://www.preprints.org/manuscript/202504.1236/v1" rel="noopener noreferrer"&gt;https://www.preprints.org/manuscript/202504.1236/v1&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;Open-source LLMs have improved &amp;gt;10× in benchmark scores since 2023.  &lt;a href="https://localaimaster.com/blog/best-open-source-llms-2026" rel="noopener noreferrer"&gt;https://localaimaster.com/blog/best-open-source-llms-2026&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  12.FAQ
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Do I need a GPU to run Open WebUI + Ollama?&lt;br&gt;
No. Small models run on CPU. GPU improves speed and allows larger models.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Is everything really offline?&lt;br&gt;
Yes. Models, embeddings, and documents stay local unless you enable external APIs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What models work best locally?&lt;br&gt;
Mistral, Llama 3, Qwen, and Phi are strong general-purpose local models.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Can I use my own documents?&lt;br&gt;
Yes. Upload PDFs, text, markdown, or code to the knowledge base.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Is this secure for company data?&lt;br&gt;
Yes. Nothing leaves your infrastructure if hosted locally.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How big can my knowledge base be?&lt;br&gt;
Limited by disk space and embeddings storage. Many GB is fine.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Can multiple users access it?&lt;br&gt;
Yes. Open WebUI supports accounts and roles.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  13.Conclusion
&lt;/h2&gt;

&lt;p&gt;Running your own offline ChatGPT-style assistant is now practical with Open WebUI and Ollama. You get privacy, control, and zero per-token cost while still enabling powerful AI search over your own knowledge.&lt;/p&gt;

&lt;p&gt;For individuals, it’s a personal AI brain.&lt;br&gt;
For teams, it’s a private knowledge assistant.&lt;br&gt;
For companies, it’s compliant AI infrastructure.&lt;/p&gt;

&lt;p&gt;Local AI isn’t replacing cloud models - it’s becoming the private layer that sits beside them.&lt;/p&gt;

&lt;p&gt;About the Author:&lt;em&gt;Ankit is a full-stack developer at &lt;a href="https://www.addwebsolution.com/" rel="noopener noreferrer"&gt;AddWebSolution&lt;/a&gt; and AI enthusiast who crafts intelligent web solutions with PHP, Laravel, and modern frontend tools.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ollama</category>
      <category>localai</category>
      <category>openwebui</category>
      <category>rag</category>
    </item>
    <item>
      <title>Server-Side Rendering with Laravel and React (Using Inertia.js)</title>
      <dc:creator>Ankit Parmar</dc:creator>
      <pubDate>Thu, 12 Mar 2026 08:15:57 +0000</pubDate>
      <link>https://dev.to/addwebsolutionpvtltd/server-side-rendering-with-laravel-and-react-using-inertiajs-1i72</link>
      <guid>https://dev.to/addwebsolutionpvtltd/server-side-rendering-with-laravel-and-react-using-inertiajs-1i72</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;“Make the simple case fast.” - Alan Kay&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Modern web applications are expected to be fast, SEO-friendly, and interactive. Traditionally, teams achieved this by splitting responsibilities: Laravel for the backend, React for the frontend, and a lot of glue code in between.&lt;/p&gt;

&lt;p&gt;That split works but it comes with cost:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Duplicate routing logic&lt;/li&gt;
&lt;li&gt;API maintenance overhead&lt;/li&gt;
&lt;li&gt;Auth and state synchronization issues&lt;/li&gt;
&lt;li&gt;SEO and performance compromises
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Inertia.js changes this equation.&lt;/strong&gt;&lt;br&gt;
In this article, we’ll explore how to implement Server-Side Rendering (SSR) with Laravel and React using Inertia.js, explain how it works internally, and understand when this approach makes sense and when it doesn’t.&lt;br&gt;
We’ll focus on architecture, flow, and usage, not UI boilerplate.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Server-Side Rendering Still Matters
&lt;/h2&gt;

&lt;p&gt;Client-side rendering alone has real drawbacks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slower first contentful paint&lt;/li&gt;
&lt;li&gt;Poor SEO without extra tooling&lt;/li&gt;
&lt;li&gt;JS-heavy initial loads&lt;/li&gt;
&lt;li&gt;Bad experience on slow networks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Server-side rendering solves this by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rendering the initial page on the server&lt;/li&gt;
&lt;li&gt;Sending ready-to-display HTML to the browser&lt;/li&gt;
&lt;li&gt;Hydrating React on the client afterward&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;SSR gives you speed + SEO + UX without sacrificing interactivity.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Laravel + React with Inertia.js?
&lt;/h2&gt;

&lt;p&gt;Inertia.js sits in a unique middle ground:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Not a traditional SPA&lt;/li&gt;
&lt;li&gt;Not a classic Blade-only app&lt;/li&gt;
&lt;li&gt;A modern monolith with React-driven views&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What Inertia does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keeps routing and controllers on the server (Laravel)&lt;/li&gt;
&lt;li&gt;Uses React for rendering pages&lt;/li&gt;
&lt;li&gt;Eliminates the need for a separate API layer&lt;/li&gt;
&lt;li&gt;Passes data as props, not JSON endpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You write &lt;strong&gt;server-driven apps with client-side UX.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Inertia.js enables SPA-like experiences using Laravel and React without building or maintaining a separate API layer.&lt;/li&gt;
&lt;li&gt;Server-Side Rendering (SSR) improves first-page load performance, SEO, and Core Web Vitals for content-heavy applications.&lt;/li&gt;
&lt;li&gt;Laravel remains the single source of truth for routing, validation, and authorization.&lt;/li&gt;
&lt;li&gt;React components are used as page views, not independent frontend routes.&lt;/li&gt;
&lt;li&gt;Inertia eliminates common SPA pain points such as API versioning, duplicated logic, and excessive frontend boilerplate.&lt;/li&gt;
&lt;li&gt;SSR with Inertia works best for dashboards, admin panels, SaaS apps, and content-driven platforms.&lt;/li&gt;
&lt;li&gt;This architecture scales well while keeping the codebase simpler and easier to maintain than traditional SPAs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Index
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;What Is Inertia.js?&lt;/li&gt;
&lt;li&gt;How SSR Works with Laravel and React&lt;/li&gt;
&lt;li&gt;Architecture Overview&lt;/li&gt;
&lt;li&gt;Setting Up Laravel + Inertia (Conceptual)&lt;/li&gt;
&lt;li&gt;Server-Side Rendering Flow&lt;/li&gt;
&lt;li&gt;Data Sharing and Props&lt;/li&gt;
&lt;li&gt;Routing and Navigation&lt;/li&gt;
&lt;li&gt;Handling SEO and Meta Data&lt;/li&gt;
&lt;li&gt;Authentication and Sessions&lt;/li&gt;
&lt;li&gt;How the Frontend Uses the Backend&lt;/li&gt;
&lt;li&gt;Why This Architecture Makes Sense&lt;/li&gt;
&lt;li&gt;Watch Out For&lt;/li&gt;
&lt;li&gt;Next Steps You Can Take&lt;/li&gt;
&lt;li&gt;Interesting Facts&lt;/li&gt;
&lt;li&gt;FAQ&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;

&lt;p&gt;Building modern web apps often means choosing between two trade-offs: the simplicity of server-rendered applications or the rich user experience of Single Page Applications (SPAs). Traditional SPAs introduce extra complexity through separate frontends, APIs, and state management, while classic server rendering can feel limiting for interactive UIs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inertia.js bridges this gap.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With Inertia.js, Laravel remains responsible for routing and data, while React handles rendering  without requiring a separate API layer. When combined with Server-Side Rendering (SSR), this approach delivers faster initial loads, better SEO, and improved Core Web Vitals, especially for content-heavy pages.&lt;/p&gt;

&lt;p&gt;This article explains how Server-Side Rendering with Laravel and React using Inertia.js works, and why it’s a practical, maintainable alternative to traditional SPA architectures.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. What Is Inertia.js?
&lt;/h2&gt;

&lt;p&gt;Inertia.js is a &lt;strong&gt;glue layer&lt;/strong&gt;, not a framework.&lt;br&gt;
It allows you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use Laravel controllers as page endpoints&lt;/li&gt;
&lt;li&gt;Render React components instead of Blade views&lt;/li&gt;
&lt;li&gt;Navigate without full page reloads&lt;/li&gt;
&lt;li&gt;Avoid building and maintaining REST or GraphQL APIs&lt;/li&gt;
&lt;li&gt;Inertia doesn’t replace Laravel or React, it connects them cleanly.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  3. How SSR Works with Laravel and React
&lt;/h2&gt;

&lt;p&gt;At a high level:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Browser requests a page&lt;/li&gt;
&lt;li&gt;Laravel controller runs&lt;/li&gt;
&lt;li&gt;Inertia renders a React page on the server&lt;/li&gt;
&lt;li&gt;HTML is returned to the browser&lt;/li&gt;
&lt;li&gt;React hydrates and takes over on the client&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Navigation is handled client-side&lt;/li&gt;
&lt;li&gt;Only data (props) is exchanged&lt;/li&gt;
&lt;li&gt;No full reloads&lt;/li&gt;
&lt;li&gt;You get SSR for the first load, SPA behavior afterward.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  4. Architecture Overview
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser
  ↓ HTTP request
Laravel Router
  ↓ Controller
Inertia Response
  ↓
React Page (SSR)
  ↓ HTML
Browser
  ↓ Hydration
Client-side Navigation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Key idea:&lt;br&gt;
Laravel owns routing and data. React owns rendering and interactivity.&lt;/p&gt;
&lt;h2&gt;
  
  
  5. Setting Up Laravel + Inertia (Conceptual)
&lt;/h2&gt;

&lt;p&gt;Typical setup involves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Laravel backend&lt;/li&gt;
&lt;li&gt;Inertia middleware&lt;/li&gt;
&lt;li&gt;React frontend bundled via Vite&lt;/li&gt;
&lt;li&gt;Shared SSR entry point&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Important detail:&lt;br&gt;
  There is no API layer between Laravel and React.&lt;/p&gt;

&lt;p&gt;Controllers return Inertia responses, not JSON.&lt;/p&gt;
&lt;h2&gt;
  
  
  6. Server-Side Rendering Flow
&lt;/h2&gt;

&lt;p&gt;When SSR is enabled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Laravel boots a Node process (via Vite/SSR)&lt;/li&gt;
&lt;li&gt;React renders the page using provided props&lt;/li&gt;
&lt;li&gt;HTML is injected into the response&lt;/li&gt;
&lt;li&gt;Browser receives a fully rendered page&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If SSR fails:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inertia gracefully falls back to client-side rendering&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes SSR opt-in but resilient.&lt;/p&gt;
&lt;h2&gt;
  
  
  7. Data Sharing and Props
&lt;/h2&gt;

&lt;p&gt;Data flows like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Controller&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nc"&gt;Inertia&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nc"&gt;React&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt;

&lt;span class="c1"&gt;// Example &lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Http\Controllers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Inertia\Inertia&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DashboardController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Inertia&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Dashboard'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'user'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="s1"&gt;'stats'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'projects'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'tasks'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;87&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No fetch()&lt;/li&gt;
&lt;li&gt;No axios&lt;/li&gt;
&lt;li&gt;No duplicated data contracts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Shared data (auth user, flash messages, settings) can be injected globally using Inertia middleware.&lt;br&gt;
This keeps data flow predictable and centralized.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The best architectures reduce the number of things you need to think about.” - Martin Fowler&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  8. Routing and Navigation
&lt;/h2&gt;

&lt;p&gt;Routing stays in Laravel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;php&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nc"&gt;Controller&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nc"&gt;React&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;

&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/dashboard'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;DashboardController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'index'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'auth'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Navigation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uses &lt;code&gt;&amp;lt;Link&amp;gt;&lt;/code&gt; components&lt;/li&gt;
&lt;li&gt;Intercepts clicks&lt;/li&gt;
&lt;li&gt;Requests only new props&lt;/li&gt;
&lt;li&gt;Preserves page state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You get SPA-like navigation without SPA complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Handling SEO and Meta Data
&lt;/h2&gt;

&lt;p&gt;With SSR:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Search engines receive rendered HTML&lt;/li&gt;
&lt;li&gt;Titles and meta tags are visible immediately&lt;/li&gt;
&lt;li&gt;Social previews work correctly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Meta management is handled at the React page level, but rendered server-side.&lt;br&gt;
This is a major advantage over pure client-side SPAs.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Authentication and Sessions
&lt;/h2&gt;

&lt;p&gt;One of Inertia’s biggest strengths.&lt;br&gt;
Because you’re still using Laravel:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sessions work normally&lt;/li&gt;
&lt;li&gt;CSRF protection stays intact&lt;/li&gt;
&lt;li&gt;Middleware applies automatically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No token juggling.&lt;br&gt;
No auth duplication.&lt;br&gt;
No client-side hacks. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Simplicity is the ultimate sophistication.” - Leonardo da Vinci&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  11. How the Frontend Uses the Backend
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// resources/js/Pages/Dashboard.jsx&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Head&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@inertiajs/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Dashboard&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stats&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Head&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Dashboard"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Welcome, &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Projects: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;projects&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Tasks: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the frontend’s perspective:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pages receive props&lt;/li&gt;
&lt;li&gt;Actions submit forms via Inertia&lt;/li&gt;
&lt;li&gt;Errors and validation flow back automatically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The frontend never:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Calls raw APIs&lt;/li&gt;
&lt;li&gt;Manages auth headers&lt;/li&gt;
&lt;li&gt;Worries about backend URLs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This drastically reduces frontend complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  12. Why This Architecture Makes Sense
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Single codebase&lt;/li&gt;
&lt;li&gt;Strong backend authority&lt;/li&gt;
&lt;li&gt;SEO-friendly by default&lt;/li&gt;
&lt;li&gt;Fast initial loads&lt;/li&gt;
&lt;li&gt;Minimal boilerplate&lt;/li&gt;
&lt;li&gt;Easier onboarding for teams&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why many teams describe Inertia as &lt;strong&gt;“the missing link”&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  13. Watch Out For
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Not ideal for public APIs&lt;/li&gt;
&lt;li&gt;Tight coupling between frontend and backend&lt;/li&gt;
&lt;li&gt;Requires Node for SSR&lt;/li&gt;
&lt;li&gt;Not suited for microservices-heavy setups&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a &lt;strong&gt;monolith-friendly&lt;/strong&gt; architecture – and that’s okay.&lt;/p&gt;

&lt;h2&gt;
  
  
  14. Next Steps You Can Take
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Add SSR caching&lt;/li&gt;
&lt;li&gt;Implement partial reloads&lt;/li&gt;
&lt;li&gt;Introduce shared layout components&lt;/li&gt;
&lt;li&gt;Optimize hydration performance&lt;/li&gt;
&lt;li&gt;Add WebSockets for real-time features&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  15. Interesting Facts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Inertia.js was created to reduce SPA complexity without losing UX benefits Source. &lt;a href="https://inertiajs.com/docs/v1/getting-started" rel="noopener noreferrer"&gt;https://inertiajs.com/docs/v1/getting-started&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;SSR significantly improves Core Web Vitals for content-heavy pages Source. &lt;a href="https://web.dev/articles/rendering-on-the-web" rel="noopener noreferrer"&gt;https://web.dev/articles/rendering-on-the-web&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Inertia avoids versioned API maintenance entirely Source. &lt;a href="https://inertiajs.com/docs/v2/core-concepts/how-it-works" rel="noopener noreferrer"&gt;https://inertiajs.com/docs/v2/core-concepts/how-it-works&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  16. FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Is Inertia.js a framework?&lt;/strong&gt;&lt;br&gt;
 No. It’s a thin adapter between server and frontend frameworks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Can I use Vue instead of React?&lt;/strong&gt;&lt;br&gt;
 Yes. Inertia supports Vue and Svelte as well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Does SSR work without JavaScript?&lt;/strong&gt;&lt;br&gt;
 Initial render does. Interactivity requires JS for hydration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Is this better than Next.js?&lt;/strong&gt;&lt;br&gt;
 Different tools. Inertia is backend-driven; Next.js is frontend-driven.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Can this scale?&lt;/strong&gt;&lt;br&gt;
 Yes – for most product-style applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  17. Conclusion
&lt;/h2&gt;

&lt;p&gt;Server-side rendering with Laravel, React, and Inertia.js offers a powerful alternative to traditional SPAs and fully decoupled architectures.&lt;br&gt;
By keeping routing, auth, and data on the server while leveraging React for UI, you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Faster pages&lt;/li&gt;
&lt;li&gt;Better SEO&lt;/li&gt;
&lt;li&gt;Less complexity&lt;/li&gt;
&lt;li&gt;Happier teams&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your product fits a server-driven model, this approach is hard to beat.&lt;/p&gt;

&lt;p&gt;About the Author: &lt;em&gt;Ankit is a full-stack developer at &lt;a href="https://www.addwebsolution.com/" rel="noopener noreferrer"&gt;AddWebSolution&lt;/a&gt; and AI enthusiast who crafts intelligent web solutions with PHP, Laravel, and modern frontend tools.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>react</category>
      <category>inertiajs</category>
      <category>serversiderendering</category>
    </item>
  </channel>
</rss>
