Thumbnails, avatars, banners — almost every app needs to shrink images at some point, and shipping full-size uploads is slow and wasteful. This guide builds a small HTTP service that takes an image, resizes it to the dimensions you ask for, and hands back a compact WebP. It runs on the Node.js application and uses sharp, the fast image library whose prebuilt binaries install cleanly on Falix.
| At a glance | |
|---|---|
| You need | A server running the Node.js application |
| Build with | Express, multer (uploads), sharp (resizing) |
| Plan | Any — free runs while your session timer has time, premium runs 24/7 |
| Time | About twenty minutes |
New to the Node application? Read Node.js on Falix first — it covers the index.js entry file, automatic npm install, and the SERVER_PORT rule this project relies on.
What you're building
| Endpoint | Method | Does |
|---|---|---|
/ |
GET | The upload form in public/ |
/resize?w=&h= |
POST | Takes an uploaded image, returns a resized WebP |
The resizer keeps aspect ratio, never upscales past the original, and caps the requested size so nobody can ask for a 40000×40000 monster.
Step 1 — Create the project files
You need three files. In the File Manager, create package.json:
{
"name": "image-resizer",
"version": "1.0.0",
"private": true,
"dependencies": {
"express": "^4.19.2",
"multer": "^1.4.5-lts.1",
"sharp": "^0.33.4"
}
}
Then index.js — the whole service:
const express = require('express');
const multer = require('multer');
const sharp = require('sharp');
const path = require('node:path');
const app = express();
const upload = multer({
limits: { fileSize: 10 * 1024 * 1024 }, // 10 MB cap
storage: multer.memoryStorage(),
});
app.use(express.static(path.join(__dirname, 'public')));
// POST an image file (field name "image") plus ?w= and/or ?h=.
// Returns a resized WebP.
app.post('/resize', upload.single('image'), async (req, res) => {
if (!req.file) return res.status(400).json({ error: 'No image uploaded (field name must be "image").' });
const width = clampDim(req.query.w);
const height = clampDim(req.query.h);
if (!width && !height) return res.status(400).json({ error: 'Pass at least ?w= or ?h=.' });
try {
const output = await sharp(req.file.buffer)
.rotate() // honour EXIF orientation
.resize({ width, height, fit: 'inside', withoutEnlargement: true })
.webp({ quality: 80 })
.toBuffer();
res.type('image/webp').send(output);
} catch (err) {
console.error('resize failed:', err.message);
res.status(422).json({ error: 'Could not process that file as an image.' });
}
});
function clampDim(v) {
const n = parseInt(v, 10);
if (Number.isNaN(n)) return undefined;
return Math.min(Math.max(n, 1), 4000); // 1..4000 px
}
const PORT = process.env.SERVER_PORT || 8080;
app.listen(PORT, '0.0.0.0', () => console.log(`Listening on port ${PORT}`));
Finally a tiny front end at public/index.html so you can test in a browser:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="utf-8"><title>Image resizer</title></head>
<body>
<h1>Resize an image</h1>
<form action="/resize?w=300" method="post" enctype="multipart/form-data">
<input type="file" name="image" accept="image/*" required>
<button>Resize to 300px wide</button>
</form>
</body>
</html>
Step 2 — Start it
Press Start on the Console page. Because a package.json exists, Falix runs npm install automatically before your code — you'll watch Express, multer, and sharp download, then:
Listening on port 25565
That line flips your server to online. sharp ships prebuilt binaries for the server's Linux environment, so there's no compiler step and no native-build failure to worry about — it just lands and loads.
🎯 Good to know:
multer.memoryStorage()keeps each upload in RAM and passes it straight to sharp — nothing is written to disk. That's simpler and avoids leaving stray files behind, at the cost of holding the file in memory while it's processed. The 10 MBfileSizelimit keeps that bounded.
Step 3 — Try it
Open your server's address (from the Network page) in a browser, pick an image, and submit. The response is a resized WebP. Or drive it from a terminal:
curl -F "[email protected]" "http://YOUR_ADDRESS:PORT/resize?w=200" -o small.webp
Ask for width only (?w=200) and height scales to match; ask for both and the image fits inside that box without distortion. A 600×400 source requested at w=200 comes back 200×133 — aspect ratio preserved.
How the resize works
The pipeline is four chained calls on the sharp object:
| Call | Why |
|---|---|
.rotate() |
Reads the photo's EXIF orientation flag and bakes it in, so phone photos aren't sideways |
.resize({ width, height, fit: 'inside', withoutEnlargement: true }) |
fit: 'inside' keeps aspect ratio within your box; withoutEnlargement refuses to blow a small image up |
.webp({ quality: 80 }) |
Encodes to WebP — much smaller than JPEG or PNG at the same quality |
.toBuffer() |
Runs the pipeline and returns the bytes, which you send() |
Want PNG or JPEG out instead? Swap .webp(...) for .png() or .jpeg({ quality: 80 }) and change the res.type(...) to match. Everything else beyond these Falix-shaped parts — crops, blurs, watermarks, format detection — is standard sharp; the official docs at sharp.pixelplumbing.com take it from here.
Extend it: save resized copies
Right now the resizer is stateless — it processes and forgets. To keep thumbnails, write the buffer to disk with sharp(...).toFile('thumbs/name.webp') instead of .toBuffer(), and serve the thumbs/ folder with another express.static. One caveat worth knowing: files you write live in /home/container, and a reinstall (or switching the server's application) wipes them. For anything you can't lose, store the bytes in a managed database or re-generate on demand.
Troubleshooting
- Page won't load / connection refused — a port or bind-address problem, not a code one. The
listencall must useSERVER_PORTand0.0.0.0; see I can't reach my app. Cannot find module 'sharp'— the install didn't finish. Scroll to the top of the console and read thenpm installoutput; or open the Packages page and installsharpthere, then restart.- HTTP 422 "Could not process that file" — the upload wasn't a real image (or was a format sharp doesn't read). That's sharp refusing bad input, exactly as intended.
- HTTP 400 on submit — either no file reached the server (the form field must be named
image) or you didn't pass?w=or?h=. - Killed under load — big images plus in-memory storage use RAM; the free plan shares 2.5 GB. See Out of memory.
Next steps
- Handle file uploads properly
- Build a QR code service
- Put it behind a real domain with HTTPS
- Going deeper with sharp? The official guide lives at sharp.pixelplumbing.com.