Node.js backend + Electron frontend desktop app for browsing Dev.to articles.
A cross-platform Electron desktop application that wraps the Dev.to platform into a native reading and interaction experience. The frontend is built with vanilla JS/HTML/CSS inside Electron, backed by an embedded Express server that proxies the Dev.to public API, handles authentication, and manages caching — all running inside a single portable .exe with no installation required.
-
Embedding a backend server inside Electron Running an Express HTTP server inside the Electron main process instead of spawning a separate Node.js process. The server starts before the browser window loads, eliminating process management, IPC complexity, and port conflicts between app instances.
-
Building a single portable .exe with no installer electron-builder with portable target bundles the entire app — Electron runtime, frontend assets, and backend with all its node_modules — into one self-contained executable. The extraResources config copies the backend folder into the packaged app's resource path at build time.
-
Bypassing Windows symlink restriction during build electron-builder downloads winCodeSign (a macOS code-signing tool archive) which contains Unix symlinks. Extracting it on Windows without Developer Mode enabled fails with Cannot create symbolic link. Solved by patching binDownload.js in three separate app-builder-lib copies to return the pre-extracted cache path directly, and setting signAndEditExecutable: false to skip the rcedit step entirely.
-
In-memory LRU cache with TTL and pattern invalidation Built a custom LRUCache class using a Map for O(1) get/set with LRU eviction when capacity is reached. Each entry carries an expiry timestamp. Supports glob-style pattern invalidation (e.g. clearing all articles:* keys when a reaction is posted) and automatic background purging of expired entries.
-
Dev.to API proxy with exponential back-off retry All outbound Dev.to API calls go through a single Axios instance with interceptors. Failed requests (non-4xx) are retried up to N times with exponential delay (delay * 2^attempt). Per-request API key injection is handled via a custom _apiKey config field on the Axios request, stripped before the request is sent.
-
JWT authentication with Supabase as the user store User registration and login use bcryptjs for password hashing and jsonwebtoken for stateless session tokens. Supabase acts as the database — the client is lazily initialised only when credentials are present in the environment, so the app degrades gracefully when the DB is not configured rather than crashing on startup.
-
Response envelope compatibility between backend and frontend The frontend's api.js calls res.json() and uses the result directly as an array or object. Wrapping responses in { success, data } broke all article/tag rendering with "Unexpected response". Solved by keeping raw data as the response body and only wrapping error responses in { error: '...' } — the shape the frontend already checks.
-
ReferenceError from temporal dead zone in module scope app.use(loggerHelper.logRequest) was placed at line 284 in server.js, but const app = express() was declared at line 991. JavaScript const is not hoisted — accessing it before its declaration throws ReferenceError: Cannot access 'app' before initialization. Fixed by moving all app.use() calls to after the app initialisation block.
-
Layered architecture refactor from a single flat file The original server.js was a single 200-line file with all routes, auth, and caching mixed together. Refactored into config/, controllers/, services/, middleware/, routes/, and utils/ layers — each with a single responsibility — while keeping the embedded server entry point working identically for the Electron main process.
-
pino transport misconfiguration causing silent log loss logger.helper.js configured a pino transport with colorize, translateTime, and other pretty-print options but omitted the required target: 'pino-pretty' field. Without target, pino ignores the entire transport block silently and writes nothing to stderr in development. The fix is either adding target: 'pino-pretty' or removing the transport block to fall back to plain JSON output.
cd backend
npm install
# Optional: add your Dev.to API key to .env
npm startcd frontend
npm install
npm startcd frontend
npm run build
# Output: dist/DevToClient Setup *.exe- Browse articles by tag
- Search articles
- Bookmark articles (stored locally)
- Dark / Light mode toggle
- Pagination