Extolling the Virtues of Dexie Cloud for Backendless Development
Dexie Cloud was released publicly today, and I love this product so much that I want to lend my voice to the marketing effort.
Dexie.js is an incredible library for interacting with IndexedDB in the browser. It wraps the sometimes-obtuse (and oft-buggy) IndexedDB APIs with much more developer-friendly interfaces. And it is fully reactive; if you change something in the database, your React, Svelte, Vue, Angular, or Solid.js (Solid support is weak) app will live update all by itself! It’s a truly heavenly coding experience.
However, web users do not live in a vacuum, and storing things in IndexedDB is fraught. The browser may delete that data on a whim, and who among us only has one device these days? You might be able to use IndexedDB as a cache, but it can’t be your source of truth.
Until today.
I signed up for the early alpha release of Dexie Cloud a long time ago… I think it might have been in 2020 (that infamous year), and I’ve been delighted with it since day one. I’ve contributed a fair number of bug reports to help the project succeed, and the maintainer, David Fahlander, has been wonderful to work with and extremely responsive in tracking down and fixing them.
Since I’ve been a alpha and beta tester for so long, I’m not really impacted by today’s announcement that Dexie Cloud is publicly available. But I’m still excited to see David’s hard work pay off, and I want to brag up the project to whatever audience I have.
What is Dexie Cloud?
Dexie Cloud is a sync service that backs up an IndexedDB database and syncs it to other devices. It is fully conflict-free, and features a simple, but powerful access model to allow users to share data with other users.
Dexie Cloud enables truly backendless development. Our app (Fablehenge) is built entirely on Dexie Cloud, and we don’t even have a backend repository. In its current incarnation, it doesn’t have a single fetch query built into it (other than those that Dexie Cloud makes on our behalf). Dexie Cloud takes care of all of the following:
- Authentication
- Authorization
- Backups
- Instantly syncing between devices
- Sharing data between users
- Conflict detection and resolution
So all our app has to do is define a data model that makes sense to Dexie.js, enable a couple of cloud features, and implement the user interface.
Every one of our data mutations are writes to IndexedDB (through Dexie), and everything else is taken care of. It’s honestly the closest thing to magic I’ve experienced in all my years of software development.
There are competing products that do similar things, such as RxDB and PouchDB, but neither provides as seamless an experience an Dexie Cloud, and they do require at least some backend configuration. Probably the closest production-capable alternative is the ever-so-overpriced Firestore.
What is it good for?
Dexie Cloud works beautifully with offline-first or progressive web applications, as you can store all your user content locally until the user connects to the network again.
If you’re on the bandwagon of folks who absolutely love Linear, Dexie Cloud is, in my opinion, the quickest way to generate a similar uber-performant sync-just-happens experience. Linear has full offline / PWA support. I don’t know a lot about how the product is implemented, but they use IndexedDB extensively to drive the instant responsiveness we love and I assume they have a custom in-house sync worker that behaves similar to Dexie Cloud. This was always possible, but Dexie Cloud brings this experience to those of us who don’t have massive venture capital investments behind us.
To be clear, Dexie Cloud isn’t suddenly the be-all-end-all solution for all web development problems. One example where it wouldn’t work too well is e-commerce; a buyer isn’t going to want to wait for the entire Amazon catalogue to sync to their device before they can search it! Dexie Cloud is more suitable for “app-like” experiences, where users primarily interact with content that they generated themselves, with an option on sharing a subset of that data with specific other users.
Basically, those experiences that are often considered prime candidates for a native app instead of a web site are also prime candidates for Dexie Cloud. You know how every e-commerce site has its own native app that is basically just a wrapper around their website, so you don’t bother to install it and just use the browser instead? That’s not what Dexie Cloud is for! Dexie Cloud is for web apps that behave like the native apps that have a prominent position on your desktop, dock, or home screen.
What does it feel like to develop with Dexie Cloud?
I’m not going to make this article into a Dexie Cloud tutorial (that may come eventually). Instead I want to describe the experience of using it.
The Dexie Cloud home page describes a four step installation process, and I can confirm that “it really is that easy.” You haven’t lived until you’ve experienced the joy of not having to set up an authentication service for that fun new hobby project you have in mind. The number of hours I’ve wasted trying to integrate e.g. Auth0 with Hasura or update to the latest broken Facebook Login button… Never again!
The hardest part is defining your Dexie database schema. IndexedDB is not relational, and any app of reasonable complexity is going to have “foreign-key-like” relationships in it. You have to manage those yourself, which is a little bit annoying when it comes to cascading deletes, but is otherwise rarely an issue.
As with all database systems, you have to be thoughtful about your indexes (yes, I know in SQL you mostly just rely on your ORM to be thoughtful about indexes for you and guess what, it’s probably doing a horrible job). Dexie is helpful here, featuring a built-in tool that warns you on the console if you make any queries that are not indexed. So the thoughtfulness is actually semi-automated! It doesn’t prevent you from creating unnecessary indexes, though, so be cautious of that, or your performance will suffer.
Once you’ve set up a few reactive queries and mutations, you can open two browsers to the same page on localhost. Dexie Cloud will prompt you to authenticate. Make some changes in one browser and they’ll show up almost instantly in the other. In 2024, you’d think this would be the default experience on all websites (it’s not like websockets are considered “new” anymore), but… well, it’s not. Maybe it will be in 2025, after everyone has started using Dexie Cloud. Let us hope!
Dexie Cloud doesn’t require a ton of configuration over Dexie.js itself, which released a pile of fancy new features (that I’ve been using for months in beta mode) yesterday. The project is strongly typed and easy to work with, debug, and introspect, and the reactive behaviour is rock solid. It now ships with a built-in cache so you only have to give minimal consideration to where your queries are run. Two copies of the same query won’t necessarily hit the user’s local disk twice.
Once your Dexie.js is set up, the cloud features are largely just a handful of
observables to introspect the current sync state and logged in user so you can
display that info in a meaningful way and protect authenticated routes. If you
use React, these observables can be listened to with useLiveQuery
, and if you
use Svelte, they are already Svelte stores, so you can access them directly
with a $
.
As your app progresses, you’ll likely want to take more control, but that control is opt-in. For example, the default log in UI is rather plain, so you’ll probably want to replace it with a custom UI that reacts to Dexie Cloud-provided state. Doing this isn’t hard, but it’s also not exactly trivial. Either way, it’s something you can postpone until you have the time to read the docs and do it right.
Similarly, if you want to customize the Dexie Authentication e-mail (something we still need to do for Fablehenge), you’re going to have to set up some email infrastructure. Or, if your site already has its own authentication infrastructure, you can remove the Dexie-provided solution and use yours instead. Again, this isn’t trivial, but it’s possible. Dexie does an amazing job of incremental complexity. You get a working app with a simple process, and you iterate until it looks and acts the way you need it to.
Pricing Strategy
Dexie Cloud has a pricing strategy that is very civilized, but only for certain workloads. You essentially only pay for monthly active users, where each user gets the first (up to) 30 days free. That’s 30 days of usage, not 30 consecutive days. If a user only logs in twice in one month, they still have 28 days to log in before you have to pay Dexie Cloud for that user’s activity.
This maps very well to “evaluation” licenses common in many subscription apps. If you have a user that is committed enough to burn through that 30 days of usage, then you hopefully have a user that is willing to pay you to continue connecting to the site. In this environment, only users that are paying you are also costing you, which is certainly the optimal way to incur costs.
It doesn’t work as well if your site’s visitors are not your paying customers. If you were building something like Patreon, for example, the people who sign up as creators are the ones who are making you money, and you don’t necessarily want to have to pay Dexie Cloud for every consumer of the content those creators create. Similarly, if your site as ad-supported, you generally want to maximize recurring time spent, which doesn’t (directly) map to the model of “every user who visits my site costs a seat on Dexie Cloud.”
If you find yourself in these situations, Dexie Cloud can also be purchased with a reasonably priced self-hosted solution. So if your annual Dexie Cloud costs are over $4000 USD, it might be time to switch to the self-hosted platform, (although I would personally value it in the six figures per year, since self hosting implies an additional dependency on a devops salary).
Suggested Tech Stack
We’ve been using Dexie Cloud with React for a few years now, and it’s rock solid. Every issue we’ve had has been the kind of bug you would expect in beta-quality code, and if you sign up for Dexie Cloud and it works flawlessly for you (which I am confident it will), I say you owe me a dime for ever bug I’ve reported (hint hint).
More recently, we rewrote our entire app to Svelte. When I first tested Dexie Cloud with Svelte, I had some issues with reactivity. To this day, I’m not certain if that’s because I was using Svelte incorrectly or because David has since fixed something upstream. Either way, it has now been equally rock solid to the React implementation.
The React implementation comes with a useLiveQuery
hook that accepts a
promise and magically (that’s my only explanation) updates itself any time
anything that is queried in that promise changes. Even if the promise queries
conditionally, Dexie still manages to always return the right results when the
data changes.
The Svelte implementation is even easier, since any Observable is a svelte store. However, there is a minor bug in Svelte that makes Typescript complain. I’ve created a couple of wrappers that fix typing and make querying a little simpler:
import { liveQuery } from "dexie";
import {
derived,
type Readable,
type Stores,
type StoresValues,
} from "svelte/store";
export function dexieStore<T>(querier: () => T | Promise<T>): Readable<T> {
// Svelte's Readable is typed incorrectly as subscribe must return an
// unsubscribe function but Svelte can also handle an object with an
// unsubscribe method, which is what dexie returns.
// (https://github.com/sveltejs/svelte/blob/main/packages/svelte/src/store/utils.js#L29)
return liveQuery(querier) as unknown as Readable<T>;
}
type Defined<T> = T extends undefined | null ? never : T;
/*
* Wrapper of derived to use the output of one Dexie store as input to another
* query function.
*
* coalesces nulls, so if the current value is null,
* the querier function will not be called.
*/
export function dexieDerived<S extends Stores, T>(
stores: S,
querier: (stores: Defined<StoresValues<S>>) => T | Promise<T>,
): Readable<T> {
return derived(stores, (current, set) =>
dexieStore(() => current && querier(current)).subscribe(set),
);
}
Every query we make in our Svelte app goes through one of these functions. Both return a store. Here’s an example of how we use them:
const slug = derived(page, ($page) => $page.params.slug);
$: book = dexieStore(() => getBookBySlug($slug));
$: draft = dexieDerived(book, (currentBook) =>
getSelectedDraft(currentBook?.id),
);
Now that I look at them, I’m pretty sure the book one should be using
dexieDerived
as well, but it happens to work because any time the slug
changes, the page is rerendered anyway. I’ll leave it as is above so you
can see both calls.
The getBookBySlug
and getSelectedDraft
functions are simply async methods
that query the db
and return the appropriate data.
You’ll notice that my tech “stack” just mentioned a frontend framework. The
type of app for which Dexie Cloud is intended, doesn’t need a backend. You
might need one or two serverless functions for sending e-mails or something, or
a scheduled task for backups, but your frontend does not need to communicate
with a server. You never need to call fetch
and you don’t need the devops
complexity of maintaining a server for SSR rendering.
It’s a truly freeing experience. I’m really excited with the product David has built here, and I’m proud to have watched it progress to final release from its earliest form. This is a really great product and you should try it out.