This document analyzes the Electron implementation in the CollectTracker application, showcasing real-world patterns and techniques.
File: main.js
The main process in CollectTracker demonstrates several advanced Electron patterns:
const { app, BrowserWindow, dialog, ipcMain } = require("electron");
const path = require("path");
const fs = require("fs");
const express = require("express");
const cors = require("cors");
const log = require("electron-log");
// Configure electron-log
log.transports.file.level = "info";
log.transports.console.level = "info";
const isDev = process.env.NODE_ENV === "development";
const PORT = process.env.PORT || 3456;
const ROOT_PATH = app.getAppPath();
// Configure all paths based on environment
const BUILD_PATH = isDev
? path.join(ROOT_PATH, "client", "build")
: path.join(process.resourcesPath);
const SERVER_PATH = isDev
? path.join(ROOT_PATH, "server")
: path.join(__dirname, "./server");
const DB_PATH = isDev
? path.join(ROOT_PATH, "database")
: path.join(app.getPath("userData"), "database");CollectTracker uses a hybrid architecture that combines Electron with an Express server:
// Create Express app with expanded limits
const expressApp = express();
expressApp.use(cors());
expressApp.use(express.json({ limit: "50mb" }));
expressApp.use(express.urlencoded({ limit: "50mb", extended: true }));
// Enhanced static file serving
expressApp.use(
express.static(BUILD_PATH, {
setHeaders: (res, filepath) => {
if (filepath.endsWith(".html")) {
res.setHeader("Content-Type", "text/html");
}
},
})
);
// Import routes using SERVER_PATH
const collectionsRoutes = require(path.join(
SERVER_PATH,
"routes",
"collections"
));
const categoriesRoutes = require(path.join(
SERVER_PATH,
"routes",
"categories"
));
// Use routes
expressApp.use("/api/collections", collectionsRoutes);
expressApp.use("/api", categoriesRoutes);
// Start Express server with enhanced logging
const server = expressApp.listen(PORT, () => {
log.info(`Server successfully running on port ${PORT}`);
});This approach offers several advantages:
- Allows the application to use the same API endpoints in both web and desktop versions
- Enables seamless transition between web and desktop deployments
- Maintains a clean separation between frontend and backend logic
The application intelligently resolves paths based on the current environment:
// Configure all paths based on environment
const BUILD_PATH = isDev
? path.join(ROOT_PATH, "client", "build")
: path.join(process.resourcesPath);
const SERVER_PATH = isDev
? path.join(ROOT_PATH, "server")
: path.join(__dirname, "./server");
const DB_PATH = isDev
? path.join(ROOT_PATH, "database")
: path.join(app.getPath("userData"), "database");Benefits of this approach:
- Seamless development-to-production transition
- Proper resource location in packaged applications
- User data storage in appropriate OS-specific locations
The application implements robust logging with electron-log:
const log = require("electron-log");
// Configure electron-log
log.transports.file.level = "info";
log.transports.console.level = "info";
// Log all paths for debugging
log.info("Current directory:", __dirname);
log.info("Root Path:", ROOT_PATH);
log.info("Build Path:", BUILD_PATH);
log.info("Server Path:", SERVER_PATH);
log.info("DB Path:", DB_PATH);This logging strategy:
- Provides visibility into application behavior
- Helps diagnose issues in production environments
- Creates persistent logs that survive application restarts
The application leverages Electron's native dialogs for file operations:
// Export collection handler
ipcMain.handle("export-collection", async (event, collection) => {
const { filePath } = await dialog.showSaveDialog({
defaultPath: `${collection.name}.json`,
filters: [{ name: "JSON Files", extensions: ["json"] }],
});
if (filePath) {
fs.writeFileSync(filePath, JSON.stringify(collection, null, 2));
return true;
}
return false;
});
// Import collection handler
ipcMain.handle("import-collection", async () => {
const { filePaths } = await dialog.showOpenDialog({
properties: ["openFile"],
filters: [{ name: "JSON Files", extensions: ["json"] }],
});
if (filePaths.length > 0) {
const data = fs.readFileSync(filePaths[0], "utf8");
return JSON.parse(data);
}
return null;
});This implementation:
- Uses IPC for secure main-to-renderer process communication
- Leverages native OS dialogs for familiar user experience
- Handles file system operations in the main process for security
The application uses a delayed window creation pattern:
app.whenReady().then(() => {
log.info("Electron app ready...");
setTimeout(createWindow, 3000);
});This technique:
- Ensures the Express server is fully initialized before loading the window
- Prevents race conditions between server startup and window loading
- Improves application stability during startup
The application handles the Electron lifecycle appropriately:
app.on("window-all-closed", () => {
server.close();
if (process.platform !== "darwin") {
app.quit();
}
});
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});This implementation:
- Properly shuts down the Express server when windows are closed
- Follows platform-specific conventions (macOS vs Windows/Linux)
- Handles application reactivation on macOS
The current implementation has some security considerations to be aware of:
const mainWindow = new BrowserWindow({
width: 1200,
height: 800,
autoHideMenuBar: true,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
enableRemoteModule: true,
},
});While this configuration enables full Node.js integration in the renderer process, it also introduces potential security risks:
- Node Integration: Enables direct access to Node.js APIs from renderer
- Disabled Context Isolation: Removes the security boundary between Electron and web content
- Remote Module: Provides additional access to main process modules
For improved security, consider:
- Enabling context isolation
- Disabling Node integration
- Using a preload script with contextBridge for exposing only necessary APIs
The hybrid Electron-Express architecture offers several advantages:
- API Consistency: Same API endpoints for web and desktop versions
- Code Reuse: Backend logic can be shared between deployment targets
- Simplified Development: Developers can work on web or desktop without changing APIs
- Progressive Enhancement: Can start as a web app and later add desktop features
The implementation includes several performance considerations:
- Increased JSON Limits: Handles large data transfers between client and server
- Environment-Specific Paths: Optimizes resource loading based on environment
- Proper Server Shutdown: Closes Express server when application exits
The CollectTracker Electron implementation demonstrates a sophisticated hybrid architecture that combines web technologies with desktop capabilities. By embedding an Express server within the Electron application, it achieves code reuse and API consistency while leveraging native desktop features like file system access and native dialogs.
This approach is particularly well-suited for applications that:
- Need to maintain both web and desktop versions
- Require complex backend logic
- Benefit from native OS integration
- Handle significant amounts of data locally
File: client/public/electron.js
The renderer process configuration in CollectTracker demonstrates a different approach to Electron setup:
const { app, BrowserWindow } = require("electron");
const path = require("path");
const isDev = process.env.NODE_ENV === "development";
function createWindow() {
const win = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
webSecurity: true,
maxHttpBufferSize: 50 * 1024 * 1024, // 50MB limit for uploads
},
});
// Update the development URL to port 5000
win.loadURL(
isDev
? "http://localhost:5000"
: `file://${path.join(__dirname, "../build/index.html")}`
);
}
app.whenReady().then(createWindow);
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
});
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});The application uses environment detection to determine how to load the application:
win.loadURL(
isDev
? "http://localhost:5000"
: `file://${path.join(__dirname, "../build/index.html")}`
);This dual-loading strategy:
- Connects to a development server during development for hot reloading
- Loads local files in production for better performance and offline capability
- Simplifies the development workflow while maintaining production readiness
The application configures increased buffer sizes for handling large data transfers:
webPreferences: {
// ...
maxHttpBufferSize: 50 * 1024 * 1024; // 50MB limit for uploads
}This configuration:
- Allows handling of large file uploads (up to 50MB)
- Prevents "payload too large" errors when working with media files
- Optimizes for the collection management use case where images may be large
Interestingly, CollectTracker maintains two separate Electron configuration files:
main.jsin the project root (analyzed earlier)client/public/electron.js(this file)
This dual configuration approach suggests:
- The application may have evolved through different architectural phases
- It may support multiple launch modes (direct Electron vs. Express-embedded)
- It provides flexibility for different deployment scenarios
Comparing this file with the main.js analyzed earlier reveals two different approaches to Electron application architecture:
-
Express-Embedded Architecture (main.js):
- Embeds a full Express server within Electron
- Routes API requests through the internal server
- Provides a more complex but potentially more flexible setup
-
Direct Loading Architecture (client/public/electron.js):
- Directly loads the React application
- Simpler configuration with fewer dependencies
- More traditional Electron application structure
This dual approach provides flexibility:
- The Express-embedded version offers better API consistency with web deployments
- The direct loading version may offer better performance for desktop-only scenarios
Similar to the main.js configuration, this file also enables Node integration and disables context isolation:
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
webSecurity: true,
// ...
}While webSecurity: true maintains some security protections (like same-origin policy), the configuration still presents similar security considerations as discussed in the main.js analysis.
The application maintains standard Electron lifecycle handling for cross-platform compatibility:
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
});
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});This implementation:
- Follows macOS conventions by keeping the application running when all windows are closed
- Recreates windows when the application is reactivated on macOS
- Provides a consistent user experience across different operating systems
File: package.json
The package.json file in CollectTracker reveals sophisticated configuration for Electron application development, building, and packaging:
{
"name": "collecttracker",
"version": "1.0.0",
"homepage": "./",
"description": "Collection tracking desktop application",
"main": "main.js",
"author": "Andrew",
"scripts": {
"start": "electron .",
"dev": "concurrently \"cd client && cross-env BROWSER=none npm start\" \"cd server && npm start\" \"electron .\"",
"build": "cd client && npm run build",
"package": "electron-builder build --win",
"package-debug": "set DEBUG=electron-builder && electron-builder build --win",
"postinstall": "cd client && npm install && cd ../server && npm install"
},
"dependencies": {
"concurrently": "^8.2.2",
"cors": "^2.8.5",
"dotenv": "^16.4.7",
"electron-log": "^5.3.3",
"express": "^4.21.2",
"multer": "1.4.5-lts.2",
"mysql2": "^3.14.0",
"path": "^0.12.7",
"sqlite3": "^5.1.7"
},
"devDependencies": {
"cross-env": "^7.0.3",
"electron": "^25.9.8",
"electron-builder": "^26.0.12"
},
"build": {
"appId": "com.collecttracker.app",
"productName": "CollectTracker",
"directories": {
"output": "dist",
"buildResources": "build"
},
"win": {
"target": "nsis",
"icon": "build/icon.ico"
},
"files": [
"build/**/*",
"client/build/**/*",
"main.js",
"server/**/*",
"database/**/*"
],
"extraResources": [
{
"from": "client/build",
"to": "."
},
{
"from": "database",
"to": "database"
}
],
"asar": true
}
}The application uses a monorepo structure with client, server, and Electron components:
"scripts": {
"start": "electron .",
"dev": "concurrently \"cd client && cross-env BROWSER=none npm start\" \"cd server && npm start\" \"electron .\"",
"build": "cd client && npm run build",
"package": "electron-builder build --win",
"package-debug": "set DEBUG=electron-builder && electron-builder build --win",
"postinstall": "cd client && npm install && cd ../server && npm install"
}This script configuration:
- Enables a unified development workflow with
npm run dev - Runs client, server, and Electron processes concurrently
- Prevents the React development server from opening a browser window
- Automatically installs dependencies for all components with a single command
- Provides specialized debugging options for packaging
The dependencies reveal the hybrid nature of the application:
"dependencies": {
"concurrently": "^8.2.2",
"cors": "^2.8.5",
"dotenv": "^16.4.7",
"electron-log": "^5.3.3",
"express": "^4.21.2",
"multer": "1.4.5-lts.2",
"mysql2": "^3.14.0",
"path": "^0.12.7",
"sqlite3": "^5.1.7"
}This dependency set demonstrates:
- Web Server Components: Express, CORS, and multer for API functionality
- Database Flexibility: Support for both MySQL and SQLite
- Development Utilities: Concurrently for running multiple processes
- Electron Enhancements: electron-log for improved logging
- Environment Management: dotenv for configuration
The electron-builder configuration is particularly sophisticated:
"build": {
"appId": "com.collecttracker.app",
"productName": "CollectTracker",
"directories": {
"output": "dist",
"buildResources": "build"
},
"win": {
"target": "nsis",
"icon": "build/icon.ico"
},
"files": [
"build/**/*",
"client/build/**/*",
"main.js",
"server/**/*",
"database/**/*"
],
"extraResources": [
{
"from": "client/build",
"to": "."
},
{
"from": "database",
"to": "database"
}
],
"asar": true
}This configuration demonstrates:
- Comprehensive Resource Management: Carefully specifies which files to include in the package
- Resource Relocation: Uses
extraResourcesto place files outside the ASAR archive for runtime access - Windows Installer Setup: Configures NSIS installer for Windows deployment
- Application Branding: Sets product name and application ID for proper system integration
- ASAR Packaging: Enables ASAR packaging for improved performance and security
The dependencies and configuration reveal a dual database strategy:
"dependencies": {
"mysql2": "^3.14.0",
"sqlite3": "^5.1.7"
}Combined with the extraResources configuration:
"extraResources": [
{
"from": "database",
"to": "database"
}
]This suggests:
- SQLite for local/offline storage in the desktop application
- MySQL support for potential server deployments or larger installations
- Database files stored outside the ASAR archive for runtime access
The package.json reveals a sophisticated development workflow:
-
Initial Setup:
npm install
This triggers the postinstall script that sets up all components.
-
Development Mode:
npm run dev
This concurrently runs:
- React development server (with browser auto-open disabled)
- Express API server
- Electron application (connecting to the development servers)
-
Production Build:
npm run build npm run package
This builds the React application and then packages everything into an Electron application.
-
Debugging Packaging:
npm run package-debug
Enables verbose logging during the packaging process for troubleshooting.
This workflow demonstrates:
- Efficient development with hot reloading
- Clear separation between development and production builds
- Integrated debugging capabilities
- Streamlined dependency management
Analyzing the main.js and package.json together reveals the IPC communication patterns in CollectTracker:
// From main.js
ipcMain.handle("export-collection", async (event, collection) => {
const { filePath } = await dialog.showSaveDialog({
defaultPath: `${collection.name}.json`,
filters: [{ name: "JSON Files", extensions: ["json"] }],
});
if (filePath) {
fs.writeFileSync(filePath, JSON.stringify(collection, null, 2));
return true;
}
return false;
});
ipcMain.handle("import-collection", async () => {
const { filePaths } = await dialog.showOpenDialog({
properties: ["openFile"],
filters: [{ name: "JSON Files", extensions: ["json"] }],
});
if (filePaths.length > 0) {
const data = fs.readFileSync(filePaths[0], "utf8");
return JSON.parse(data);
}
return null;
});The application uses modern IPC patterns:
- Promise-Based IPC: Uses
ipcMain.handleand presumablyipcRenderer.invokefor promise-based communication - Native Dialog Integration: Leverages Electron's dialog API for file operations
- Data Serialization: Properly handles JSON serialization/deserialization
- Error Handling: Returns appropriate values based on operation success/failure
This approach:
- Provides a clean, promise-based API for renderer processes
- Encapsulates native functionality securely in the main process
- Maintains separation of concerns between UI and system operations
Analyzing all three files together provides a complete picture of CollectTracker's architecture:
-
Hybrid Application Model:
- React frontend for UI
- Express backend for API
- Electron wrapper for desktop capabilities
- Support for both SQLite and MySQL databases
-
Flexible Deployment Options:
- Can run as a standalone desktop application
- Potentially deployable as a web application with minimal changes
- Supports different database backends
-
Development Optimizations:
- Concurrent development processes
- Environment-specific configurations
- Comprehensive logging
- Debugging tools
-
Production Readiness:
- Complete packaging configuration
- Resource management for distribution
- Windows installer setup
- ASAR packaging for performance
This architecture demonstrates a sophisticated approach to cross-platform application development that leverages web technologies while providing native desktop capabilities.