| name | desktop-apps |
| description | Desktop application development with Electron and Tauri |
| domain | domain-applications |
| version | 1.0.0 |
| tags | ["electron","tauri","desktop","cross-platform","native"] |
| triggers | {"keywords":{"primary":["electron","tauri","desktop app","cross-platform","native app"],"secondary":["ipc","preload","auto-updater","rust backend","menu","tray"]},"context_boost":["desktop","windows","macos","linux","native"],"context_penalty":["web","mobile","browser","api"],"priority":"medium"} |
Desktop Application Development
Overview
Building cross-platform desktop applications using web technologies with Electron and Tauri.
Electron
Main Process
import { app, BrowserWindow, ipcMain, dialog, Menu } from 'electron';
import path from 'path';
let mainWindow: BrowserWindow | null = null;
function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
minWidth: 800,
minHeight: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
},
titleBarStyle: 'hiddenInset',
frame: process.platform !== 'darwin',
});
if (process.env.NODE_ENV === 'development') {
mainWindow.loadURL('http://localhost:3000');
mainWindow.webContents.openDevTools();
} else {
mainWindow.loadFile(path.join(__dirname, '../dist/index.html'));
}
mainWindow.on('closed', () => {
mainWindow = null;
});
}
app.whenReady().then(() => {
createWindow();
createMenu();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
ipcMain.handle('dialog:openFile', async () => {
const result = await dialog.showOpenDialog(mainWindow!, {
properties: ['openFile'],
filters: [
{ name: 'Documents', extensions: ['txt', 'md', 'json'] },
],
});
if (!result.canceled && result.filePaths.length > 0) {
return result.filePaths[0];
}
return null;
});
ipcMain.handle('dialog:saveFile', async (_, content: string) => {
const result = await dialog.showSaveDialog(mainWindow!, {
filters: [{ name: 'JSON', extensions: ['json'] }],
});
if (!result.canceled && result.filePath) {
await fs.writeFile(result.filePath, content);
return result.filePath;
}
return null;
});
ipcMain.handle('app:getVersion', () => app.getVersion());
import { autoUpdater } from 'electron-updater';
autoUpdater.checkForUpdatesAndNotify();
autoUpdater.on('update-available', () => {
mainWindow?.webContents.send('update-available');
});
autoUpdater.on('update-downloaded', () => {
mainWindow?.webContents.send('update-downloaded');
});
ipcMain.handle('app:installUpdate', () => {
autoUpdater.quitAndInstall();
});
Preload Script
import { contextBridge, ipcRenderer } from 'electron';
contextBridge.exposeInMainWorld('electronAPI', {
openFile: () => ipcRenderer.invoke('dialog:openFile'),
saveFile: (content: string) => ipcRenderer.invoke('dialog:saveFile', content),
readFile: (path: string) => ipcRenderer.invoke('fs:readFile', path),
writeFile: (path: string, content: string) =>
ipcRenderer.invoke('fs:writeFile', path, content),
getVersion: () => ipcRenderer.invoke('app:getVersion'),
getPlatform: () => process.platform,
installUpdate: () => ipcRenderer.invoke('app:installUpdate'),
onUpdateAvailable: (callback: () => ) => {
ipcRenderer.(, callback);
ipcRenderer.(, callback);
},
: {
ipcRenderer.(, callback);
ipcRenderer.(, callback);
},
: ipcRenderer.(),
: ipcRenderer.(),
: ipcRenderer.(),
:
ipcRenderer.(, title, body),
});
{
{
: {
: < | >;
: < | >;
: <>;
: <>;
: <>;
: ;
: <>;
: ;
: ;
: ;
: ;
: ;
: <>;
};
}
}
Renderer (React)
function App() {
const [updateAvailable, setUpdateAvailable] = useState(false);
const [updateReady, setUpdateReady] = useState(false);
useEffect(() => {
const removeAvailable = window.electronAPI.onUpdateAvailable(() => {
setUpdateAvailable(true);
});
const removeDownloaded = window.electronAPI.onUpdateDownloaded(() => {
setUpdateReady(true);
});
return () => {
removeAvailable();
removeDownloaded();
};
}, []);
const handleOpenFile = async () => {
const filePath = await window.electronAPI.openFile();
if (filePath) {
const content = await window.electronAPI.readFile(filePath);
}
};
const handleSaveFile = () => {
content = .(data, , );
..(content);
};
(
);
}
() {
platform = ..();
(
);
}
Tauri
Rust Backend
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use tauri::{CustomMenuItem, Menu, MenuItem, Submenu};
use std::fs;
#[tauri::command]
fn read_file(path: String) -> Result<String, String> {
fs::read_to_string(&path).map_err(|e| e.to_string())
}
#[tauri::command]
fn write_file(path: String, content: String) -> Result<(), String> {
fs::write(&path, &content).map_err(|e| e.to_string())
}
#[tauri::command]
fn get_app_version() -> String {
env!("CARGO_PKG_VERSION").to_string()
}
#[tauri::command]
async fn perform_heavy_task(input: String) -> Result<String, String> {
tokio::task::spawn_blocking(move || {
(, input)
})
.
.(|e| e.())
}
() {
= Menu::()
.(Submenu::(
,
Menu::()
.(CustomMenuItem::(, ).())
.(CustomMenuItem::(, ).())
.(MenuItem::Separator)
.(MenuItem::Quit),
))
.(Submenu::(
,
Menu::()
.(MenuItem::Undo)
.(MenuItem::Redo)
.(MenuItem::Separator)
.(MenuItem::Cut)
.(MenuItem::)
.(MenuItem::Paste),
));
tauri::Builder::()
.(menu)
.(|event| {
event.() {
=> {
event.().(, {}).();
}
=> {
event.().(, {}).();
}
_ => {}
}
})
.(tauri::generate_handler![
read_file,
write_file,
get_app_version,
perform_heavy_task,
])
.(tauri::generate_context!())
.();
}
Tauri Configuration
{
"build": {
"beforeBuildCommand": "npm run build",
"beforeDevCommand": "npm run dev",
"devPath": "http://localhost:3000",
"distDir": "../dist"
},
"package": {
"productName": "My App",
"version": "1.0.0"
},
"tauri": {
"allowlist": {
"all": false,
"dialog": {
"all": true
},
"fs": {
Frontend Integration
import { invoke } from '@tauri-apps/api/tauri';
import { open, save } from '@tauri-apps/api/dialog';
import { readTextFile, writeTextFile } from '@tauri-apps/api/fs';
import { sendNotification } from '@tauri-apps/api/notification';
import { listen } from '@tauri-apps/api/event';
async function readFile(path: string): Promise<string> {
return invoke('read_file', { path });
}
async function writeFile(path: string, content: string): Promise<void> {
return invoke('write_file', { path, content });
}
async function openFileDialog() {
const selected = await open({
multiple: false,
filters: [{ : , : [, , ] }],
});
(selected && selected === ) {
content = (selected);
{ : selected, content };
}
;
}
() {
filePath = ({
: [{ : , : [] }],
});
(filePath) {
(filePath, content);
filePath;
}
;
}
(, () => {
file = ();
(file) {
}
});
() {
({ title, body });
}
Local Storage & Database
import Database from 'better-sqlite3';
const db = new Database('app.db');
db.exec(`
CREATE TABLE IF NOT EXISTS documents (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
content TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`);
const insertDoc = db.prepare(
'INSERT INTO documents (id, title, content) VALUES (?, ?, ?)'
);
const getDoc = db.prepare('SELECT * FROM documents WHERE id = ?');
const getAllDocs = db.prepare('SELECT * FROM documents ORDER BY updated_at DESC');
const updateDoc = db.prepare(
'UPDATE documents SET title = ?, content = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?'
);
const deleteDoc = db.prepare('DELETE FROM documents WHERE id = ?');
insertDoc.run(uuid(), 'New Document', '');
const doc = getDoc.get('doc-id');
const docs = getAllDocs.all();
Related Skills
- [[frontend]] - Web technologies
- [[system-design]] - Application architecture
- [[devops-cicd]] - Desktop app distribution