{"id":14759,"date":"2026-03-26T17:56:54","date_gmt":"2026-03-26T15:56:54","guid":{"rendered":"https:\/\/shortpixel.com/blog\/?p=14759"},"modified":"2026-03-26T17:57:46","modified_gmt":"2026-03-26T15:57:46","slug":"automate-image-optimization-in-express-js-with-the-shortpixel-middleware","status":"publish","type":"post","link":"https:\/\/shortpixel.com\/blog\/automate-image-optimization-in-express-js-with-the-shortpixel-middleware\/","title":{"rendered":"Automate Image Optimization in Express.js with the ShortPixel Middleware"},"content":{"rendered":"\n<p>If you are building an Express.js application that handles images, whether users are uploading avatars, providing image URLs, or processing local server files, you already know the drill. <\/p>\n\n\n\n<p>You have to parse the request, buffer the file, send it to an optimization service, wait for the result, and <em>then<\/em> pass it along to your storage logic.<\/p>\n\n\n\n<p>In our previous guide, we covered the core <a href=\"https:\/\/shortpixel.com\/blog\/how-to-optimize-images-in-node-js-with-the-shortpixel-sdk\/\" target=\"_blank\" rel=\"noreferrer noopener\">ShortPixel Node SDK<\/a>, which gives you absolute control over this process. <\/p>\n\n\n\n<p>But what if you don&#8217;t want to orchestrate all that logic inside every single route handler?<\/p>\n\n\n\n<p>Learn more about <strong><code>ShortPixelExpress<\/code><\/strong> &#8211; the official Express middleware layer for the ShortPixel Node SDK.<\/p>\n\n\n\n<p>Instead of manually wiring up API calls in your controllers, <code>ShortPixelExpress<\/code> acts as a request adapter. You mount it before your route handler, and it automatically intercepts supported images, optimizes them, and hands your route handler a request object populated with fully optimized buffers and normalized metadata.<\/p>\n\n\n\n<p>Let&#8217;s look at how to set it up and how it handles the three most common image processing scenarios in Express.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Getting started<\/h2>\n\n\n\n<p>The middleware is bundled with the official package. It is ESM-only, so ensure your project has <code>\"type\": \"module\"<\/code> in its <code>package.json<\/code> and runs on Node 20 or higher.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>npm i @shortpixel-com\/shortpixel\n<\/code><\/pre>\n\n\n\n<p>To initialize it, you just need your API key and your preferred optimization settings (like lossy compression or WebP\/AVIF conversion).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import { ShortPixelExpress } from \"@shortpixel-com\/shortpixel\";\n\nconst optimizeImages = ShortPixelExpress({\n  apiKey: process.env.SHORTPIXEL_API_KEY,\n  lossy: 1,           \/\/ 1 = Lossy compression\n  convertto: \"+webp\", \/\/ Automatically generate WebP\n});\n<\/code><\/pre>\n\n\n\n<p><em>Note: The middleware doesn&#8217;t parse multipart forms or JSON bodies itself. You still need to mount your standard parsers like <code>multer<\/code> or <code>express.json()<\/code> before it.<\/em><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">3 ways the middleware automates your workflow<\/h2>\n\n\n\n<p>The beauty of <code>ShortPixelExpress<\/code> is that it handles the exact input format you throw at it and mutates the request intelligently.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">1. Intercepting uploaded files (Multer)<\/h3>\n\n\n\n<p>If you are accepting user uploads via <code>multer<\/code>, the middleware reads <code>req.file<\/code> or <code>req.files<\/code>. If the file is in memory, it optimizes it and replaces the buffer <em>in place<\/em>. If it&#8217;s on disk, it writes the optimized bytes back to disk and updates the file path.<\/p>\n\n\n\n<p>By the time the request hits your controller, <code>req.file.buffer<\/code> is already optimized.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import express from \"express\";\nimport multer from \"multer\";\nimport { ShortPixelExpress } from \"@shortpixel-com\/shortpixel\";\n\nconst app = express();\nconst upload = multer({ storage: multer.memoryStorage() });\n\napp.post(\n  \"\/upload\",\n  upload.single(\"image\"),\n  ShortPixelExpress({\n    apiKey: process.env.SHORTPIXEL_API_KEY,\n    lossy: 1,\n    convertto: \"+webp\",\n  }),\n  (req, res) =&gt; {\n    \/\/ req.file.buffer is ALREADY the optimized WebP buffer!\n    const result = req.shortPixel?.files?.&#91;0];\n\n    res.json({\n      optimizedFilename: req.file?.originalname,\n      resultFilename: result?.filename,\n      savedToRequest: !!req.file?.buffer,\n    });\n  }\n);\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">2. Processing remote URLs from <code>req.body<\/code><\/h3>\n\n\n\n<p>Sometimes your users submit a URL instead of a file. The middleware scans the request body for standard whitelisted keys (like <code>url<\/code> or <code>urls<\/code>). If it finds a valid HTTPS URL, it optimizes the remote image and attaches the result directly to <code>req.shortPixel.urls<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app.use(express.json());\n\napp.post(\n  \"\/from-url\",\n  ShortPixelExpress({\n    apiKey: process.env.SHORTPIXEL_API_KEY,\n    lossy: 1,\n  }),\n  (req, res) =&gt; {\n    \/\/ The original req.body.url is untouched, but the optimized result is ready\n    const result = req.shortPixel?.urls?.&#91;0];\n\n    res.json({\n      sourceUrl: result?.input,\n      filename: result?.filename,\n      optimizedBytes: result?.buffer?.length,\n    });\n  }\n);\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">3. Optimizing local file paths<\/h3>\n\n\n\n<p>If you pass local filesystem paths in the request body (e.g., <code>req.body.imagePath<\/code>), the middleware optimizes the file, writes it next to the original file, and mutates the <code>req.body<\/code> value to point to the <em>new<\/em> optimized path.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app.post(\n  \"\/from-path\",\n  ShortPixelExpress({\n    apiKey: process.env.SHORTPIXEL_API_KEY,\n    extraWhitelist: &#91;\"coverPath\"], \/\/ Tell it to look for this specific key\n  }),\n  (req, res) =&gt; {\n    const result = req.shortPixel?.paths?.&#91;0];\n\n    res.json({\n      originalPath: result?.input,\n      optimizedPath: req.body.coverPath, \/\/ This has been automatically updated!\n      filename: result?.filename,\n    });\n  }\n);\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Global middleware &amp; the <code>passthrough<\/code> option<\/h2>\n\n\n\n<p>If you don&#8217;t want to attach the middleware to every single image-heavy route, you can mount it globally using the <code>passthrough: true<\/code> option.<\/p>\n\n\n\n<p>This tells the middleware: <em>&#8220;If a request doesn&#8217;t contain any image files or matching fields, just skip it and let it proceed normally.&#8221;<\/em> Furthermore, the middleware automatically skips uploads and local files smaller than 50KB to save your quota and processing time.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app.use(\n  ShortPixelExpress({\n    apiKey: process.env.SHORTPIXEL_API_KEY,\n    passthrough: true, \/\/ Let non-image requests pass through without throwing errors\n    lossy: 1,\n  })\n);\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Summary<\/h2>\n\n\n\n<p>If the core <a href=\"https:\/\/www.npmjs.com\/package\/@shortpixel-com\/shortpixel\" data-type=\"link\" data-id=\"https:\/\/www.npmjs.com\/package\/@shortpixel-com\/shortpixel\" target=\"_blank\" rel=\"noopener\">ShortPixel SDK<\/a> is the engine, <code>ShortPixelExpress<\/code> is the automatic transmission. It inspects the request, optimizes whatever matches, and hands you the normalized results right on the Express request object.<\/p>\n\n\n\n<p>Ready to drop it into your stack? Check out the <a href=\"https:\/\/github.com\/nita-andrei-cristian\/node-short-pixel\/tree\/main\/express\" target=\"_blank\" rel=\"noreferrer noopener\">full documentation on GitHub<\/a> for a deep dive into custom field matching, metadata extraction, and advanced error handling.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>If you are building an Express.js application that handles images, whether users are uploading avatars, providing image URLs, or processing local server files, you already know the drill. You have to parse the request, buffer the file, send it to an optimization service, wait for the result, and then pass it along to your storage [&hellip;]<\/p>\n","protected":false},"author":22,"featured_media":14763,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-14759","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-world-of-wordpress"],"blocksy_meta":[],"_links":{"self":[{"href":"https:\/\/shortpixel.com\/blog\/wp-json\/wp\/v2\/posts\/14759","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/shortpixel.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/shortpixel.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/shortpixel.com\/blog\/wp-json\/wp\/v2\/users\/22"}],"replies":[{"embeddable":true,"href":"https:\/\/shortpixel.com\/blog\/wp-json\/wp\/v2\/comments?post=14759"}],"version-history":[{"count":5,"href":"https:\/\/shortpixel.com\/blog\/wp-json\/wp\/v2\/posts\/14759\/revisions"}],"predecessor-version":[{"id":14765,"href":"https:\/\/shortpixel.com\/blog\/wp-json\/wp\/v2\/posts\/14759\/revisions\/14765"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/shortpixel.com\/blog\/wp-json\/wp\/v2\/media\/14763"}],"wp:attachment":[{"href":"https:\/\/shortpixel.com\/blog\/wp-json\/wp\/v2\/media?parent=14759"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shortpixel.com\/blog\/wp-json\/wp\/v2\/categories?post=14759"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shortpixel.com\/blog\/wp-json\/wp\/v2\/tags?post=14759"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}