js

Build Offline-First Desktop Apps with Electron and Sequelize

Learn how to create cross-platform desktop apps using web skills and local databases with Electron and Sequelize.

Build Offline-First Desktop Apps with Electron and Sequelize

I’ve been thinking about desktop applications lately. Not the bulky, platform-specific software of the past, but the kind you can build with the web skills you already have. What if you could create a powerful app for Windows, Mac, or Linux that manages its own data, works perfectly offline, and feels right at home on the desktop? This question led me directly to a powerful combination: Electron and Sequelize.

Electron lets you build desktop apps using JavaScript, HTML, and CSS. It wraps your web app in a native shell. But a desktop app often needs to store data locally, not just in a browser’s temporary storage. That’s where Sequelize comes in. It’s a Node.js tool that lets you work with databases like SQLite or PostgreSQL using simple JavaScript objects. Put them together, and you have a complete toolkit.

Why does this matter? Because users expect applications to remember their work. A note-taking app should save your notes. A project management tool should keep your tasks. Relying on a remote server for every single action creates a fragile experience. With a local database, your app gains independence and speed.

Let’s look at how this starts. First, you set up a basic Electron app. Then, you add Sequelize to talk to a database. SQLite is a perfect partner here—it stores everything in a single file on the user’s computer, with no extra software needed.

Here’s a glimpse of the initial setup in your main Electron process file (main.js or index.js):

const { app, BrowserWindow } = require('electron');
const path = require('path');
const { Sequelize, DataTypes } = require('sequelize');

let mainWindow;
let sequelize;

async function createWindow() {
  // Define the database path inside the user's app data directory
  const userDataPath = app.getPath('userData');
  const dbPath = path.join(userDataPath, 'appdatabase.sqlite');

  // Initialize Sequelize with SQLite
  sequelize = new Sequelize({
    dialect: 'sqlite',
    storage: dbPath, // The file where data lives
    logging: false // Turn off SQL log noise in development
  });

  // Now define what a "Task" looks like in our database
  const Task = sequelize.define('Task', {
    title: {
      type: DataTypes.STRING,
      allowNull: false // This field must have a value
    },
    completed: {
      type: DataTypes.BOOLEAN,
      defaultValue: false // New tasks start as not done
    }
  });

  // This line creates the 'Tasks' table in the SQLite file
  await sequelize.sync();

  mainWindow = new BrowserWindow({ width: 800, height: 600 });
  mainWindow.loadFile('index.html');
}

app.whenReady().then(createWindow);

See what happened? We didn’t write a single line of SQL. We described our data in JavaScript, and Sequelize handled the rest. The sequelize.sync() call is powerful—it makes sure the database file and tables exist, creating them if needed. This is ideal for a desktop app installed on a user’s machine.

But an empty database isn’t useful. How do we actually create, read, update, and delete data? This is where the real power shines through. You perform these operations in your renderer process (the web page part of your app) by asking the main process to handle them securely.

Here’s a simple example. Imagine a function in your renderer that saves a new task:

// In your renderer process (e.g., a script tag in index.html)
async function addNewTask(taskTitle) {
  // Ask the main process to create the task
  const newTask = await window.electronAPI.createTask({ title: taskTitle });
  console.log('Task saved with ID:', newTask.id);
  return newTask;
}

And in your main process, you’d expose an API handler:

// In main.js, after defining the Task model
ipcMain.handle('create-task', async (event, taskData) => {
  try {
    const task = await Task.create(taskData);
    return task; // This gets sent back to the renderer
  } catch (error) {
    console.error('Failed to create task:', error);
  }
});

This pattern keeps your database operations secure in the main process, away from the direct reach of the web page, while still making them easy to use.

Have you considered what happens when you update your app and need to change the database structure? This is a common challenge. You might start with a simple Task model, but later need to add a dueDate field or a priority level. Sequelize migrations provide a safe, version-controlled way to manage these changes. You write a small script that alters the database from one state to the next. When a user updates your app, it can run the necessary migrations to update their local data file without losing any information.

Another point to consider is performance. SQLite is remarkably fast for local operations, but you should still be thoughtful. Avoid fetching ten thousand records at once to display in a list. Use Sequelize’s query features to get only what you need. For example, to find the first 20 incomplete tasks, you could write:

const pendingTasks = await Task.findAll({
  where: { completed: false },
  limit: 20,
  order: [['createdAt', 'DESC']] // Show newest first
});

This approach is efficient and keeps your application responsive.

The beauty of this setup is its simplicity for the end user. They install your app. It creates a small database file on their computer. They use the app, and their data is just there, persistent and private. There’s no configuration, no database server to install. It just works.

This combination opens doors. You can build a personal finance tracker, a local document catalog, a customer management system for a small shop, or a dedicated tool for any hobby or business that needs structured data. You use the same front-end skills you use for the web, but you deliver a solid, standalone desktop experience.

What kind of app have you wanted to build that could benefit from its own dedicated data store? The tools are ready and surprisingly straightforward to use.

I encourage you to start a small project. Try building a simple list manager or a habit tracker. You’ll quickly see how Electron and Sequelize fit together. The feeling of creating a real, installable application that manages its own data is incredibly rewarding. It bridges the gap between web development and traditional software in a very practical way.

If you found this walk-through helpful, please share it with a developer friend who might be curious about building desktop apps. Have you tried this stack before? What was your experience? Let me know in the comments—I’d love to hear what you’re building.


As a best-selling author, I invite you to explore my books on Amazon. Don’t forget to follow me on Medium and show your support. Thank you! Your support means the world!


101 Books

101 Books is an AI-driven publishing company co-founded by author Aarav Joshi. By leveraging advanced AI technology, we keep our publishing costs incredibly low—some books are priced as low as $4—making quality knowledge accessible to everyone.

Check out our book Golang Clean Code available on Amazon.

Stay tuned for updates and exciting news. When shopping for books, search for Aarav Joshi to find more of our titles. Use the provided link to enjoy special discounts!


📘 Checkout my latest ebook for free on my channel!
Be sure to like, share, comment, and subscribe to the channel!


Our Creations

Be sure to check out our creations:

Investor Central | Investor Central Spanish | Investor Central German | Smart Living | Epochs & Echoes | Puzzling Mysteries | Hindutva | Elite Dev | JS Schools


We are on Medium

Tech Koala Insights | Epochs & Echoes World | Investor Central Medium | Puzzling Mysteries Medium | Science & Epochs Medium | Modern Hindutva

Keywords: electron,sequelize,desktop apps,sqlite,nodejs



Similar Posts
Blog Image
Build a Distributed Task Queue System with BullMQ, Redis, and TypeScript: Complete Professional Guide

Learn to build a distributed task queue system with BullMQ, Redis & TypeScript. Complete guide with worker processes, monitoring, scaling & deployment strategies.

Blog Image
How to Integrate Prisma with GraphQL for Type-Safe Database Operations in TypeScript Applications

Learn to integrate Prisma with GraphQL for type-safe database operations in TypeScript apps. Build scalable APIs with auto-generated clients and seamless data layers.

Blog Image
Building Production-Ready Event-Driven Microservices with NestJS, RabbitMQ, and MongoDB

Build production-ready event-driven microservices with NestJS, RabbitMQ & MongoDB. Learn Saga patterns, error handling & deployment strategies.

Blog Image
Complete Guide to Building Type-Safe Next.js Applications with Prisma ORM Integration

Learn how to integrate Next.js with Prisma ORM for type-safe full-stack applications. Master database operations, schema management, and seamless deployment.

Blog Image
Master GraphQL Subscriptions: Apollo Server and Redis PubSub for Real-Time Applications

Master GraphQL real-time subscriptions with Apollo Server & Redis PubSub. Learn scalable implementations, authentication, and production optimization techniques.

Blog Image
Why Next.js and Prisma Are the Perfect Full-Stack Match for Modern Web Apps

Discover how combining Next.js with Prisma simplifies full-stack development, boosts performance, and streamlines your database workflow.