DEV Community

Roman Klymenko
Roman Klymenko

Posted on • Originally published at pixozip.com

How to Automate Image Compression with an API: A Node.js & PHP Guide

Images are still the single heaviest asset on most web pages, and one unoptimized hero shot can wreck your Largest Contentful Paint (LCP) before a line of your JavaScript even runs. The catch is that manual optimization — dragging files through a desktop tool before every deploy — simply does not scale. The moment your users start uploading their own avatars, products, or listings, compression has to happen automatically, on the server, in real time.

That is exactly what an image optimization API is for. In this guide we wire one into a real backend using the Pixozip API, with working Node.js and PHP examples, plus the async patterns you actually need in production.


1. How an Image Optimization API Works

The flow is simpler than it looks. Instead of bundling sharp, libvips, or ImageMagick into every service and fighting native build dependencies in CI, you POST the raw image bytes to a stateless HTTP endpoint and get an optimized file back.

A typical request lifecycle looks like this:

  1. Upload — your backend sends the raw image buffer to POST /v1/shrink.
  2. Queue — the gateway authenticates your API key, validates the payload, and enqueues a job.
  3. Process — a horizontally-scaled worker re-encodes the image to WebP, AVIF, or JPEG XL.
  4. Deliver — you receive metadata plus a short-lived URL to download the optimized bytes.

Because the heavy lifting runs on dedicated workers, your application servers stay light: no CPU spikes from sharp, no memory pressure, and no native modules to compile.

Concern In-process (sharp/IM) Optimization API
Native build deps Required in every image None
CPU / memory load On your app servers Offloaded to workers
Horizontal scaling Manual Built-in queue
Modern formats Manual per-codec webp / avif / jxl

2. Node.js Integration

The official @pixozip/sdk is dependency-free — it runs on native fetch and streams, so it adds almost nothing to your bundle. Install it:

npm install @pixozip/sdk
Enter fullscreen mode Exit fullscreen mode

Then compress a local file and write the result to disk:

import { Pixozip } from "@pixozip/sdk";
import * as fs from "fs/promises";

const zip = new Pixozip({ apiKey: process.env.PIXOZIP_API_KEY! });

const input = await fs.readFile("original-photo.jpg");

const result = await zip.shrink(input, {
  contentType: "image/jpeg",
  output: "webp",        // "auto" | "webp" | "avif" | "jpeg" | "png" | "jxl"
  qualityTier: "balanced" // "fast" | "balanced" | "max"
});

console.log(`Saved ${Math.round((1 - (result.ratio ?? 1)) * 100)}%`);

const optimized = await result.download();
await fs.writeFile("optimized.webp", optimized);
Enter fullscreen mode Exit fullscreen mode

The result object carries everything you need: jobId, outputBytes, the compression ratio, and a download() helper that fetches the final bytes from the short-lived storage URL. You can also resize on the fly:

await zip.shrink(input, {
  contentType: "image/png",
  output: "avif",
  resize: { width: 1200, fit: "inside" } // cover | contain | fill | inside | outside
});
Enter fullscreen mode Exit fullscreen mode

3. PHP Integration

For Laravel, WordPress, or plain PHP backends, the pixozip/sdk Composer package mirrors the same API:

composer require pixozip/sdk
Enter fullscreen mode Exit fullscreen mode
<?php
use Pixozip\Pixozip;

$zip = new Pixozip(['apiKey' => getenv('PIXOZIP_API_KEY')]);

$bytes = file_get_contents('upload.jpg');

$result = $zip->shrink($bytes, [
    'contentType' => 'image/jpeg',
    'output'      => 'webp',
    'qualityTier' => 'balanced',
]);

// $result is an array: ['jobId' => ..., 'outputBytes' => ..., 'ratio' => ...]
$optimized = $zip->download($result['downloadUrl']);
file_put_contents('optimized.webp', $optimized);
Enter fullscreen mode Exit fullscreen mode

Both SDKs throw a typed error (PixozipError in Node, PixozipException in PHP) that exposes the HTTP status and the gateway body so you can branch on rate_limit_exceeded, file_too_large, and friends.


4. Raw REST — No SDK Required

Under the hood it is just one authenticated POST. The image goes in the request body as raw bytes; the options ride along in an x-options JSON header:

curl -X POST https://api.pixozip.pretzl.dev/v1/shrink \
  -H "Authorization: Bearer img_live_your_key" \
  -H "Content-Type: application/octet-stream" \
  -H 'x-options: {"output":"webp","qualityTier":"balanced"}' \
  --data-binary @photo.jpg
Enter fullscreen mode Exit fullscreen mode

This portability is the whole point of an API: any language that can make an HTTP request — Go, Python, Ruby, Rust — can optimize images with no native dependencies at all.


5. Going Async at Scale

Synchronous calls are perfect for single uploads, but for batch imports or large payloads you should not hold an HTTP connection open while a worker grinds. Pass async: true and the API returns instantly with a jobId you poll:

const job = await zip.shrink(input, { contentType: "image/jpeg", async: true });

let status = job;
while (status.status === "queued" || status.status === "processing") {
  await new Promise(r => setTimeout(r, 1000));
  status = await zip.getJob(job.jobId);
}

if (status.status === "completed") {
  await fs.writeFile("out.webp", await status.download());
}
Enter fullscreen mode Exit fullscreen mode

Two more endpoints make life easier: POST /v1/shrink-from-url pulls a remote image directly (no need to buffer it yourself), and POST /v1/srcset returns a full set of responsive variants in one call — ideal for feeding a <picture> element.


6. Production Best Practices

  • Always send an idempotencyKey. Network retries are inevitable; an idempotency key guarantees a flaky connection never bills you — or processes the same image — twice.
  • Use output: "auto" to let the API negotiate the best modern format per image instead of hard-coding one.
  • Strip metadata by default. Leave keepMetadata off unless you genuinely need EXIF/GPS — it shaves extra bytes and removes a privacy leak.
  • Go async for anything batch. Queue-and-poll keeps your request workers free and your timeouts sane.
  • Start free. Every account gets 500 optimizations per month at no cost, and an unauthenticated POST /v1/public/shrink endpoint allows 5 test compressions per IP per day — enough to prototype before you ever add a key.

Conclusion

Build-time tooling optimizes the images you ship. An API optimizes the images everyone else uploads — automatically, at scale, without dragging native codecs into your stack. With a dependency-free SDK for Node.js and PHP and a plain REST endpoint for everything else, wiring it in is a single function call.

Grab a free API key, keep your 500 monthly optimizations, and stop shipping heavy images. Start with Pixozip →

Top comments (0)