A CloudConvert-style, browser-based platform for image/font conversion and everyday developer tools.
Optimize images, convert fonts to web formats, generate favicon packs, turn JSON into TypeScript types, and use common developer utilities β all in one place.
Add a screenshot / GIF here:
docs/demo.gif
A single multilingual (EN / DE / TR) SPA bundling 5 tools:
| Tool | Route | What it does |
|---|---|---|
| πΌοΈ Image Optimize | / |
Convert to WebP Β· AVIF Β· JPEG Β· PNG Β· TIFF Β· GIF, quality control, resize, EXIF keep |
| π€ Font Convert | /font |
TTF Β· OTF Β· WOFF Β· WOFF2 β WOFF2 Β· WOFF Β· TTF |
| β Favicon Generator | /favicon |
From one image: favicon.ico + all PNG sizes + site.webmanifest + <head> snippet (ZIP) |
| 𧬠JSON β TypeScript | /json |
Generate interface/type from JSON β syntax-highlighted editor |
| π§° Dev Tools | /tools |
Base64 Β· URL encode/decode Β· JWT decoder Β· Hash (SHA) Β· UUID generator |
| π One-Time Secret | /secret |
Share an encrypted secret via a link that self-destructs after a single view |
| π¦ File Transfer | /transfer |
WeTransfer-style: upload files, share one link, download until it expires |
- β‘ Multi-file & parallel processing β batch upload via drag & drop; up to 10 concurrent conversions (queue-managed pool), the rest wait in line
- π¦ Bulk ZIP download β all outputs in a single archive
- π Before/After slider β draggable comparison of original vs. optimized image
- π i18n β English / Deutsch / TΓΌrkΓ§e, auto-detected from the browser with
localStoragepersistence - π¨ Syntax-highlighted code editor β CodeMirror (lazy-loaded so it doesn't bloat the main bundle)
A fully TypeScript monorepo with a clean, layered separation of concerns.
toolbox/
βββ server/ # Express + Sharp API (TypeScript, strict)
β βββ src/
β βββ config/ # Environment / configuration
β βββ constants/ # Supported image & font formats
β βββ types/ # Shared types + ambient .d.ts
β βββ utils/ # AppError, etc.
β βββ services/ # Business logic: optimizer Β· font Β· favicon
β βββ controllers/ # HTTP request/response layer
β βββ middleware/ # multer upload + centralized errorHandler
β βββ routes/ # health Β· convert Β· font Β· favicon
β βββ app.ts # Express app setup
β βββ server.ts # Entry point
β
βββ client/ # React + Vite UI (TypeScript, strict)
β βββ src/
β βββ api/ # Backend clients (convert Β· font Β· favicon)
β βββ components/ # Dropzone, JobList, CompareSlider, CodeEditor β¦
β βββ hooks/ # useBatchConverter (concurrency + state)
β βββ pages/ # 5 tool pages
β βββ i18n/ # Translation dictionary + Provider/hook
β βββ utils/ # pool, zip, jsonToTs, devtools β¦
β
βββ docker-compose.yml # Whole stack with one command
βββ README.md
Design choice: business logic (services) is fully decoupled from the HTTP layer β functions like optimizeImage, convertFont, and jsonToTypescript are tested and reused without any dependency on Express.
Requires: Node.js 20+
cd server
npm install
npm run dev # tsx watch β http://localhost:6000cd client
npm install
npm run dev # http://localhost:6001The frontend proxies /api requests to the backend (6000) via Vite.
docker compose up --build # http://localhost:8080nginx serves the frontend and proxies /api to the backend service; the backend is not exposed publicly. (This local compose runs its own MongoDB.)
Everything runs in containers; TLS is terminated by the host's nginx. The app is published on 127.0.0.1:8080 only.
# 1. Configure β copy the template and fill in the values
cp .env.example .env
# generate secrets: openssl rand -hex 32
$EDITOR .env # DOMAIN, SESSION_SECRET, SECRET_ENCRYPTION_KEY, ADMIN_PASSWORD β¦
# 2. Bring up the stack (client + server + mongo)
docker compose -f docker-compose.prod.yml up -d --build
# 3. Host nginx: point your server block at 127.0.0.1:8080 and get a cert
sudo cp deploy/nginx-host.conf.example /etc/nginx/sites-available/toolbox.fatihakyol.com
sudo ln -s /etc/nginx/sites-available/toolbox.fatihakyol.com /etc/nginx/sites-enabled/
sudo certbot --nginx -d toolbox.fatihakyol.com
sudo nginx -t && sudo systemctl reload nginxPrerequisites: DNS A/AAAA for the domain β this server; ports 80/443 open. The compose file requires DOMAIN, SESSION_SECRET, SECRET_ENCRYPTION_KEY, and ADMIN_PASSWORD in .env (it fails fast if any is missing). .env is git-ignored β never commit it. Cookies are Secure in this setup; the X-Forwarded-Proto header is propagated host nginx β container nginx β backend so logins work over HTTPS. Uploads and the transfer volume persist in named Docker volumes; client_max_body_size is raised on both nginx layers for large transfers.
Request flow: browser β host nginx (TLS) β 127.0.0.1:8080 container nginx β /api to server / static SPA otherwise.
multipart/form-data
| Field | Type | Description |
|---|---|---|
image |
file | Source image (required) |
format |
string | webp Β· avif Β· jpeg Β· png Β· tiff Β· gif |
quality |
number | 1β100 (default 80) |
width / height |
number | optional resize |
keepMetadata |
boolean | keep EXIF |
Response: converted image (binary). Size/format info in X-* headers.
| Field | Type | Description |
|---|---|---|
font |
file | ttf Β· otf Β· woff Β· woff2 (required) |
format |
string | woff2 Β· woff Β· ttf |
| Field | Type | Description |
|---|---|---|
image |
file | Source image (required) |
Response: favicons.zip (favicon.ico + PNG sizes + manifest + head snippet).
{ status, imageFormats, fontFormats }
| Method & path | Auth | Description |
|---|---|---|
POST /api/auth/login |
β | { email, password, rememberMe }. rememberMe β persistent 1-year cookie; otherwise a browser-session cookie |
POST /api/auth/logout |
session | Destroys the session |
GET /api/auth/me |
β | { user } (or null) |
POST /api/auth/change-password |
session | { currentPassword, newPassword }; clears the first-login flag |
GET /api/admin/users |
admin | List accounts |
POST /api/admin/users |
admin | Create account { email, password, role } |
DELETE /api/admin/users/:id |
admin | Remove account |
| Method & path | Auth | Description |
|---|---|---|
POST /api/secrets |
session | Create { content, passphrase?, ttlSeconds, requireLogin } β returns a share token |
GET /api/secrets |
session | Current user's secrets (metadata only, never content) |
GET /api/secrets/:token/meta |
β | View-page metadata (status, expiry, flags) β no content |
POST /api/secrets/:token/reveal |
β* | One-time reveal; returns content once, then wipes it. *requireLogin secrets need a session |
| Method & path | Auth | Description |
|---|---|---|
POST /api/transfers |
session | Multipart upload files[] + { message?, passphrase?, ttlSeconds, requireLogin } |
GET /api/transfers |
session | Current user's transfers (metadata only) |
DELETE /api/transfers/:id |
session | Owner deletes a transfer and its files |
GET /api/transfers/:token/meta |
β | Download-page info (file list, sizes, flags) |
POST /api/transfers/:token/verify |
β* | Checks login/passphrase without downloading |
GET /api/transfers/:token/download |
β* | Streams the file, or a zip of all files. *requireLogin/passphrase enforced |
WeTransfer-style sharing. Upload one or more files (stored on disk under UPLOAD_DIR) and get a single shareable link. The link is downloadable until it expires (selectable TTL, default 7 days), download count is tracked, and a single file streams directly while multiple files stream as an on-the-fly zip (archiver). Options mirror the secret feature: optional passphrase (bcrypt-gated) and a "login required to download" checkbox. Expired transfers are swept and their files wiped from disk by a background job; owners can also delete transfers manually from their history. Behind nginx, client_max_body_size and streaming proxy buffers are configured for large files.
Paste text β it's stored encrypted (AES-256-GCM, server-side key + optional passphrase) and you get a shareable link. Opening the link once reveals the content and permanently deletes it from the database; only metadata survives so the creator's history shows unopened / viewed / expired plus timestamps β the content is never shown again. Options: selectable TTL (1h/1d/7d/30d, default 7d), optional passphrase, and a "login required to open" checkbox. Expired-but-unopened secrets are swept and wiped by a background job.
Session-based auth (Passport local strategy) backed by MongoDB via connect-mongo. There is no public registration β an admin creates accounts, and each new account must change its password on first login. Roles: admin and user (more admin roles can be added later). All tool routes require a logged-in user.
The first admin is seeded on startup from env vars if no admin exists yet.
| Variable | Default | Description |
|---|---|---|
MONGO_URI |
mongodb://localhost:27017/toolbox |
MongoDB connection string |
SESSION_SECRET |
change-me-in-production |
Session signing secret β set this in production |
SECRET_ENCRYPTION_KEY |
change-me-secret-encryption-key |
AES key for one-time secrets β set this in production |
UPLOAD_DIR |
uploads |
Directory where transfer files are stored |
MAX_TRANSFER_SIZE |
2147483648 |
Max total upload size per transfer in bytes (2 GB) |
MAX_TRANSFER_FILES |
20 |
Max number of files per transfer |
ADMIN_EMAIL |
admin@toolbox.local |
Seeded first-admin email |
ADMIN_PASSWORD |
admin1234 |
Seeded first-admin password (change on first login) |
REMEMBER_ME_MAX_AGE |
31536000000 |
"Remember me" cookie lifetime in ms (1 year) |
COOKIE_SECURE |
true in production |
Set false when serving over plain HTTP |
Local dev: run MongoDB (e.g.
docker run -p 27017:27017 mongo:7), thennpm run devinserver. Docker Compose brings up Mongo automatically.
cd server && npm test # node:test β optimizer & font services
cd client && npm test # vitest β pool, jsonToTs, devtools, format- Server: real
sharpconversions (resize/fit, format), error paths - Client: concurrency pool (max-10 cap), JSONβTS generation, Base64/JWT/SHA-256, byte formatting
Frontend: React 18 Β· TypeScript Β· Vite Β· React Router Β· CodeMirror Β· JSZip Backend: Node.js 20 Β· Express Β· Sharp Β· fontverter Β· png-to-ico Β· Multer Β· archiver Β· MongoDB Β· Mongoose Β· Passport Β· connect-mongo Infra: Docker Β· nginx Β· Vitest Β· node:test
| Command | Description |
|---|---|
npm run dev |
Development (watch mode) |
npm run build |
Production build |
npm run typecheck |
Type checking (no output) |
npm test |
Run tests |
npm start |
(server) Run compiled dist/server.js |
Real optimization with Sharp Β· fully TypeScript Β· Docker-ready