A production-ready Electron starter template featuring React 19, TypeScript, Tailwind CSS, and a modular architecture powered by @devisfuture/electron-modular. This boilerplate comes with pre-configured OAuth authentication (Google, GitHub), auto-update logic, comprehensive unit testing, and ready-to-use AI Agent documentation for GitHub Copilot.
This is a full-featured starter kit designed to accelerate Electron application development. Whether you're building a desktop app from scratch or migrating an existing project, this boilerplate provides:
- Modular architecture with Dependency Injection (DI) pattern
- Production-ready authentication with OAuth 2.0 (Google, GitHub)
- Auto-update system using
electron-updater - Type-safe IPC communication between renderer and main processes
- Modern React stack with hooks, context patterns, and virtualized lists
- Comprehensive testing setup with Vitest
- CI/CD pipeline with GitHub Actions
- AI-friendly documentation optimized for GitHub Copilot
- βοΈ React 19 - Latest React with concurrent features
- π¨ Tailwind CSS 3.4 - Utility-first CSS framework
- π§ React Router DOM 7 - Hash-based routing for Electron
- π¦ Vite 6 - Lightning-fast development server and build tool
- π Lucide React - Beautiful icon library
- π React Window - Virtualized lists for performance
- π React Virtualized Auto Sizer - Auto-sizing for virtualized components
- π Electron 38 - Latest Electron with modern APIs
- ποΈ @devisfuture/electron-modular - Dependency Injection framework
- π OAuth 2.0 Authentication - Google, GitHub
- π‘ Axios - HTTP client for REST API calls
- πΎ Electron Store - Persistent storage with encryption support
- π Electron Log - Production-grade logging
- β¬οΈ Electron Updater - Auto-update functionality
- π§ Electron Builder - Multi-platform builds (Windows, macOS, Linux)
- π TypeScript 5.7 - Strict type checking
- β ESLint 9 - Code linting with TypeScript support
- π Prettier 3.7 - Code formatting with import sorting
- π§ͺ Vitest 3 - Fast unit testing framework
- π Testing Library - React component testing utilities
- π¦ PostCSS + Autoprefixer - CSS processing
The main process uses a modular architecture with Dependency Injection:
src/main/
βββ app.ts # Application entry point
βββ config.ts # Global configuration
βββ preload.cts # Preload script for IPC bridge
βββ @shared/ # Shared utilities
β βββ store.ts # State management (Map + electron-store)
β βββ logger.ts # Logging utilities
β βββ ipc/ # IPC type-safe helpers
β βββ error-messages.js # Error notification system
βββ app/ # Main application module
β βββ module.ts # Module registration
β βββ service.ts # Business logic
β βββ ipc.ts # IPC handlers
β βββ window.ts # Window manager
βββ auth/ # OAuth authentication module
β βββ module.ts
β βββ service.ts # Auth logic (logout, storage cleanup)
β βββ ipc.ts # Auth IPC handlers
β βββ window.ts # OAuth popup window manager
βββ user/ # User data module
β βββ module.ts
β βββ service.ts # User API calls
βββ rest-api/ # HTTP client module
β βββ module.ts
β βββ service.ts # Axios wrapper with caching
βββ updater/ # Auto-update module
β βββ module.ts
β βββ services/ # Update logic for Windows/macOS
β βββ window.ts # Update notification window
βββ notification/ # System notifications
βββ menu/ # Application menu
βββ tray/ # System tray icon
Key Concepts:
- Each feature is a self-contained module with clear responsibilities
- Services handle business logic and are auto-injected via
@Injectable() - IPC Handlers manage renderer β main communication with
@IpcHandler() - Window Managers control window lifecycle with
@WindowManager() - Tokens enable custom dependency injection
The renderer follows a domain-driven design with React:
src/renderer/
βββ App.tsx # App shell with providers + router
βββ main.tsx # React entry point
βββ components/ # Reusable UI primitives
β βββ Button/
β βββ IconButton/
β βββ Avatar/
β βββ Popover/
β βββ List/
β βββ TextField/
βββ composites/ # Cross-cutting feature blocks
β βββ Routes/ # Public/Private route guards
β βββ LightDarkMode/ # Theme toggle
β βββ LazyRender/ # Virtualized list renderer (react-window + AutoSizer) - renders only visible items for large collections
β βββ AppVersion/ # Version display
βββ conceptions/ # Domain modules (feature packages)
β βββ Auth/ # Authentication
β β βββ Context/ # Auth state (useSyncExternalStore pattern)
β β βββ components/ # SignIn, ProviderButton
β β βββ hooks/ # useControl, useSelectors
β βββ User/ # User profile
β β βββ Context/ # User state
β β βββ components/ # UserPopover, Avatar
β β βββ hooks/ # User data hooks
β βββ Updater/ # Update UI
β βββ Context/ # Update state
β βββ components/ # UpdateNotification
βββ layouts/ # Page layouts
β βββ Main.tsx
β βββ TopPanel.tsx
βββ windows/ # Route pages
βββ Home/
βββ Settings/
Key Patterns:
- Context Pattern with
useSyncExternalStore- Optimized state management without unnecessary re-renders - Subscription-based state - Components subscribe to specific state slices
- Domain modules (conceptions) - Feature-complete packages with state + UI + hooks
- Separation of concerns - UI primitives, composites, and domain logic are clearly separated
This project implements a complete OAuth 2.0 flow with support for multiple providers.
- β Google OAuth 2.0
- β GitHub OAuth
-
User clicks "Sign In with Google/GitHub" in the renderer
-
Renderer sends IPC message
windowAuthwith provider type -
Main process opens OAuth popup window (
AuthWindow)// src/main/auth/window.ts @WindowManager<TWindows["auth"]>({ hash: "window:auth", options: { width: 400, height: 400, sandbox: true } })
-
Popup navigates to provider OAuth URL
GET {BASE_REST_API}/api/auth/google GET {BASE_REST_API}/api/auth/github -
Backend handles OAuth flow:
- Redirects to Google/GitHub authorization page
- User grants permissions
- Provider redirects back with authorization code
- Backend exchanges code for access token
- Backend fetches user profile from provider API
- Backend creates/updates user in database
- Backend redirects to:
{APP_URL}/api/auth/verify?token={JWT}&userId={ID}
-
AuthWindow intercepts redirect via
onWebContentsWillRedirect:const isVerify = /api\/auth\/verify\?token\=/g.test(url); if (isVerify) { const token = searchParams.get("token"); const userId = searchParams.get("userId"); // Store credentials setElectronStorage("authToken", token); setElectronStorage("userId", userId); // Notify renderer ipcWebContentsSend("auth", mainWindow.webContents, { isAuthenticated: true, }); // Close popup this.window?.close(); }
-
Renderer updates auth state and redirects to authenticated routes
The boilerplate uses a custom RestApiService with:
- Axios instance with base URL from
.env - Response caching using
electron-store - Token-based authentication with Bearer tokens
- Error handling with 401 redirect to logout
// src/main/user/service.ts
async byId<R extends TUser>(id: string): Promise<R | undefined> {
const response = await this.restApiProvider.get<R>(
`${restApi.urls.base}${restApi.urls.baseApi}${restApi.urls.user.base}/${id}`,
{
headers: {
Authorization: `Bearer ${getElectronStorage("authToken")}`
},
isCache: true
}
);
// Auto-logout on 401
if (response.error?.details?.statusCode === 401) {
this.authProvider.logout(mainWindow);
return;
}
return response.data;
}| Method | Endpoint | Purpose |
|---|---|---|
| GET | /api/auth/google |
Initiate Google OAuth flow |
| GET | /api/auth/github |
Initiate GitHub OAuth flow |
| GET | /api/auth/verify?token=&userId= |
Callback with JWT token |
| GET | /api/user/{userId} |
Fetch user profile (requires Bearer token) |
Built-in auto-update functionality using electron-updater:
- Automatic update checks on app launch
- Background downloads with progress tracking
- GitHub Releases integration - fetches updates from repository releases
- Platform-specific implementations:
- Windows: NSIS installer with differential downloads (auto-update works on Windows without code signing)
- macOS: DMG with code signing support. Important: macOS requires code signing and notarization for standard auto-update via the ecosystem. This project includes a custom macOS auto-update implementation (using built-in Node.js modules) so an update workflow can work without
electron-buildercode signing if you need it. If you prefer the standard signed macOS flow, useelectron-builderand configure macOS code signing/notarization. - Linux: AppImage
- Update notifications with system tray integration
- Manual update checks via application menu
- Windows: We use
electron-builderfor Windows builds. Windows auto-update can work without code signing, so signing is optional; however, if you want signed installers, configure a Windows code signing certificate and set it in yourelectron-builderconfiguration. - macOS: Official macOS auto-update and distribution typically requires proper code signing and notarization. This boilerplate provides a custom macOS auto-update (not relying on
electron-builder) to support update flows without signing. If you need code-signed macOS builds and standard auto-update behavior, configureelectron-builderand supply the appropriate Apple Developer certificates and notarization settings.
All IPC communication is fully typed with a single API:
// Renderer β Main (fire-and-forget)
window.electron.send("send", {
type: "windowAuth",
data: { provider: "google" },
});
// Renderer β Main (request/response)
const version = await window.electron.invoke("invoke", {
type: "getAppVersion",
});
// Main β Renderer (push events)
ipcWebContentsSend("auth", webContents, { isAuthenticated: true });All IPC types are defined globally in types/:
// types/sends.d.ts
type TEventPayloadSend = {
windowAuth: { provider: TProviders };
windowUpdateApp: undefined;
// ...
};
// types/invokes.d.ts
type TEventPayloadInvoke = {
getAppVersion: undefined;
getUser: { id: string };
// ...
};
// types/receives.d.ts
type TEventPayloadReceive = {
auth: { isAuthenticated: boolean };
updater: TUpdaterPayload;
// ...
};Comprehensive unit testing setup with Vitest:
- β Main process tests - All services, IPC handlers, window managers
- β Renderer tests - React components, hooks, contexts
- β Mocked dependencies - Electron APIs, stores, IPC
# Run all tests
npm run test:unit:renderer
npm run test:unit:main
# Watch mode (development)
vitest src/renderer --watch
vitest --config vitest.config.main.ts --watchsrc/main/
βββ auth/
βββ service.ts
βββ service.test.ts # Unit tests for AuthService
βββ ipc.ts
βββ ipc.test.ts # Unit tests for IPC handlers
src/renderer/
βββ conceptions/
βββ Auth/
βββ hooks/
β βββ useControl.ts
β βββ useControl.test.ts
βββ Context/
βββ Context.tsx
βββ Context.test.tsx
The docs/ folder contains comprehensive guides optimized for GitHub Copilot:
| Document | Description |
|---|---|
| typescript.md | TypeScript best practices (use type instead of interface, naming conventions) |
| javascript.md | Modern JavaScript patterns, performance optimization, algorithms |
| react.md | React component patterns, custom hooks, props typing |
| Ρontext-pattern.md | Context pattern with useSyncExternalStore for optimal re-renders |
| main-process-modular-architecture.md | Electron main process DI architecture |
| renderer-process-architecture.md | Renderer domain-driven design |
| ipc-communication.md | Type-safe IPC patterns |
| tailwind-css.md | Tailwind utility patterns |
| clsx-tailwind.md | Conditional className composition |
| lucide-react.md | Icon usage guidelines |
| event-delegation-guide.md | Event delegation patterns |
| react-form-instructions.md | Form handling best practices |
| main-process-modular-unit-tests.md | Testing main process modules |
| renderer-process-unit-tests.md | Testing React components |
| electron-path-aliasing.md | Import path aliases configuration |
| git-commit-instructions.md | Commit message conventions |
These docs help AI agents (like GitHub Copilot) understand project patterns and generate consistent, high-quality code.
- Node.js 22.x or higher
- npm 10.x or higher
- Git
git clone https://github.com/trae-op/electron-modular-boilerplate.git
cd electron-modular-boilerplatenpm installCreate .env file in the root directory:
# REST API Base URL (your backend server)
BASE_REST_API=http://localhost:3000
# Development mode (automatically set by scripts)
NODE_ENV=developmentFor production builds, create .env.production:
BASE_REST_API=https://your-production-api.com
NODE_ENV=productionYou need a backend server that handles OAuth. If you don't have one, a reference implementation built with NestJS is available: https://github.com/trae-op/nestjs-boilerplate β it includes ready-to-use OAuth endpoints, environment examples, and JWT/session handling.
-
Configure redirect URIs (example for local dev):
http://localhost:3000/api/auth/google/callback http://localhost:3000/api/auth/github/callback -
Environment variables (examples used by the reference backend):
GOOGLE_CLIENT_ID=... GOOGLE_CLIENT_SECRET=... GITHUB_CLIENT_ID=... GITHUB_CLIENT_SECRET=... JWT_SECRET=... -
Implement endpoints (or use the reference):
GET /api/auth/google- Redirect to Google OAuthGET /api/auth/github- Redirect to GitHub OAuthGET /api/auth/verify- Return JWT token after successful auth (redirect target after provider auth)GET /api/user/:id- Fetch user by ID (requires Bearer token)
You need a backend server that handles OAuth. If you don't have one, a implementation built with NestJS is available: https://github.com/trae-op/nestjs-boilerplate β it includes ready-to-use OAuth endpoints that described in this README.md, environment examples, and JWT/session handling.
# Start both React dev server and Electron
npm run dev
# Or run separately:
npm run dev:react # Start Vite dev server (port 5173)
npm run dev:electron # Start Electron appnpm run dev # Run React + Electron in parallel
npm run dev:react # Start Vite dev server only
npm run dev:electron # Start Electron onlynpm run build # Build React app (production)
npm run transpile:electron # Transpile TypeScript (main process)
npm run build:mac # Build macOS .dmg
npm run build:win # Build Windows .exe (NSIS)
npm run build:linux # Build Linux AppImagenpm run test:unit:renderer # Run renderer process tests
npm run test:unit:main # Run main process testsnpm run lint # Run ESLint
npm run format # Format code with Prettier- Version:
prettier(^3.7.4) β configured inpackage.json. - Config file:
.prettierrcat repo root. Key rules includesemi: true,trailingComma: 'all',singleQuote: false,printWidth: 80,tabWidth: 2. - Import sorting: Uses
@trivago/prettier-plugin-sort-importswith animportOrderarray,importOrderSeparation: true, andimportOrderSortSpecifiers: trueso imports are deterministic and grouped consistently. - Format script:
npm run formatrunsprettier --write "src/**/*.{ts,tsx,cts,css,json}". - Tip: Enable editor integration (Prettier extension / format on save) and run
npm run formatbefore commits to keep code style consistent.
- Config file:
eslint.config.jsat repo root. It usestypescript-eslintwrapper and@eslint/jsrecommended rules. - Scope & ignore: Lints
**/*.{ts,tsx}and ignoresdist(seeignoresin config). - Plugins & rules: Includes
eslint-plugin-react-hooksandeslint-plugin-react-refresh. Notable rules: React Hooks recommended rules are enabled andreact-refresh/only-export-componentsis set towarn. - Run:
npm run lintrunseslint .. - Tip: Install the ESLint extension in your editor and enable auto-fix on save for the smoothest workflow.
npm run build:macOutput: dist/mac/electron-modular-boilerplate.app and dist/electron-modular-boilerplate-{version}.dmg
npm run build:winOutput: dist/electron-modular-boilerplate-setup-{version}.exe
npm run build:linuxOutput: dist/reminder-{version}.AppImage
The project includes a GitHub Actions workflow that:
- Runs on push to
mainbranch - Runs unit tests for both renderer and main processes
- Builds for macOS and Windows
- Publishes releases to GitHub Releases (if version changed)
Workflow file: .github/workflows/build.yml
To enable auto-publishing:
- Create a GitHub Personal Access Token (PAT). For private repositories you need the
reposcope; for public-only publishingpublic_repomay be sufficient. - In your GitHub repository go to Settings β Secrets and variables β Actions and add the token as a secret (common name:
GH_TOKEN). Note: GitHub Actions also provides an automatically-generatedGITHUB_TOKENfor workflows, but for publishing from private repositories or when workflows need elevated permissions, you should use a PAT stored as a secret. - If your production build or publish scripts need access to the token at runtime, also add
GH_TOKENto your.env.production(do not commit this file). Keep tokens secret and never hardcode them in your repository.
The project includes 15+ production-ready React components:
Button- Primary/secondary/tertiary variantsIconButton- Icon-only buttons with tooltipsAvatar- User avatar with fallback initialsAvatarButton- Avatar with click functionalityPopover- Dropdown menus and popoversTextField- Form inputs with validationSelect- Custom select dropdownsCheckbox/RadioGroup- Form controlsList- Virtualized lists for performanceLazyRender- Composite for virtualized rendering (usesreact-window+react-virtualized-auto-sizer). Efficiently renders only visible items in a long collection (e.g., 1,000 options may render ~10 visible rows), improving responsiveness for slow renders. Reused byAutocomplete/AutocompleteMultipleto virtualize option lists inside popovers.LoadingSpinner- Loading statesCard- Content containersAutocomplete- Search with suggestions (supports multiple selection and usesLazyRenderfor large option sets)Popup- Modal dialogs
All components are fully typed, tested, and follow Tailwind CSS patterns.
useClosePreloadWindow- Close splash screen after app loadsuseDayjs- Localized date formattinguseControl- Auth control (login/logout)useSelectors- Subscribe to specific context state slicesuseDispatch- Get state setter functions
All contexts use the Subscription Pattern with useSyncExternalStore:
// Avoid unnecessary re-renders
const isAuthenticated = useAuthIsAuthenticatedSelector(); // Only re-renders when auth status changes
const setAuth = useSetAuthIsAuthenticatedDispatch(); // Never re-renders- Dark mode support via
classstrategy - Custom color palette with CSS variables
- Responsive design utilities
- Custom plugins for animations
Light/dark mode toggle is built-in:
// src/renderer/composites/LightDarkMode/
const { isDarkMode, toggleTheme } = useLightDarkMode();- Context Isolation enabled in preload script
- Sandbox enabled for OAuth windows
- CSP headers in production builds
- Secure token storage with
electron-store - No node integration in renderer
- IPC validation with TypeScript

