imgflow
v1.0.2
Published
Simple Express image upload middleware powered by sharp
Downloads
282
Maintainers
Readme
imgflow
A simple, TypeScript-first image upload middleware for Express.js. Built on top of multer (memory storage) and sharp. You describe where and how files should be saved; imgflow handles the rest.
What does it do?
- reads
multerin-memory buffers - accepts images only
- resizes/optimizes with
sharp - generates unique filenames (
uuid) - creates folders automatically
- writes files to disk
- puts the resulting filenames into
req.body(string or string[])
Installation
npm install imgflowESM:
import { imgflow, imgflowUpload } from "imgflow";CommonJS:
const { imgflow, imgflowUpload } = require("imgflow");How it works
- multer reads
multipart/form-dataand keeps files in memory (buffer). - imgflow validates and processes those buffers, then saves them to disk.
req.body[field]becomes a string or string[] depending onmaxCount.
Quick start (cover + gallery)
import express from "express";
import multer from "multer";
import { imgflow } from "imgflow";
const app = express();
const upload = multer({ storage: multer.memoryStorage() });
app.post(
"/post/create",
upload.fields([
{ name: "cover", maxCount: 1 },
{ name: "images", maxCount: 10 },
]),
imgflow({
uploadRoot: "uploads",
fields: {
cover: { dir: "posts/covers", maxCount: 1, resize: { width: 1024 } },
images: { dir: "posts/images", maxCount: 10, resize: { width: 1600 } },
},
}),
(req, res) => {
res.json({ cover: req.body.cover, images: req.body.images });
}
);
app.listen(3000);Resulting structure:
uploads/
posts/
covers/
<uuid>.webp
images/
<uuid>.pngOne clean middleware (imgflowUpload)
imgflowUpload bundles multer.memoryStorage() + upload.fields(...) + imgflow(...) into a single array of middlewares.
import { imgflowUpload } from "imgflow";
app.post(
"/profile",
imgflowUpload({
uploadRoot: "uploads",
fields: {
avatar: {
dir: "avatars",
maxCount: 1,
resize: { width: 256, height: 256, fit: "cover" },
},
gallery: { dir: "gallery", maxCount: 12, resize: { width: 1600 } },
},
}),
(req, res) => res.json(req.body)
);Configuration: ImgflowOptions
imgflow({
uploadRoot: "uploads", // required: root folder
fields: { ... }, // required: per-field config
fileName?: ({ field, ext }) => string, // optional: custom filename
onError?: (err) => ({ status?: number; message: string }) // optional: custom response
});fields (FieldConfig)
Each field can be defined in two ways:
fields: {
// short form: only folder (maxCount = 1)
avatar: "avatars",
// full config
cover: {
dir: "posts/covers", // required
maxCount: 1, // default: 1
resize: { ... }, // optional (see Resize)
output: { ... }, // optional (see Output)
},
}dir: relative folder insideuploadRoot. Invalid paths (..or absolute) throwINVALID_SUBFOLDER.maxCount: aligned with multer’s limit; exceeding it throwsLIMIT_<field>.- Result type:
maxCount=1→req.body[field]is a string, otherwise a string[].
Resize (resize)
{
width?: number;
height?: number;
fit?: "inside" | "cover" | "contain" | "fill" | "outside";
withoutEnlargement?: boolean; // default: true
}- Only
widthorheightstill preserves aspect ratio (fit="inside"by default). fit="cover"crops from center—great for avatars.withoutEnlargement=trueprevents upscaling smaller images (default).
Examples:
resize: { width: 1200 } // maintain aspect ratio by width
resize: { width: 256, height: 256, fit: "cover" } // square avatar
resize: { width: 1600, withoutEnlargement: false } // allow upscaling if neededOutput (output)
{
format?: "jpeg" | "png" | "webp" | "avif"; // default: original format
quality?: number; // jpeg/webp/avif quality
compressionLevel?: number; // png only (0..9)
}Examples:
output: { format: "webp", quality: 80 } // webp + quality
output: { quality: 90 } // keep original format, tweak quality
output: { format: "png", compressionLevel: 9 } // max compression for pngCustom filenames (fileName)
imgflow({
...,
fileName: ({ field, ext }) => `${field}-${Date.now()}.${ext}`,
});- Default:
uuidv4()with the final format extension. extis the resulting format (jpeg/png/webp/avif/original).
Error handling (onError)
imgflow({
...,
onError: (err) => {
if (err.message === "INVALID_TYPE")
return { status: 415, message: "Only images are accepted." };
return { status: 400, message: "Upload failed." };
},
});Built-in messages:
LIMIT_<field>→"<field> file limit exceeded."INVALID_TYPE→Only images are accepted.INVALID_IMAGE→Invalid image file.INVALID_SUBFOLDER→Invalid upload folder path.- Default status:
400.
Reading results and serving files
// maxCount = 1
console.log(req.body.cover); // "b12c3d.webp"
// multiple files
console.log(req.body.images); // ["a1.png", "b2.png"]Serve uploaded files:
import express from "express";
app.use("/uploads", express.static("uploads"));
// /uploads/posts/covers/<uuid>.webp