How I Smashed Image Compression in Laravel Using Node.js —…
    Neura MarketNeura Market/Stable Diffusion
    ChatGPTChatGPTClaudeClaudeGeminiGeminiCursorCursorGrokGrokPerplexityPerplexityStable DiffusionStable Diffusion
    DeepSeekDeepSeekCoPilotCoPilotMidjourneyMidjourney
    View All Directories
    OverviewPromptsBlogVideosGuidesCoursesCommunityModelsLoRAsComfyUI WorkflowsTrending
    Stable DiffusionBlogHow I Smashed Image Compression in Laravel Using Node.js
    Back to Blog
    How I Smashed Image Compression in Laravel Using Node.js
    productivity

    How I Smashed Image Compression in Laravel Using Node.js

    Muhamad Sulaiman May 24, 2026
    0 views

    Hey everyone! I’ve been learning more deeply about Node.js recently, and instead of studying it in...

    Hey everyone! I’ve been learning more deeply about Node.js recently, and instead of studying it in isolation, I wanted to use it to solve a real problem inside the project that I've been working on.

    One of the functions that I need to build is an image compression. But these functions are not developed 100% in Node.js; instead, they are inside my Laravel app.

    One issue kept showing up: image uploads are huge, and processing them directly during a normal Laravel request quickly becomes expensive. So I tried a hybrid approach. Laravel for the application flow, and Node.js with Sharp for the heavy image work.

    This setup ended up being one of the most practical architecture experiments I’ve done in a while.


    The real problem

    In many Laravel apps, users upload images straight from their phones or from Designers. Those files can easily be several megabytes each, and once you start resizing, converting, and compressing them, the request can become much heavier than it looks.

    The first version of this problem is simple: the upload works, but the response gets slower. The second version is worse: if several users upload at the same time, the application starts spending too much time on image processing instead of serving the rest of the app.

    That was the point where I stopped asking, “How do I make PHP do this?” and started asking, “What should Laravel do, and what should it delegate?”

    While I'm studying Node.js, out of the blue, I'm thinking... "Why don't I pass the heavy work to compress the image by using Node.js?"

    And I've made it. I create the logic and execute it.


    Why I moved it to a queue

    The biggest improvement was not Node.js alone. It was moving the image optimization work into a queued job.

    Laravel queues are designed for time-intensive tasks, so the application can return a faster response while the heavy work continues in the background. In other words, the controller no longer needs to wait around doing expensive image work before responding to the user.

    That changed the architecture completely:

    • Laravel handles validation, persistence, and job dispatching.
    • The queue handles background execution.
    • Node.js with Sharp handles image transformation.

    That separation feels much more natural than trying to do everything inside one request.


    Why Sharp

    I chose Sharp because it is a high-speed Node.js image processing library that uses libvips under the hood, and it supports common web image formats like JPEG, PNG, WebP, GIF, and AVIF. Its installation guide also provides prebuilt binaries for many common platforms, which makes it practical to use in real deployments instead of feeling like a purely experimental tool.

    For this use case, Sharp gave me exactly what I needed:

    • Resize oversized uploads.
    • Convert to WebP.
    • Lower storage usage.
    • Keep the worker focused on one job only.

    The architecture

    I now think about this flow in three layers:

    1. Laravel controller receives the upload and stores the original file temporarily.
    2. Laravel queue job gets dispatched for background processing.
    3. Node.js script runs Sharp to resize and convert the image.

    So Laravel still owns the application flow, but it no longer performs the expensive image manipulation inside the request itself. That is the part that made the solution feel production-friendly.


    Why I kept Node.js isolated

    I also kept the Node.js worker code inside its own scripts/ directory with its own package.json.

    Could Sharp be installed at the Laravel root? Sometimes yes. But I preferred isolation because the root package.json usually belongs to frontend tooling such as Vite, Tailwind, Vue, or React, while this worker is backend-oriented and has a different responsibility.

    Keeping the worker separate gave me three benefits:

    • Clearer ownership of dependencies.
    • Cleaner deployment steps.
    • Less mental overhead when maintaining the project later.

    Instead of mixing everything together, the project now has a small, focused Node environment whose only job is image optimization.


    Project structure

    laravel-project/
    ├── app/Http/Controllers/ImageController.php
    ├── app/Jobs/OptimizeImageJob.php
    ├── scripts/
    │   ├── optimize.js
    │   ├── package.json
    │   └── .gitignore
    └── storage/app/public/optimized/
    

    Controller flow

    The controller became much simpler. Its job is no longer to optimize the file immediately. It only stores what is needed and dispatches the background job.

    public function store(StoreImageRequest $request)
    {
        $request->validated();
        
        $path = $request->file('image')->store('private/uploads');
    
        // You can extract this to services later.
        $image = Image::create([
            'folder' => 'senior-leadership',
            'original_path' => $path,
            'status' => 'queued',
        ]);
    
        OptimizeImageJob::dispatch($image, 'senior-leadership');
    
        return response()->json([
            'message' => 'Image uploaded successfully and queued for optimization.',
            'status' => 'queued',
        ], 202);
    }
    

    That felt much better architecturally. The request stays focused on application flow, and the expensive work happens later.


    The queued job

    This is where Laravel and Node.js meet.

    Laravel jobs can run asynchronously through the queue system, and Laravel also supports passing serialized model data into jobs cleanly. That makes the job layer a natural place to hand the file over to an external worker process.

    <?php
    
    namespace App\Jobs;
    
    use App\Models\Image;
    use Illuminate\Bus\Queueable;
    use Illuminate\Contracts\Queue\ShouldQueue;
    use Illuminate\Foundation\Bus\Dispatchable;
    use Illuminate\Queue\InteractsWithQueue;
    use Illuminate\Queue\SerializesModels;
    use Symfony\Component\Process\Process;
    
    class OptimizeImageJob implements ShouldQueue
    {
        use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
    
        public function __construct(
            public Image $image,
            public string $folder
        ) {}
    
        public function handle(): void
        {
            $inputPath = storage_path('app/' . $this->image->original_path);
            $outputFolder = storage_path('app/public/optimized/' . $this->folder);
    
            if (! is_dir($outputFolder)) {
                mkdir($outputFolder, 0755, true);
            }
    
            $process = new Process([
                'node',
                base_path('scripts/optimize.js'),
                $inputPath,
                $outputFolder,
            ]);
    
            $process->run();
    
            if (! $process->isSuccessful()) {
                $this->image->update([
                    'status' => 'failed',
                    'error_message' => $process->getErrorOutput() ?: $process->getOutput(),
                ]);
    
                throw new \RuntimeException('Image optimization failed.');
            }
    
            $result = json_decode($process->getOutput(), true);
    
            $this->image->update([
                'status' => 'optimized',
                'optimized_path' => 'optimized/' . $this->folder . '/' . $result['filename'],
            ]);
        }
    }
    

    I prefer this over doing the work directly in the controller because it is easier to retry, easier to monitor, and easier to scale later.


    The Node.js worker

    The Node.js side stays intentionally small.

    const sharp = require('sharp');
    const path = require('path');
    const fs = require('fs');
    
    const [,, inputPath, outputFolder] = process.argv;
    
    async function compressImage() {
        try {
            const filename = path.basename(inputPath, path.extname(inputPath));
            const outputPath = path.join(outputFolder, `${filename}.webp`);
    
            await sharp(inputPath)
                .resize({ width: 1200, withoutEnlargement: true })
                .webp({ quality: 80 })
                .toFile(outputPath);
    
            if (fs.existsSync(outputPath)) {
                fs.unlinkSync(inputPath);
            }
    
            console.log(JSON.stringify({
                success: true,
                filename: `${filename}.webp`
            }));
        } catch (error) {
            console.error(JSON.stringify({
                success: false,
                error: error.message
            }));
            process.exit(1);
        }
    }
    
    compressImage();
    

    I like this part because it does one thing only: optimize the image and return a machine-readable result.


    What improved

    After switching to this design, I got improvements in the areas that actually mattered:

    • Better request responsiveness because the optimization no longer runs during the normal HTTP request path.
    • Cleaner separation of concerns because Laravel owns app logic and the worker owns image processing.
    • Lower storage usage because the output is resized and converted to WebP.
    • Safer failure handling because a failed optimization becomes a job failure, not a broken user request.

    Trade-offs

    This approach is not free of trade-offs.

    You are now maintaining two runtimes in one project: PHP and Node.js. You also need queue workers running properly in each environment, and Laravel supports multiple queue backends like database, Redis, and SQS, depending on how you want to operate the system.

    So I would not use this pattern for every app. But for applications that deal with large uploads or frequent image processing, it feels like a very practical split.


    Final thought

    As someone still learning Node.js, this was a great reminder that learning sticks better when it solves a real problem.

    Laravel remained the application brain. The queue became the handoff layer. Node.js with Sharp became the specialist worker.

    And honestly, that combination felt much more scalable than forcing the whole workflow into a single request.

    Tags

    productivityprogrammingjavascriptlaravel

    Comments

    More Blog

    View all
    Context bankruptcy: The case for strategic forgetting for AI Agentsai

    Context bankruptcy: The case for strategic forgetting for AI Agents

    Most of us have seen a coding agent fail to complete a task we know it can do. We just don't...

    J
    James O'Reilly
    Parallel Compliance Engine: Drive-to-Sheets Multi-Agent Orchestrationgooglecloud

    Parallel Compliance Engine: Drive-to-Sheets Multi-Agent Orchestration

    When building Generative AI applications, developers often encounter a massive bottleneck: sequential...

    A
    Aryan Irani
    Is It Ethical to Post and Ask About Circuits on Dev.to?discuss

    Is It Ethical to Post and Ask About Circuits on Dev.to?

    I’ve been thinking about sharing some electronic circuit posts on Dev.to — small circuits, DIY...

    C
    codebunny20
    The One-Click Exporter: AI Studio Antigravity, Probed to Its Limitsagents

    The One-Click Exporter: AI Studio Antigravity, Probed to Its Limits

    What nobody tells you about exporting your multi-agent prototype to a local workspace. Every...

    L
    leslysandra
    Guarding the till while autonomous data agents do the diggingagenticarchitect

    Guarding the till while autonomous data agents do the digging

    Autonomous agents are genuinely good at answering messy business questions. Give one an LLM and a set...

    S
    Sireesha Pulipati
    Return on Attention: Why AI Code Reviews Are Wearing Us Outai

    Return on Attention: Why AI Code Reviews Are Wearing Us Out

    PR volume went up, ticket quality didn't, and the gap got filled with LLMs on both sides of the review: bots reviewing, bots replying, bots occasionally arguing with bots about priorities that only existed in a teammate's head. Our CEO named the actual problem, and it's bigger than code review.

    C
    christine

    Stay up to date

    Get the latest Stable Diffusion prompts, rules, and resources delivered to your inbox weekly.

    Neura Market LogoNeura Market

    Discover the best AI prompts, plugins, and resources for Stable Diffusion and more.

    Content Types

    • Rules
    • Prompts
    • MCPs
    • Agents
    • Guides

    Platforms

    • ChatGPT Directory
    • Claude Directory
    • Gemini Directory
    • Cursor Directory
    • Grok Directory
    • Perplexity Directory
    • DeepSeek Directory
    • CoPilot Directory
    • Stable Diffusion Directory
    • Midjourney Directory
    • All Directories

    Resources

    • Blog
    • Documentation
    • Help Center
    • Marketplace

    Legal

    • Privacy Policy
    • Terms of Service

    © 2026 Neura Market. All rights reserved.

    |

    Not affiliated with any AI platform vendors.

    Ready-made automations for this

    Workflows from the Neura Market marketplace related to this Stable Diffusion resource

    • Reinforced Learning Chatbot for Enhanced User Supportn8n · $24.99 · Related topic
    • Automate S3 File Compression for Lead Nurturingn8n · $15.99 · Related topic
    • Streamlined Document Intelligence Workflow for AI & Machine Learningn8n · $13.46 · Related topic
    • Zip Compression of Images for Dropbox Uploadn8n · $6.99 · Related topic
    Browse all workflows