📦 Electron Billing Software

Complete Setup Guide - From Scratch to .EXE

React + Vite + Tailwind v4 + Framer Motion + SQLite

📋 Table of Contents

🚀 Prerequisites & Installation

Required Software

  1. Node.js LTS (Latest recommended version)
  2. Git (Optional but helpful)
  3. 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

2. Electron Integration

3. Offline SQLite Database

4. Generic Database API

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

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

  1. Customer Management: Add, edit, delete customers
  2. Product/Item Management: Inventory tracking
  3. Invoice Generation: Create invoices with line items
  4. GST Calculation: Automatic tax calculation
  5. Payment Tracking: Record payments against invoices
  6. Invoice Numbering: Auto-generate sequential numbers (e.g., INV/25-26/0001)
  7. Reports: Sales reports, GST reports, profit/loss
  8. PDF Export: Generate and print invoices
  9. Backup & Restore: Export/import database
  10. 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

Security

Performance

User Experience

🚀 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