Introduction
I recently developed an offline-first mobile app using React Native. The app was designed for small businesses to manage their everyday activities, such as keeping track of customers, recording sales, managing products, and creating quotations and invoices.
While working on it, I decided to make the app fully offline. But why? I had a simple thought: for the basic tasks this app was designed to handle, why should users need an internet connection just to access their records or save new ones?
That's where the idea of offline-first comes in.
What is Offline-First?
An offline-first app is one that can perform all, or at least a critical part, of its core functionality without an internet connection. Instead of depending entirely on a remote server, it can use locally stored data to keep essential features working even when the device is offline.
In my application, I took this approach further by keeping all business data on the device itself, without cloud storage or automatic synchronisation.
Why SQLite?
React Native offers several options for local data storage, including SQLite-based solutions, AsyncStorage, MMKV, and database libraries such as WatermelonDB.
For my application, I chose SQLite. The reason was that the data I'm storing isn't just a collection of independent values. Different parts of the application are connected to each other. For example, customers, products, sales, quotations, and invoices all have relationships with one another. Since managing this structured data is a core part of my application, I wanted to use a relational database.
I chose OP-SQLite as the SQLite library because it is actively maintained and provides a native SQLite solution for React Native.
How the application is structured
Without a proper structure, an application can quickly become cluttered and difficult to maintain. So I decided to separate the UI, application logic, and database operations to keep things organised.
1. Screen — User Interface
This is where user interaction with the application happens. The screen components are primarily focused on rendering the UI. They display information, collect user input, and use the relevant hooks to get the data and perform actions.
2. Hook — Application Logic
Each main feature has its own hook that handles the operations related to that feature and provides the necessary data to the screen. This helps separate application logic from the UI, keeping the screen components clean and easier to maintain.
3. Repository — Data Access Operations
This is where communication with the database happens. The repository contains the operations required to insert, retrieve, update, and delete data in SQLite. The hooks call the relevant repository functions whenever they need to perform a database operation.
4. SQLite Database — Local Data Storage
This is where the application data is stored locally on the device. I use OP-SQLite to execute database operations requested by the repository layer. The results are then passed back through the repository and hook to update the UI.
The data is organised into separate tables based on the type of information being stored. For example, customer details are stored in the customers table, while product information is stored in the products table. Related records, such as sales and their individual items, are also organised separately.
export async function createCustomer( input: CreateCustomerInput, ): Promise<number> { const now = new Date().toISOString(); const result = await db.execute( ` INSERT INTO customers ( name, email, phone, company_name, tax_number, address, notes, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) `, [ input.name.trim(), input.email ?? null, input.phone ?? null, input.company_name ?? null, input.tax_number ?? null, input.address ?? null, input.notes ?? null, now, now, ], ); if (result.insertId === undefined) { throw new Error('Failed to retrieve the new customer ID.'); } return result.insertId; } 5. Data Backup and Restore
Since the app works entirely offline, users still need an easy way to keep a copy of their business data without relying on cloud backups. For this, I added a data backup and restore option. Users can export all their business data in JSON format and import it later when needed.
One important thing to keep in mind is that an exported backup only protects against device loss if the user stores it somewhere separate and safe.
Conclusion
I chose an offline-first approach for this application because I wanted to make it easier for small businesses to manage their everyday activities without dealing with account creation, server setup, or cloud dependencies.
Working on this application helped me think more about how users interact with an application and what they actually need from it. It was also a good learning experience in bringing different business features together while keeping the application simple and usable.
I'd be happy to hear your thoughts or answer any questions about the implementation.

