A production-ready Electron + React + TypeScript desktop application template.
Built from lessons learned creating production desktop apps including StoryFlow.
- Auto-updates via GitHub Releases (electron-updater)
- SQLite database with better-sqlite3 (WAL mode for performance)
- Settings management with key-value store
- Window state persistence (size, position, maximized state)
- Cross-platform builds for macOS, Windows, and Linux
- CI/CD with GitHub Actions
- Code signing support for macOS (notarization ready)
- Dark mode support
Download the latest release to see the template in action:
| Platform | File |
|---|---|
| macOS (Apple Silicon) | DesktopStarterApp-x.x.x-arm64.dmg |
| macOS (Intel) | DesktopStarterApp-x.x.x-x64.dmg |
| Windows | DesktopStarterApp-x.x.x.Setup.exe |
| Linux | desktop-starter-app_x.x.x_amd64.deb |
Test auto-updates: Download an older version from releases, install it, and watch it automatically update to the latest version!
-
Clone this repository
git clone https://github.com/dotnetfactory/desktop-starter-app.git cd YOUR_REPO_NAME -
Update placeholders (see Configuration below)
-
Install dependencies
npm install
-
Run development
npm run dev
Edit app.config.ts - this is the single source of truth for your app:
export const config = {
// App Identity
productName: 'Your App Name',
executableName: 'your-app-name',
appBundleId: 'com.yourcompany.yourapp',
// Database
appDataFolder: 'YourAppName',
dbFilename: 'app.db',
// GitHub (for releases & auto-updates)
github: {
owner: 'your-username',
repo: 'your-repo',
private: false,
},
// Features
autoUpdateEnabled: true,
maintainer: 'Your Name <[email protected]>',
};Edit package.json:
{
"name": "your-app-name",
"productName": "Your App Name",
"description": "Your app description",
"author": {
"name": "Your Name",
"email": "[email protected]"
},
"homepage": "https://github.com/YOUR_USERNAME/YOUR_REPO"
}The .env file is only for secrets (API keys, tokens, certificates). App configuration is in app.config.ts.
cp .env.example .env| Variable | Description | When Needed |
|---|---|---|
GH_TOKEN |
GitHub PAT for private repo access | Private repos |
APPLE_* |
macOS signing credentials | macOS releases |
Add these secrets to your GitHub repository for automated releases:
| Secret | Description | Required |
|---|---|---|
GH_TOKEN |
Personal Access Token (private) | Yes* |
APPLE_ID |
Apple Developer email | macOS |
APPLE_PASSWORD |
App-specific password | macOS |
APPLE_TEAM_ID |
Apple Team ID | macOS |
APPLE_CERTIFICATE |
Base64-encoded .p12 certificate | macOS |
APPLE_CERTIFICATE_PASSWORD |
Certificate password | macOS |
*Required only for private repositories
npm run dev # Start with hot reload
npm run lint # Run ESLint
npm run format # Format with Prettier
npm run test # Run testsnpm run make # Build for current platform# Bump version (creates git tag)
npm run version:patch # 1.0.0 -> 1.0.1
npm run version:minor # 1.0.0 -> 1.1.0
npm run version:major # 1.0.0 -> 2.0.0
# Push tag to trigger release workflow
git push --follow-tagsGitHub Actions will automatically build for all platforms and create a release.
├── app.config.ts # App configuration (committed to repo)
├── forge.config.ts # Electron Forge build config
├── src/
│ ├── main.ts # Electron main process
│ ├── preload.ts # IPC bridge (window.api)
│ ├── database/
│ │ ├── connection.ts # Database singleton
│ │ ├── config.ts # Database path management
│ │ └── schema.ts # Schema definition
│ ├── ipc/
│ │ └── handlers.ts # IPC handlers
│ ├── types/
│ │ └── window.ts # TypeScript types for window.api
│ └── renderer/
│ ├── App.tsx # Main React component
│ ├── index.tsx # Entry point
│ ├── components/ # React components
│ └── styles/ # CSS
└── .env # Secrets only (gitignored)
-
Add create function in
src/database/schema.ts:function createYourTable(db: Database.Database): void { const tableExists = db .prepare( ` SELECT name FROM sqlite_master WHERE type='table' AND name='your_table' ` ) .get(); if (!tableExists) { db.exec(` CREATE TABLE your_table ( id TEXT PRIMARY KEY, name TEXT NOT NULL, created_at INTEGER NOT NULL ); `); } }
-
Call it from
initializeDatabase():export function initializeDatabase(db: Database.Database): void { createSettingsTable(db); createYourTable(db); // Add this }
-
Add handler in
src/ipc/handlers.ts:ipcMain.handle('myFeature:doSomething', async (_, arg: string) => { try { // Your logic here return { success: true, data: result }; } catch (error) { return { success: false, error: { code: 'ERROR', message: String(error) } }; } });
-
Add API method in
src/preload.ts:const myFeatureAPI = { doSomething: (arg: string) => ipcRenderer.invoke('myFeature:doSomething', arg), }; // Add to exposeInMainWorld contextBridge.exposeInMainWorld('api', { // ... existing APIs myFeature: myFeatureAPI, });
-
Add types in
src/types/window.ts:export interface MyFeatureAPI { doSomething: (arg: string) => Promise<IPCResponse<YourResultType>>; }
Settings are stored as key-value pairs:
// Set a setting
await window.api.settings.set('my_setting', 'value');
// Get a setting
const result = await window.api.settings.get('my_setting');
if (result.success) {
console.log(result.data); // 'value'
}
// Get all settings
const allSettings = await window.api.settings.getAll();The app automatically checks for updates on startup when running in production. Updates are downloaded in the background and users are prompted to restart.
For detailed setup instructions, see docs/updater.md.
| App | Description |
|---|---|
| NitroInbox | Email client for devs using on-devise AI |
| StoryFlow | AI-powered book writing app |
| Model Faceoff | Compare AI models side-by-side |
| MarkdownFlows | Markdown editor with workflows |
Built something with this template? Open a PR to add it here!
Elite Coders - Premium software development and consulting services.
Built by Emad Ibrahim.
MIT
