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.
- 🎯 Project Overview: Overview of the boilerplate's features and goals.
- 🚀 Features: Core technologies used in the project.
- 🏗️ Architecture: Explanation of the main and renderer process architecture.
- 🔐 OAuth Authentication Flow: Details about the OAuth 2.0 implementation.
- 🔄 Auto-Update System: Description of the auto-update functionality.
- 📡 Type-Safe IPC Communication: Overview of the IPC communication setup.
- 🧪 Testing: Information about the testing setup and structure.
- 📚 AI Agent Documentation: Guides for GitHub Copilot and AI agents.
- 📦 Installation: Steps to set up the project locally.
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;
}This project exposes the User feature as a lazy main-process module. The renderer must initialize it with init-user-lazy before sending the "user" IPC request so the main process has registered the module's IPC handlers.
Main process (module declaration)
// src/main/user/module.ts
@RgModule({
imports: [RestApiModule, AuthModule],
ipc: [UserIpc],
providers: [UserService],
lazy: {
enabled: true,
trigger: "init-user-lazy",
},
})
export class UserModule {}Renderer (safe init + IPC ordering)
export const useIpc = () => {
const setUser = useSetUserDispatch();
const initUserModule = useCallback(async (successfulCallback: () => void) => {
const data = await window.electron.invoke("init-user-lazy");
const { initialized, error } = data;
if (initialized && error === undefined) {
successfulCallback();
}
}, []);
useEffect(() => {
initUserModule(() => {
window.electron.send("user");
});
}, [initUserModule]);
useEffect(() => {
const unSub = window.electron.receive("user", (data) => {
if (data === undefined) {
return;
}
setUser(data.user);
});
return unSub;
}, []);
};| 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 .agents/skills/ folders contain comprehensive guides optimized for GitHub Copilot. These skills are structured as per the Agent Skills standard and include instructions, scripts, and resources to enhance Copilot's capabilities.
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

