📋 Table of Contents
- Prerequisites & Installation
- Step 1: Create Vite + React Project
- Step 2: Add Tailwind CSS v4
- Step 3: Add Framer Motion
- Step 4: Add Electron
- Step 5: Configure Package.json
- Step 6: Add SQLite Database
- Step 7: Setup Database Module
- Step 8: Configure Main Process (IPC)
- Step 9: Create Preload Script
- Step 10: Use Database in React
- Step 11: Build .EXE File
- Database API Reference
- Common Issues & Solutions
- Project Summary
🚀 Prerequisites & Installation
Required Software
- Node.js LTS (Latest recommended version)
- Git (Optional but helpful)
- Visual Studio Build Tools (For Windows - required for SQLite)
⚠️ Windows Users: You need Visual Studio Build Tools with "Desktop development with C++" workload installed for SQLite native modules to compile properly.
Check Your Installation
node -v
npm -v
1 Create Vite + React Project
Initialize Project
npm create vite@latest my-electron-app -- --template react
cd my-electron-app
npm install
💡 What this does: Creates a new React project using Vite as the build tool. Vite is much faster than Create React App and perfect for Electron.
2 Add Tailwind CSS v4
Install Tailwind
npm i -D tailwindcss @tailwindcss/vite
Update vite.config.js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [react(), tailwindcss()],
});
Update src/index.css
Replace all content with:
@import "tailwindcss";
Test Tailwind (src/App.jsx)
export default function App() {
return (
<div className="min-h-screen flex items-center justify-center text-3xl font-bold">
Tailwind v4 ✅
</div>
);
}
Run Development Server
npm run dev
Open http://localhost:5173 to verify Tailwind is working.
3 Add Framer Motion
Install Framer Motion
npm i framer-motion
Example Animation (src/App.jsx)
import { motion } from "framer-motion";
export default function App() {
return (
<div className="min-h-screen flex items-center justify-center">
<motion.div
initial={{ opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
className="rounded-2xl shadow-xl p-8 bg-white text-black"
>
<h1 className="text-2xl font-bold">Electron + Vite + Tailwind v4</h1>
<p className="mt-2 text-sm opacity-70">Framer Motion ✅</p>
</motion.div>
</div>
);
}
4 Add Electron
Install Electron Dependencies
npm i -D electron electron-builder concurrently wait-on
Create Electron Folder
mkdir electron
Create electron/main.cjs
⚠️ Important: Use .cjs extension to avoid "require is not defined" errors.
const { app, BrowserWindow } = require("electron");
const path = require("path");
const isDev = !app.isPackaged;
function createWindow() {
const win = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
},
});
if (isDev) {
win.loadURL("http://localhost:5173");
win.webContents.openDevTools();
} else {
win.loadFile(path.join(__dirname, "../dist/index.html"));
}
}
app.whenReady().then(() => {
createWindow();
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
app.on("window-all-closed", () => {
if (process.platform !== "darwin") app.quit();
});
5 Configure Package.json
Update package.json
Add these properties to your package.json:
{
"name": "my-electron-app",
"private": true,
"version": "1.0.0",
"type": "module",
"main": "electron/main.cjs",
"scripts": {
"dev": "concurrently -k \"npm:dev:vite\" \"npm:dev:electron\"",
"dev:vite": "vite",
"dev:electron": "wait-on http://localhost:5173 && electron .",
"build": "vite build",
"rebuild": "electron-rebuild -f -w better-sqlite3",
"dist": "npm run build && npm run rebuild && electron-builder"
},
"build": {
"appId": "com.yourcompany.myelectronapp",
"productName": "MyElectronApp",
"directories": {
"output": "release"
},
"files": [
"dist/**",
"electron/**",
"package.json"
],
"win": {
"target": "nsis"
}
}
}
Run Electron in Dev Mode
npm run dev
✅ Success: You should see the Electron window loading your React app from localhost:5173
6 Add SQLite Database
Install SQLite Library
npm i better-sqlite3
npm i -D electron-rebuild
Rebuild Native Modules
npm run rebuild
⚠️ Common Error: If you get "NODE_MODULE_VERSION" mismatch error, run the rebuild command above. This happens because SQLite is a native module that needs to be compiled for Electron's Node version.
💡 Why better-sqlite3? It's the fastest, most reliable SQLite library for Node.js and Electron. Perfect for offline desktop apps.
7 Setup Database Module
Create electron/db.cjs
const path = require("path");
const Database = require("better-sqlite3");
let db;
function initDb(userDataPath) {
const dbPath = path.join(userDataPath, "billing.sqlite");
db = new Database(dbPath);
// SQLite optimization settings
db.pragma("journal_mode = WAL");
db.pragma("foreign_keys = ON");
return dbPath;
}
/**
* Generic SQL executor
* - If query returns rows (SELECT), returns rows
* - Else returns info (changes, lastInsertRowid)
*/
function execute(query, params = []) {
if (!db) throw new Error("DB not initialized");
const trimmed = String(query).trim().toUpperCase();
const isSelect = trimmed.startsWith("SELECT") ||
trimmed.startsWith("PRAGMA") ||
trimmed.startsWith("WITH");
const stmt = db.prepare(query);
if (isSelect) {
const rows = stmt.all(params);
return { type: "rows", rows };
} else {
const info = stmt.run(params);
return {
type: "run",
changes: info.changes,
lastInsertRowid: info.lastInsertRowid,
};
}
}
module.exports = { initDb, execute };
💡 Key Feature: This module exports ONE generic execute() function that can run ANY SQL query. No need to create separate functions for each table or operation.
8 Configure Main Process (IPC)
Update electron/main.cjs
Add IPC handler and DB initialization:
const { app, BrowserWindow, ipcMain } = require("electron");
const path = require("path");
const { initDb, execute } = require("./db.cjs");
const isDev = !app.isPackaged;
function createWindow() {
const win = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
preload: path.join(__dirname, "preload.cjs"),
nodeIntegration: false,
contextIsolation: true,
},
});
if (isDev) {
win.loadURL("http://localhost:5173");
win.webContents.openDevTools();
} else {
win.loadFile(path.join(__dirname, "../dist/index.html"));
}
}
app.whenReady().then(() => {
// Initialize database
const dbPath = initDb(app.getPath("userData"));
console.log("SQLite DB:", dbPath);
// Register IPC handler for database operations
ipcMain.handle("db:execute", async (_event, { query, params }) => {
try {
const result = execute(query, params || []);
return { ok: true, result };
} catch (err) {
return { ok: false, error: err?.message || String(err) };
}
});
createWindow();
});
💡 Database Location: The database file is stored in
app.getPath("userData") which is:
- Windows: C:\Users\<username>\AppData\Roaming\<YourApp>\billing.sqlite
- macOS: ~/Library/Application Support/<YourApp>/billing.sqlite
- Linux: ~/.config/<YourApp>/billing.sqlite
9 Create Preload Script
Create electron/preload.cjs
const { contextBridge, ipcRenderer } = require("electron");
contextBridge.exposeInMainWorld("db", {
execute: async (query, params = []) => {
const resp = await ipcRenderer.invoke("db:execute", { query, params });
if (!resp.ok) throw new Error(resp.error);
return resp.result;
},
});
💡 What is Preload? The preload script acts as a secure bridge between your React app (renderer) and Electron's main process. It exposes only the functions you explicitly allow, keeping your app secure.
Add TypeScript Types (Optional)
Create src/types/global.d.ts for better IDE support:
export {};
declare global {
interface Window {
db: {
execute: (query: string, params?: any[]) => Promise
;
};
}
}
10 Use Database in React
Example: src/App.jsx
import { useEffect, useState } from "react";
export default function App() {
const [logs, setLogs] = useState([]);
const [customers, setCustomers] = useState([]);
const log = (msg) => setLogs((p) => [...p, msg]);
useEffect(() => {
(async () => {
try {
// 1) Create table
await window.db.execute(`
CREATE TABLE IF NOT EXISTS customers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
phone TEXT,
created_at TEXT DEFAULT (datetime('now', '+5 hours', '+30 minutes'))
)
`);
log("✅ customers table ready");
// 2) Insert sample data
const insertRes = await window.db.execute(
`INSERT INTO customers (name, phone) VALUES (?, ?)`,
["Manesh", "9999999999"]
);
log(`✅ Inserted customer, rowid: ${insertRes.lastInsertRowid}`);
// 3) Read data
const readRes = await window.db.execute(
`SELECT * FROM customers ORDER BY id DESC`
);
setCustomers(readRes.rows);
log(`✅ Loaded ${readRes.rows.length} customers`);
} catch (e) {
log("❌ " + e.message);
}
})();
}, []);
return (
<div className="min-h-screen p-6 bg-zinc-950 text-white">
<h1 className="text-2xl font-bold">Billing App (SQLite Offline)</h1>
<div className="mt-6 grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="rounded-xl border border-white/10 p-4">
<h2 className="font-semibold mb-2">Customers</h2>
<div className="space-y-2">
{customers.map((c) => (
<div key={c.id} className="rounded-lg bg-white/5 p-3">
<div className="font-semibold">{c.name}</div>
<div className="text-sm opacity-70">{c.phone}</div>
<div className="text-xs opacity-50">{c.created_at}</div>
</div>
))}
</div>
</div>
<div className="rounded-xl border border-white/10 p-4">
<h2 className="font-semibold mb-2">Logs</h2>
<div className="text-sm opacity-80">
{logs.map((l, i) => (
<div key={i}>• {l}</div>
))}
</div>
</div>
</div>
</div>
);
}
✅ Indian Standard Time (IST): Notice the '+5 hours', '+30 minutes' in the created_at default? This ensures all timestamps are stored in Indian Standard Time, not UTC.
11 Build .EXE File
Build for Windows
npm run dist
Output Location
Your .exe installer will be created in:
release/MyElectronApp Setup 1.0.0.exe
✅ Complete: You now have a fully functional Windows installer that includes:
- Your React UI
- Offline SQLite database
- All dependencies bundled
- Automatic updates support (if configured)
⚠️ Important: The rebuild step in the dist script is crucial. Without it, the SQLite native module won't work in the packaged app.
📚 Database API Reference
Core Function
window.db.execute(query, params)
Response Format
| Query Type |
Response Structure |
Example |
| SELECT, PRAGMA, WITH |
{ type: "rows", rows: [...] } |
Array of row objects |
| INSERT, UPDATE, DELETE |
{ type: "run", changes: N, lastInsertRowid: ID } |
Affected row count and last ID |
Example Operations
1. Create Table
await window.db.execute(`
CREATE TABLE IF NOT EXISTS products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
price REAL NOT NULL,
stock INTEGER DEFAULT 0
)
`);
2. Insert Data
const result = await window.db.execute(
`INSERT INTO products (name, price, stock) VALUES (?, ?, ?)`,
["Laptop", 45999.99, 10]
);
console.log("Inserted ID:", result.lastInsertRowid);
3. Select Data
const result = await window.db.execute(
`SELECT * FROM products WHERE price > ?`,
[10000]
);
console.log("Products:", result.rows);
4. Update Data
const result = await window.db.execute(
`UPDATE products SET stock = stock - ? WHERE id = ?`,
[1, 5]
);
console.log("Updated rows:", result.changes);
5. Delete Data
const result = await window.db.execute(
`DELETE FROM products WHERE stock = 0`
);
console.log("Deleted rows:", result.changes);
6. Transactions (Multiple Operations)
try {
await window.db.execute("BEGIN TRANSACTION");
await window.db.execute(
`INSERT INTO invoices (customer_id, total) VALUES (?, ?)`,
[1, 5000]
);
await window.db.execute(
`UPDATE customers SET balance = balance + ? WHERE id = ?`,
[5000, 1]
);
await window.db.execute("COMMIT");
} catch (error) {
await window.db.execute("ROLLBACK");
throw error;
}
💡 Best Practice: Always use parameterized queries (?) instead of string concatenation to prevent SQL injection and handle special characters properly.
🔧 Common Issues & Solutions
1. "require is not defined in ES module scope"
Problem: Electron main file is .js while package.json has "type": "module"
Solution: Use electron/main.cjs and "main": "electron/main.cjs" in package.json
2. NODE_MODULE_VERSION Mismatch
Problem: better-sqlite3 compiled for wrong Node version
Solution: Run npm run rebuild or npx electron-rebuild -f -w better-sqlite3
3. Blank White Screen After Build
Problem: Electron not loading correct dist/index.html path
Solution: Verify this line in main.cjs: win.loadFile(path.join(__dirname, "../dist/index.html"))
4. Database File Not Found
Problem: Can't find database after app restart
Solution: Make sure you're using app.getPath("userData") for database storage, not relative paths
5. Window.db is Undefined
Problem: React can't access window.db
Solution: Ensure preload.cjs is loaded in BrowserWindow webPreferences and contextIsolation is true
📦 Project Summary
What We Built
1. Desktop App Foundation
- Windows desktop application using Electron
- UI built with React + Vite
- Styling using Tailwind CSS v4 (className)
- Animations using Framer Motion
2. Electron Integration
- Dev mode: Loads http://localhost:5173
- Production: Loads bundled dist/index.html
- Packaging: Creates .exe using electron-builder (NSIS installer)
3. Offline SQLite Database
- Local database using SQLite with better-sqlite3
- Database stored in
userData directory (persists across updates)
- No internet required - fully offline
- Fast, reliable, and perfect for billing applications
4. Generic Database API
- One function only:
window.db.execute()
- Can run ANY SQL query (CREATE, INSERT, UPDATE, DELETE, SELECT)
- No need for separate functions per table
- Dynamic table creation supported
Architecture Flow
React Component (renderer)
↓
window.db.execute(sql, params)
↓
Preload Script (secure bridge)
↓
IPC Channel: "db:execute"
↓
Main Process (Electron)
↓
SQLite Database (better-sqlite3)
↓
Response back to React
💡 Security: React cannot directly access SQLite. The preload script acts as a controlled bridge, exposing only what you explicitly allow, keeping your app secure.
Key Features
- ✅ Completely offline - no internet needed
- ✅ Fast and responsive UI
- ✅ Secure architecture (contextIsolation + preload)
- ✅ Professional packaging as Windows .exe
- ✅ Database persists across app updates
- ✅ Indian Standard Time (IST) support built-in
- ✅ Dynamic table creation
- ✅ Transaction support
File Structure
my-electron-app/
├── electron/
│ ├── main.cjs # Main Electron process
│ ├── preload.cjs # Secure bridge script
│ └── db.cjs # Database module
├── src/
│ ├── App.jsx # Main React component
│ ├── main.jsx # React entry point
│ ├── index.css # Tailwind imports
│ └── types/
│ └── global.d.ts # TypeScript definitions
├── dist/ # Build output (generated)
├── release/ # .exe files (generated)
├── package.json # Project configuration
└── vite.config.js # Vite configuration
🎯 Next Steps for Billing Software
Recommended Database Schema
For a complete billing application, consider these tables:
-- Customers table
CREATE TABLE customers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
phone TEXT,
email TEXT,
address TEXT,
gstin TEXT,
balance REAL DEFAULT 0,
created_at TEXT DEFAULT (datetime('now', '+5 hours', '+30 minutes'))
);
-- Products/Items table
CREATE TABLE items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
description TEXT,
price REAL NOT NULL,
stock INTEGER DEFAULT 0,
hsn_code TEXT,
gst_rate REAL DEFAULT 18,
created_at TEXT DEFAULT (datetime('now', '+5 hours', '+30 minutes'))
);
-- Invoices table
CREATE TABLE invoices (
id INTEGER PRIMARY KEY AUTOINCREMENT,
invoice_number TEXT UNIQUE NOT NULL,
customer_id INTEGER NOT NULL,
date TEXT DEFAULT (datetime('now', '+5 hours', '+30 minutes')),
subtotal REAL NOT NULL,
tax_amount REAL NOT NULL,
total REAL NOT NULL,
status TEXT DEFAULT 'pending',
notes TEXT,
FOREIGN KEY (customer_id) REFERENCES customers(id)
);
-- Invoice Items (line items)
CREATE TABLE invoice_items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
invoice_id INTEGER NOT NULL,
item_id INTEGER NOT NULL,
quantity INTEGER NOT NULL,
price REAL NOT NULL,
tax_rate REAL NOT NULL,
amount REAL NOT NULL,
FOREIGN KEY (invoice_id) REFERENCES invoices(id),
FOREIGN KEY (item_id) REFERENCES items(id)
);
-- Payments table
CREATE TABLE payments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
invoice_id INTEGER NOT NULL,
amount REAL NOT NULL,
payment_method TEXT,
payment_date TEXT DEFAULT (datetime('now', '+5 hours', '+30 minutes')),
notes TEXT,
FOREIGN KEY (invoice_id) REFERENCES invoices(id)
);
-- Settings table
CREATE TABLE settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
Features to Implement
- Customer Management: Add, edit, delete customers
- Product/Item Management: Inventory tracking
- Invoice Generation: Create invoices with line items
- GST Calculation: Automatic tax calculation
- Payment Tracking: Record payments against invoices
- Invoice Numbering: Auto-generate sequential numbers (e.g., INV/25-26/0001)
- Reports: Sales reports, GST reports, profit/loss
- PDF Export: Generate and print invoices
- Backup & Restore: Export/import database
- Search & Filter: Find customers, invoices quickly
Useful Libraries for Billing Features
| Feature |
Library |
Purpose |
| PDF Generation |
jsPDF / pdfmake |
Create invoice PDFs |
| Date Handling |
date-fns |
Format dates, calculate periods |
| Number Formatting |
numeral.js |
Currency, percentage formatting |
| Excel Export |
xlsx |
Export reports to Excel |
| Form Validation |
react-hook-form |
Handle form inputs |
| Charts |
recharts |
Sales charts, analytics |
💡 Best Practices
Database
- ✅ Always use parameterized queries (?)
- ✅ Enable foreign keys for data integrity
- ✅ Use transactions for multi-step operations
- ✅ Create indexes on frequently queried columns
- ✅ Regular database backups
- ✅ Validate data before inserting
Security
- ✅ Keep contextIsolation enabled
- ✅ Never use nodeIntegration: true
- ✅ Only expose necessary functions in preload
- ✅ Validate all user inputs
- ✅ Use latest Electron version for security patches
Performance
- ✅ Use WAL mode for better concurrency
- ✅ Batch multiple inserts in transactions
- ✅ Create indexes for search operations
- ✅ Lazy load data (pagination)
- ✅ Optimize React re-renders (useMemo, useCallback)
User Experience
- ✅ Show loading states during database operations
- ✅ Display error messages clearly
- ✅ Implement undo functionality for critical actions
- ✅ Auto-save drafts
- ✅ Keyboard shortcuts for common actions
- ✅ Offline-first mindset (no network dependencies)
🚀 Quick Reference Commands
| Command |
Purpose |
npm run dev |
Start development mode (Vite + Electron) |
npm run build |
Build React app for production |
npm run rebuild |
Rebuild native modules for Electron |
npm run dist |
Create .exe installer |
npm install |
Install all dependencies |