7.9 KiB
PNGer Development & Technical Instructions
Project Overview
PNGer is a single-container web application for PNG editing and resizing, designed for deployment on Unraid with Gitea version control. It features a modern Svelte frontend and a high-performance Node.js/Sharp backend.
Technical Architecture
Architecture Overview
PNGer uses a multi-layered architecture designed for responsiveness and efficiency:
graph TD
A[User Browser] --> B[Svelte Frontend]
B --> C["Canvas API (live preview, client-side)"]
B --> D["Express API (POST /api/transform)"]
D --> E[Sharp Image Library]
E --> F[Response buffer streamed back as a download]
Nothing is persisted at any point: the upload lives in memory for the duration of the request and the result is streamed straight back.
Backend (Express + Sharp)
The backend is built with Node.js and TypeScript, using Express for the API and Sharp for high-performance image processing.
Endpoints — there is exactly one:
POST /api/transform—multipart/form-data, fieldfile, plus optionalwidth,height,quality(default 80),format(png|webp|jpeg, defaultpng),fit(defaultinside) andposition(defaultcenter). Returns the image bytes withContent-Disposition: attachment;400if no file,500if Sharp throws.
There is no
GET /api/health. Earlier revisions of this document claimed one. The router defines onlyPOST /transform, so a GET to/api/healthfalls through to the SPA catch-all and returnsindex.htmlwith a 200 — which means a monitor pointed at it will always report healthy. The container's ownHEALTHCHECKcorrectly probes/instead.
Key Dependencies:
sharp: High-performance image processing (handles resizing, cropping, and format conversion). Resizes usewithoutEnlargement: true, so output never exceeds the source dimensions.multer: Middleware for handlingmultipart/form-data, used for file uploads. Configured withmemoryStorage()and nolimits— see the dead-code note below.express: Web framework for the API; also serves the built frontend fromdist/publicwith anapp.get("*")SPA fallback.cors: Applied wide open (app.use(cors())) so the Vite dev server on:5173can reach the API on:3000. In production both are the same origin.
Dead code you will trip over
backend/src/server.jsis not the server. The entrypoint issrc/index.ts→dist/index.js.server.jsis an older standalone CommonJS implementation with/api/health,/api/processand/api/metadata,helmet, a PNG-only file filter and a realMAX_FILE_SIZElimit.tsconfig.jsonhas noallowJs, sotscnever compiles it and it never reachesdist/; it alsorequireshelmetanddotenv, which are not inpackage.json. It is the source of most of the features these docs used to describe. Either port the good parts (size limit, MIME filter, real health endpoint) intoroutes/image.tsor delete it.frontend/src/main.jsandfrontend/src/main.tsboth exist.index.htmlloadsmain.js, and onlymain.jsimports./app.css— "fixing" the script tag to point atmain.tswould silently drop every style in the app.
Frontend (Svelte + Vite)
The frontend is a reactive Svelte application that prioritizes real-time feedback and UX.
Core Components & Modules:
App.svelte: Main application container managing state and UI.lib/api.ts: API client for backend communication.lib/preview.ts: Client-side preview logic using the Canvas API for instant feedback.lib/theme.ts: Dark/light theme management with persistent storage.lib/presets.ts: Configuration for smart image presets.
Design System (Dark/Light Modes)
The UI uses a modern design system with CSS custom properties for theming:
- Light Mode: Clean white background with dark gold (
#b8860b) accents. - Dark Mode: Deep black (
#0a0a0a) background with light gold (#daa520) accents. - Transparencies: Dark/Light toggling allows users to inspect PNG transparency against different backgrounds.
Implementation Details
Live Preview System
Live preview is implemented using a client-side Canvas-based approach to provide instant feedback (< 100ms) without server round-trips.
How it works:
- User uploads an image.
- The browser loads the image into an HTML5 Canvas.
- On any parameter change (width, quality, etc.), a debounced (300ms) update applies transformations to the canvas.
- The canvas content is displayed side-by-side with the original for comparison.
- File sizes are estimated from the Canvas data URL to provide immediate feedback on optimization savings.
Fidelity caveats — the preview is an approximation, not the output:
- The preview is encoded by the browser's Canvas encoder; the download is encoded by Sharp. Byte counts will differ.
- Canvas ignores the quality argument for PNG (it is always lossless), so dragging quality changes nothing in a PNG preview while it does change the real output.
- The preview's
calculateDimensions()will happily scale an image up; the server setswithoutEnlargement: trueand will not. The "Retina @2x" preset is exactly this case — the preview grows, the downloaded file does not.
Docker Strategy & Fixes
PNGer uses a multi-stage Docker build to minimize image size and maximize security.
Optimization Fixes applied:
- Dependency Installation: Uses
npm installinstead ofnpm cito handle missing lockfiles gracefully while maintaining stability via caret ranges inpackage.json. - Security: Runs as a non-root
nodeuser in the final Alpine-based image. - Multer Upgrade: Upgraded to
v2.1.0for improved security and performance.
Local Development Setup
Prerequisites
- Node.js 20+
- npm or pnpm
- Docker (optional)
Setup Steps
# Clone repository
git clone https://git.alwisp.com/jason/pnger.git
cd pnger
# Setup backend
cd backend
npm install
npm run dev
# Setup frontend (separate terminal)
cd frontend
npm install
npm run dev
Environment Variables
Only PORT is actually read by the application. The full table, including which variables are declared-but-unused, is in INSTALL.md.
Backend:
PORT: 3000 — read bysrc/index.ts.MAX_FILE_SIZE: declared in theDockerfileand compose file, not enforced — the livemulterconfig sets nolimits.CORS_ORIGIN: inbackend/.env.example, not read —app.use(cors())takes no options.TEMP_DIR//app/temp: created by theDockerfile, never written to — processing is entirely in memory.
Frontend:
VITE_API_URL: not read.lib/api.tspicks its base URL fromimport.meta.env.DEV(http://localhost:3000/apiin dev,/apiin a build).
Development Workflow & Standards
Workflow
- Feature Branch: Create from
main. - Develop: Use hot-reload (
npm run dev). - Test: Perform manual testing of image operations and presets.
- Commit: Use
type: descriptionformat (e.g.,feat: add rotation). - Merge: Merge into
mainafter review.
Code Standards
- TypeScript: Use strict types where possible.
- Svelte: Keep components modular and under 300 lines.
- Async/Await: Use for all asynchronous operations.
- Semantic HTML: Ensure accessibility and proper structure.
Troubleshooting
More symptoms, including container-level ones, in INSTALL.md.
- Port in use:
lsof -ti:3000 | xargs kill -9 - Sharp issues:
npm rebuild sharp - Docker Cache:
docker builder pruneif builds fail unexpectedly. - Preview Glitches: Check browser console for Canvas API errors.
Last Updated: July 2026 — reconciled against the code during the docs standardization pass. Deployment and configuration detail now lives in INSTALL.md and UNRAID.md.