clientful

clientful

How to create real-time collaborative offline-first apps without traditional backend services or centralized databases Instead of: Client + Backend + DB + Cache + Files storage + Realtime Layer + Kubernetes + Servers We will need only: Client + Files storage + Realtime Layer
 
 

From the Author

notion image
 
Hello, dear reader. My name is David Shekunts, and I am a Tech Lead with 12 years of experience.
clientful is not an established term. I use it here because it best describes the class of solutions I discuss in this article.
This approach is already proving viable and unlocks not only technical advantages, but also meaningful business advantages when building products.
In this work, I want to share my experience, principles, and practical techniques for building full-fledged SaaS-like applications without a backend.
📮
Subscribe on X channel [ $davids.sh ] , where announcements of new chapters and updates to existing ones will be posted.

i. Introduction

These are the conditions we want to satisfy while removing server-side applications and databases:
 
  1. All business logic and computation must happen on the client
  1. All database work must happen on the client side
  1. The system must support multiple devices for the same user
  1. The system must be multi-user
  1. The system must also be able to store files: photos, videos, audio, PDFs, etc.
  1. The system must be secure
 
To achieve this “without a backend”, we need the following:
 
  1. File Storage – external file storage*: S3-like, Google Drive-like, or IPFS-like services for persisting and transferring data and files between devices and users. At the same time, users can use their own storage without requiring us to provide our own backend.
  1. WebSocket-like Broker for routing messages between clients. A clientful implementation can use almost any service that lets clients connect to a room and broadcast messages to all connected users.
 
* – there are ways to avoid external file storage while preserving collaboration, but the average implementation becomes much more complex. These options usually make sense only in very narrow cases.
 

ii. Advantages

What you get out of the box:

a. Business

 
  1. Zero infrastructure cost – for some businesses, the cost of storing user data and processing user requests is a barrier that prevents them from surviving long enough to reach payback. In clientful, all compute resources and storage systems are shifted to the user side, which means you do not have to pay for servers, applications, databases, caches, and similar infrastructure at all.
  1. Integration speed – the whole system is a set of a client application, for example HTML/CSS/JS, S3-like storage, and a WebSocket server. This makes it possible to integrate such products into third-party systems very quickly.
  1. Own Your Data – data can be stored entirely on the user side and can be fully encrypted, which can become a critical advantage over competitors.
  1. 99.9999% uptime – the application either exists on the user’s devices and in large clouds such as Google Drive, or it is connected to your S3 server. As a result, the system has the lowest possible probability of failure.
  1. Maximally interactive UI — the user never waits for a “server response”. Everything changes locally, so the application feels extremely fast. As soon as external changes arrive, they are immediately reflected in the UI.
  1. The application always remains with the user – as long as the user has a copy of your HTML/CSS/JS, the application remains available and works. You can sell the product with a guarantee that the user will be able to use it from that moment onward.
  1. Anti Vendor-lock – all data will be represented as JSON files organized in folders, meaning that if a user urgently needs to migrate (for example, due to distrust of the platform, or new country regulations), they can migrate freely between the file system, Google Drive-like providers, S3-like providers, and similar storage backends.
 

b. Technical

 
  1. No vendor lock-in — there is no dependency on server providers at all.
  1. Infinite scalability by design — every new user brings their own resources, so load does not grow.
  1. Development speed – no staging or dev servers, no deployment of ten applications, no Kubernetes, and not even Docker. You need nothing except to build a client-side web or native application that only requires a CDN.
  1. E2E testing out of the box – the entire system is literally just the client, so writing a client test is effectively writing an E2E test that covers the whole infrastructure.
  1. LLM-optimized – the whole application is literally client-side code, which means the LLM only needs the application context to understand the entire system.
  1. Operational stability – how often has a technology failed simply because the backend application, server, or database went down? In this case, we have offline-first behavior.
  1. Offline-first out of the box — the application works without an internet connection, and the data is always available.
  1. Non-internet connection – devices can communicate over any protocol, for example LAN, BLE, I2C, and similar options.
  1. Option for centralization – in practice, if your project needs a central “database” that stores all or part of the data, you can connect your own central S3-like or Google Drive-like storage and publish part of the users’ content there.
  1. Real-time-native – as soon as any change reaches the client, the interface immediately reflects it.
 

c. Security

 
  1. Smaller attack surface — no server means no server-side attack point for hackers.
  1. Privacy by default — data does not leave the user’s device, so you do not have to think about GDPR exposure and data leaks in the same way.
  1. Theoretically unbreakable system – encryption strategies are possible where keys are created and always remain on users’ devices. They literally do not exist in any centralized place, and even if someone downloads the data from file storage, they cannot decrypt it back into readable form.
 

iii. Implementation

To implement a clientful application, we need the following components:
 
  1. Data storage algorithm
  1. Storage
  1. Communication protocol
  1. Auth / Identity
  1. Encryption
 

a. Data Storage Algorithm

We need a way to store data on the client side while supporting:
 
  1. Multiple devices – when a user opens the application on another device, for example on both a computer and a phone, they must be able to work with the same data.
  1. Multi-user collaboration – users must be able to work collaboratively on the same data, both separately while seeing saved changes and in real time.
 
It is important to clarify that there are two models for solving this:
 
  1. Pure P2P – every node is equal and can make decisions fully independently.
  1. Host-based P2P – at a specific point in time, or for specific operations, one of the nodes becomes the main node and processes requests from other nodes.
 
Several algorithms implement these models:
 
  1. Pure P2P
    1. CRDT — data structures and algorithms that allow multiple nodes to independently change the same state and then merge those changes without conflicts so that all replicas converge to the same result.
    2. CouchDB — an eventual-consistency document database and replication protocol: nodes exchange versioned documents, while conflicts are stored as alternative versions and require a resolution strategy. In other words, merge is not “magical” as in CRDT; it is defined by the application’s model or logic.
    3. Blockchain — a distributed ledger where nodes agree on a single transaction order through a consensus mechanism such as PoW or PoS. It is “democratic” in the sense that there is no trusted center, but that “democracy” comes at the cost of expensive consensus.
    4. Git-like log – users, either manually or automatically according to application logic, create branches for changes and then merge them into each other when exchanging messages.
  1. Host-based P2P
    1. OT — an approach to collaborative editing where changes are represented as operations, such as insert/delete/replace, and competing operations are transformed relative to each other so that all participants converge to the same result.
    2. PAXOS / Raftconsensus algorithms for log replication in a cluster: a group of nodes elects a leader, and the leader accepts commands, writes them to a log, and replicates them to a quorum.
 
Libraries that implement this functionality:
 
  1. Yjs — one of the most widely used CRDT libraries
  1. Automerge – another CRDT implementation, but it stores data as an immutable graph, similar to a Git tree
  1. OrbitDB – a database based on an “operation log” over CRDT, with a built-in libp2p communication protocol over IPFS and extended operations over CRDT structures
  1. PouchDB – a client-side implementation of the CouchDB protocol
  1. Gun.js — a distributed graph database with P2P synchronization
  1. Hypercore – a distributed append-only immutable log, with a large ecosystem around it
  1. SQLite + CRDT – libraries that combine CRDT and SQLite
    1. CR-Sqlite
    2. Sqlite-sync
 

In practice, only CRDT really makes sense

Yes, I listed several algorithms above, but if we are being genuinely honest, only CRDT truly satisfies all the conditions required for clientful:
 
  1. OT – most often still assumes a single node that collects the sequence of operations.
  1. CouchDB – the protocol was designed around the idea of an eventual-consistency database where clients can connect to central nodes and transform part of the data. Attempts to make it fully P2P do exist today, but they are still highly experimental.
  1. PAXOS / Raft – these are possible, but they only have an advantage over CRDT when (a) you need strict transactionality and (b) you can trust one of the participants. The implementation complexity is so high that PAXOS / RAFT only makes sense when there is no other viable option.
  1. Blockchain – it is practically similar to PAXOS / Raft, but requires trust not in one node, but in a majority of nodes. It has the same high technological complexity, so its use should require an extremely strong reason.
  1. Git-like log – an interesting model, but it assumes either user-side conflict resolution or simply does not fit many business cases.
 
So, in practical terms, CRDT will be the sensible choice in 90% of cases.
 

Bonus

Most CRDT implementations give us a set of methods for working with key-value storage, but sometimes we need more: indexing data, building aggregates, running complex queries, and so on. In those cases, we can store both the CRDT structures themselves and their duplicates in other embedded stores specifically for these tasks:
 
  1. SQLite – even without built-in CRDT support, you can store CRDT content directly in a SQLite database. This can be useful if, for example, you want to index part of that content.
  1. PGlite – similar to SQLite, but literally PostgreSQL compiled to WASM, and not only working, but also supporting extensions.
  1. DuckDB – an embedded analytical database that can even load files by URL, for example directly from S3, which makes it an ideal candidate for use in clientful.
  1. LanceDB – similar to DuckDB, but vector-oriented and designed out of the box for storage in external file stores.
 

b. Storage

Where and how we will store data on each node, and what kind of File Storage we will use.
 
Here we divide storage into remote and local options.
 
  1. Remote
    1. Google Drive-like – an ideal candidate, because most clients already have this kind of storage. That means file storage can be fully moved to the user side.
    2. S3-like – any MinIO, rustfs, or cloud S3 is well suited for this task.
    3. CDN-like – using any CDN provider to upload and download files.
    4. IPFS / Filecoin — decentralized crypto storage.
    5. Nostr-like — a decentralized protocol for storing and exchanging data without a server.
    6. Torrent – again, why not? It is literally a protocol for P2P data transfer and storage.
    7. Git-like – using Git repositories for file persistence and transfer? Why not?
  1. Local
    1. Browser Local Storage / IndexedDB / OPFS — native browser storage, ideal for client-side-first applications.
    2. File System – literally the local file system.
 

c. Communication

The main task of the communication protocol is to let clients subscribe to a “room” by its unique ID, usually a UUID, and send or receive data deltas / snapshots to and from all participants. Given the rest of the clientful stack, nothing else is required from the communication protocol, because authentication and data synchronization are handled by other parts of the system.
 
Protocol options:
 
  1. WebRTC – enables P2P connections between clients
  1. WebSocket – persistent two-way communication over TCP
  1. QUIC – a newer UDP-based protocol with TCP-like guarantees and TLS security
 
This is a large and complex topic, especially when we try to establish P2P connections between clients. I recommend studying the code and work of Holepunch; you will find a lot of useful and interesting material on this subject:
 
 

d. Auth / Identity

How we authenticate and even authorize clients:
 
  1. Google Drive-like OAuth
      • We use an OAuth provider as the identity source: Google, Microsoft, and so on.
      • Tokens are needed not only for login, but also for access to the user’s File Storage, such as Drive, Dropbox, etc. In other words, authorization for file read/write operations is “built in” through the provider.
      • Pros: minimal UX friction, a clear access model, and many users already have such accounts.
      • Cons: dependency on the provider, API limits/quotas, and potential complexity around shared folders and access between users.
  1. Sign-in with Ethereum (SIWE)
      • Identity means ownership of the wallet’s private key; login means signing a challenge message with a nonce.
      • Authorization between users can be built through on-chain or off-chain ACLs: for example, “who can read/write” is determined by a list of addresses and roles.
      • Pros: portable identity, no central authority, convenient for web3/community products.
      • Cons: UX, seed phrases, wallets, chain-specific behavior, and greater difficulty for a mass audience; an external data delivery channel such as storage or a broker is still often needed.
  1. Local-first keys (device/user keys)
      • Identity is built around locally generated keys, for example Ed25519/X25519:
        • device key + user key and/or workspace keys.
      • Public keys are distributed through File Storage, a registry, or invites; private keys never leave the devices.
      • Authorization equals signatures and capability tokens, for example “this key can write to this folder/document”.
      • Pros: maximally aligned with the clientful/privacy-by-default idea, and E2E encryption is possible.
      • Cons: access recovery, key rotation, onboarding new devices, and access revocation.
  1. AT Protocol (Bluesky stack)
      • Identity equals a DID, Decentralized Identifier, bound to a handle/domain; account portability is provided by the protocol.
      • AT can be used as an “identity + directory + transport” layer, while application data is stored in File Storage with permissions bound to DIDs.
      • Pros: decentralized identity, built-in replication/federation mechanisms, and potential convenience for social-like products.
      • Cons: the stack is relatively specific; you will need to carefully separate what lives in AT from what lives in your file storage / CRDT data.
 

e. (coming soon) Encryption

(I will postpone this chapter for now because the topic is very large.)
 

f. Backend-like

From time to time, we inevitably encounter situations where we need some form of backend. For example, we may want to build a crawler that collects information from websites and writes it into our File DB.
 
For this exact task, we could build a small backend, for example a serverless function, that works with our File DB. But this work is focused on exploring options where there should be no backend on our side.
 
So what are the options? Good old self-hosting.
 
We can embed this kind of functionality directly into the user’s application and run these processes in the background.
 
“But that would be a backend” – not in the traditional sense. Such an application can simply live on the user’s computer and run in the background, or the user can deploy it on their own home server, as people now do with the popular OpenClaw.
 
Yes, it is not ideal, but it follows the main principles of clientful: compute power and storage live on the client side.
 

iv. Challenges

What you need to consider when using clientful, from more serious aspects to less serious ones:
 

1. Dependency on the CRDT Implementation

First, there is no single correct approach to implementing CRDTs: there are variants based on the simplest structures, and there are Merkle Tree-based CRDTs that store the entire history of changes with a built-in rollback mechanism.
 
Accordingly, if your clients use different programming languages, you need to hope that there is a library available for all of them.
 
Libraries such as Yjs have implementations for a large number of languages, plus a Rust version that you can connect to almost any other language via FFI. In practice, the problem is mostly solvable, but not without caveats.
 

2. Collaboration Only

Put differently: clientful is not suitable for systems where you need to resolve or prevent competition.
 
Example: wallets with money and the ability to send funds to each other. Every operation over data available to the client is considered valid by all other clients, which means any transaction can be made from any wallet to another.
 
The only option is to embed operation-trust validation into the system itself. For example, use private-key signatures and public-key verification for every operation:
 
  • Use private-key signatures and public-key verification for every operation, and mark operations as untrusted if verification fails
  • Use a Merkle Tree to recompute entity states while taking signatures into account
  • Use blockchain-like structures
  • Or assign one master client that can decide whether a given action is legal
 
But first, the amount of required code is incomparably larger than in any centralized system; second, it is easy to miss edge cases. So when competition matters, it is better to keep it centralized.
 

3. Inconsistent States

The most common problem:
 
  1. A group of people is working on a document, and one of them boards a plane
  1. While offline, that person deletes the document.
  1. The others continue working on that document.
  1. When the offline user comes back online, their state synchronizes; since nobody “cancelled the deletion”, the document is actually “deleted”, even though colleagues were working on it
 
To fix such situations, you need application-specific business-logic rules, for example:
 
  • When “deleting”, first move the document to “trash” instead of deleting it; when coming back online, if work was done on the document, restore it automatically
  • Or prohibit deleting documents while offline
  • Or create a “deletion” task, but do not apply it until the user comes online and sees colleagues’ changes
 
Solutions exist, but you must always remember these CRDT-specific behaviors.
 

4. “If X, then Y is allowed/forbidden” does not work

Even if you wrote code that checks that a client “cannot do X if Y does or does not exist”, for example create a comment if there is no article, it will not work that way:
 
  1. Client A creates a post
  1. Client B receives this post
  1. Client A creates a comment on it
  1. Client B sees the comment
  1. Client A deletes the post and, along with it, all comments, in this case one comment
  1. Client A tries to create a new comment on the post, but is prevented from doing so because the post no longer exists
  1. Client B has not yet received information about the deleted post, so they calmly create a second comment
  1. Clients A and B receive each other’s changes, and the result is that the post is deleted while the second comment still exists
 
Therefore, the logic “if X, then forbid Y” practically does not work, because there is no central synchronization point that will always enforce this rule.
 
There are three options here:
 
  1. Aftermath Compensation – after state updates, check whether there are comments without posts and delete them
  1. Preemptive Compensation – when creating a comment, in a local transaction, “recreate the post if it suddenly does not exist”. In other words, you send all clients both your comment and the post it belongs to; therefore, even if it was deleted on Client A, it will be created there again.
  1. Conflict management – detect this conflict and show users a message about it; for example, do not fully delete the post until the deletion has synchronized across most nodes
 
In other words, solution options exist. They simply break the standard paradigm for people used to consistent CP applications, and require rethinking how this kind of logic is designed.
 
If you are interested in the kinds of problems this creates and how they are solved, read more about Eventual Consistency.
 

5. Classic Problems of Offline Applications

In practice, a clientful application is an offline application that can synchronize with others when it comes online, which means it inherits all the problems of offline applications:
 
  • Data migrations – you must remember that the client already has data locally. You cannot simply “delete a key and replace it with a new one”; at some point, you will have to migrate data from the old format to the new one
  • If state breaks, you cannot open a central database and inspect what happened. You will need mechanisms for remotely checking what happened to the client’s state
  • Different versions on different devices
 
On top of that, you get another interesting difficulty of offline-first applications, which can operate fully autonomously: different clients, and even different devices of the same client, may run different versions of your application, for example if you ship native apps or cache the application without forcing updates.
 
This means that old and new clients can work on the same stored data at the same time.
 
Yes, this problem can occur in any application. But here you do not have a central database or central backend that can reject changes from old versions, expose v2 endpoints, let you manually rebuild broken data later, or let you test such a migration in advance in the same way.
 
With local-first applications, the complexity increases several times over.
 

v. Bottom Line

clientful is a very unfamiliar paradigm for standard Web 2.0, but it does not fully fit into Web 3.0 either. It has many limitations and specifics that you need to get used to. But precisely because it is so “different”, it gains unique advantages that are simply unavailable to standard Web 2.0 systems.
 
Would I build everything on clientful? No. But for a SaaS application in the style of Notion, Slack, Miro, or a mood tracker, I would consider clientful for the sake of development speed, built-in collaboration and offline-first behavior, zero infrastructure cost, and Own Your Data positioning, all of which can create product advantages.
 
clientful is one of N possible chairs. It is important to try it so that you always have more than one chair available in any given situation.
 

vi. What Comes Next

I plan to continue expanding this work, developing and adding sections until it turns into a full book.
 
At minimum, what comes next:
 
  1. I will explain and show how libraries for clientful are built
  1. Real examples of problems in clientful applications, the technologies used, and their solutions
  1. Analyses of existing projects
  1. Studying transactionality in such systems
  1. How clientful fits into the current ecosystem of AI pipelines and agents
  1. Structures that extend CRDT capabilities, such as Merkle Tree, Merge Tree, etc.
  1. Operation authorization
 
📮
Subscribe on X channel [ $davids.sh ] , where announcements of new chapters and updates to existing ones will be posted.
 

vii. Useful Links

a. Clientful-format Technologies

 
  1. GitHubGitHubGitHub - alexanderop/awesome-local-first: Useful Links for Everything related to LocalFirst
  1. www.localfirst.fm
  1. Pears_p2pPears_p2pPears | Unleash the Power of P2P
 

b. Transactions in P2P Systems

 
  1. YouTubeYouTubeCRDT Research Meetup // AntidoteDB - Nuno Preguiça
  1. YouTubeYouTubeAnnette Bieniusa - AntidoteDB: highly available, transactional database - Code BEAM Lite Munich 2018
 

c. CRDT

 
  1. ElectricSQLElectricSQLIntroducing Rich-CRDTs | ElectricSQL
  1. EDB Postgres Distributed (PGD) v4.3.8 - Conflicts