top | item 40788648

Show HN: Triplit – Open-source syncing database that runs on server and client

279 points| matlin | 1 year ago |github.com | reply

Hey HN, we’re Matt and Will, the co-founders of Triplit (https://www.triplit.dev). Triplit is an open-source database (https://github.com/aspen-cloud/triplit) that combines a server-side database, client-side cache, and a sync engine into one cohesive product. You can try it out with a new project by running:

  (npm|bun|yarn) create triplit-app
As a team, we’ve worked on several projects that aspired to the user experience of Linear or Superhuman, where every interaction feels instant like a native app while still having the collaborative and syncing features we expect from the web. Delivering this level of UX was incredibly challenging. In each app we built, we had to implement a local caching strategy, keep the cache up to date with optimistic writes, individually handle retries and rollbacks from failures, and do a lot of codegen to get Typescript to work properly. This was spread across multiple libraries and infrastructure providers and required constant maintenance.

We finally decided to build the system we always wanted. Triplit enables your app to work offline and sync in real-time over websockets with an enjoyable developer experience.

Triplit lets you (1) define your schema in Typescript and simply push to the server without writing migration files; (2) write queries that automatically update in real-time to both remote changes from the server and optimistic local mutations on the client—with complete Typescript types; (3) run the whole stack locally without having to run a bunch of Docker containers.

One interesting challenge of building a system like this is enabling partial replication and incremental query evaluation. In order to make loading times as fast as possible, Triplit will only fetch the minimal required data from the server to fulfill a specific query and then send granular updates to that client. This differs from other systems which either sync all of a user’s data (too slow for web apps) or repeatedly fetch the query to simulate a subscription (which bogs down your database and network bandwidth).

If you’re familiar with the complexity of cache-invalidation and syncing, you’ll know that Triplit is operating firmly in the distributed systems space. We did a lot of research and settled on a local first approach that uses a fairly simple CRDT (conflict-free replicated data type) that allows each client to work offline and guarantees that they will converge to a consistent state when syncing. It works by treating each attribute of an entity as a last writer wins register. Compared to more complex strategies, this approach ends up being faster and doesn’t require additional logic to handle conflicting edits between concurrent writers. It’s similar to the strategy Figma uses for their collaborative editor.

You can add Triplit to an existing project by installing the client NPM package. You may self-host the Triplit Server or pay us to manage an instance for you. One cool part is that whether you choose to self-host or deploy on Triplit Cloud, you can still use our Dashboard to configure your database or interactively manage your data in the Triplit Console, a spreadsheet-like GUI.

In the future, we plan to add APIs for authentication, file uploads, and presence to create a Supabase/Firebase-like experience.

You can get started by going to https://triplit.dev or find us on Github https://github.com/aspen-cloud/triplit. Thanks for checking us out and we are looking forward to your feedback in the comments!

100 comments

order
[+] thanhnguyen2187|1 year ago|reply
Hi Triplit's team,

Congrats on the launch and thanks for the awesome product! I've been using Triplit in one of my projects [1], and it do work as expected. In my self-promotion on Reddit [2], I posted about Triplit as well:

> I think Triplit is a nice database and works as expected. It's data model fits well with what I have in mind (more decentralized/P2P instead of having a single centralized database as the source of truth), but there are 2 areas I find lacking:

> - Server side/self-hosted: Triplit server requires a token for authentication. Triplit's documentation has a section about how to start the server without explicitly showing how to generate the token, which is mildly inconvenient. Therefore, on self-hosting the server, I opted for using their CLI command dev since the command has the token generation that I needed. I know it is not a good security practice as when the command is used as a system service, the token will be logged in plain text, but I have a bigger problem when someone can access that anyway.

> - Query language: I find their custom query DSL not as expressive as a full-fledged query language (lacking UNIQUE and COUNT like SQL is on the top of my mind). You'll have to do a bit of data aggregating yourself.

Recently, I found Evolu [3], which is quite similar to your project in terms of scope and functionalities as well. From a quick skim of their documentation, I think the differences are:

- Triplit have `.subscribe()`, while Evolu don't

- Evolu's querying is more familiar/advanced (typed SQL via Kysely)

- In the browser, Evolu seems to use SQLite on top of OPFS, while Triplit uses IndexedDB

I think there are more intricacies on the way Triplit differs from Evolu, so can you enlighten me on that?

Really appreciate your comment! Thanks!

- [1]: https://github.com/thanhnguyen2187/cryptaa

- [2]: https://www.reddit.com/r/sveltejs/comments/1dndpj8/cryptaa_a...

- [3]: https://www.evolu.dev/docs

[+] matlin|1 year ago|reply
Glad you've enjoyed using Triplit!

on self-hosting: We're cleaning up the docs on self-hosting to make the configuration clearer, thanks for point that out.

on querying: Yeah we don't have aggregations yet but it's on our roadmap. Don't want to over promise but I think we can make something awesome here by leverage our incremental querying engine. Like consider a data dashboard that needs to be updated every hour; in a traditional system (postgres, mongo, etc) you would need to rerun the query from scratch each time. Our plan is to create something closer to what Materialize does and just process the new data so it's much more efficient and can just update continuously rather than every hour.

re Evolu: I haven't actually gotten a change to try it out but there might be in someone in our Discord[1] that has that could compare/contrast

1. https://triplit.dev/discord

[+] ddrdrck_|1 year ago|reply
Thanks for pointing out Evolu, both solutions (Triplit and Evolu) seem very interesting. I would also be interested to see a comparison between the two
[+] steida|1 year ago|reply
Evolu has subscribe with useQuery or separated.
[+] lars512|1 year ago|reply
Related question: when you're using databases with great offline sync protocols like this, how do you do schema evolution of your DB, especially in the face of different client versions that you cannot upgrade in lockstep?

My context here is having worked in the past on a mobile health app, and recalling all the pain we had around this problem.

[+] rockwotj|1 year ago|reply
Only create new tables, don't make breaking changes to existing tables. If needed dual write both versions. Looks a lot like a live migration (zero downtime) due breaking change to a SQL DB, except you have to keep the logic longer because you're at the customer's mercy of when the switchover happens.

Also it's important to have a table that is used to coordinate latest version. If you make a breaking change have the client that is behind to ask the user to upgrade. You can also tie this into how long you keep the dual write/read around with a min version supported across all clients.

[+] matlin|1 year ago|reply
This is a great question! The short answer is by maintaining backwards compatibility in your schema--it's basically the easiest way to guarantee compatibility. We have warnings in place to let you know when you've made a change that isn't backwards compatible, you can read about it in our docs: https://www.triplit.dev/docs/schemas/updating#pushing-the-sc...

However, overtime this can naturally lead to a mess of a schema definition that has a lot of confusing names. We haven't released a solution to fix this yet but we're working on a few things that should make this less painful. For background on the various approaches, the Cambria doc is an amazing resource: https://www.inkandswitch.com/cambria/

[+] manmal|1 year ago|reply
I think some clients (like the server) must be able to sync with very old schemas because the user might have forgotten their phone in a drawer for two years. Each client just migrates itself asap.
[+] robertlagrant|1 year ago|reply
I would say having a built in way to define and support historic migrations would be a pretty killer feature. Saves everyone inventing their own way of managing migrations.
[+] Kiro|1 year ago|reply
I don't understand in what apps it's acceptable for the client to be able to write to the database directly. Or how you can get away without any backend logic.

I have the same questions about Supabase and Firestore so it seems like I'm missing something.

[+] breakfastduck|1 year ago|reply
Most things built 'out in the world' have hardly any business logic, they're all just CRUDs.

In enterprise this is obviously the opposite way around. It's actually super frustrating to watch discussions that ignore this, it's a really big problem on tech twitter etc especially, where people advocate for certain stacks or ways or working or whatever that make it clear as day they've never had to build a business system, they've just been building CRUDs, so they're incapable of understanding why experienced devs who have disagree with them.

[+] robertlagrant|1 year ago|reply
I built a collaboration app using Firebase - it works okay for that sort of thing, where you're highly constraining what each person does to their own comments / cards, and they just have permission to do certain things. For things with a load of backend logic I can't imagine it working that well.
[+] Lalabadie|1 year ago|reply
For both of these, there's backend-enforced access, so it's not without backend logic. With Supabase for example, the feature is called row-level security.

The client can fire requests to Supabase, but Supabase runs additional queries on the back-end that determine whether an incoming request is allowed or not.

A simple example would be that a client is only allowed to read, write or update a row if the value in the UserID column is the same as the requesting authenticated user's.

[+] armincerf|1 year ago|reply
We’ve been using triplit to store user settings that can be managed by admins, it’s important users always feel like the app runs locally and they often don’t have good internet but we still needed sync as the users often switch devices and admins need to see and manage other users settings.

Overall triplit has been really great, both as a frontend dx and also their support - whenever we find an issue or have a feature it gets handled very quickly by the team which is awesome!

As soon as they have an answer for HA deployments we will be moving more critical data there instead of Postgres

[+] matlin|1 year ago|reply
It's been great working with you to get things right. Your feedback and bug reports have helped immensely to shape Triplit!
[+] candiddevmike|1 year ago|reply
Why did you choose to license this under the AGPL?
[+] matlin|1 year ago|reply
Our hope with the AGPL license is that it will make Triplit easily self-hostable while ensuring that anyone who makes modifications contributes those back to the community.
[+] victor9000|1 year ago|reply
So you have to AGPL your product because of the database that it uses lol, hard pass.
[+] satvikpendem|1 year ago|reply
I believe I saw your video presentations on YouTube [0] from the Local First Discord server [1], so nice to see your Show HN here. I am not using TypeScript so I might not be your target audience, I'm using local-first especially for mobile apps where connections can be spotty unlike the web which by definition (at least on first load) is always connected to the internet, and I'm using Flutter for this use case, as well as a backend in Rust. Other local first solutions are generally agnostic to the client and server because they work directly on the database layer, ie ElectricSQL and PowerSync will sync the client and server database. Other solutions with CRDTs can also work on the client and server with some FFI, ie automerge is in Rust so in my case it'll work via FFI on the Flutter side via flutter_rust_bridge (or WASM on the web) and of course on my Rust backend server, but it looks like you are looking to offer a more classic client-server syncing solution instead needing conflict free resolutions on disparate clients, ie the server is the source of truth.

So with all that, my question is, is there a reason you guys went with more of a full language level solution rather than being more agnostic to the client and server? It seems harder to support other languages and frameworks other than JS based ones in the future, but I suppose the market for that is already big enough. ElectricSQL etc also have SDKs for TypeScript as well as Flutter and others so they are similar to your solution but it seems like they can support more clients and servers in the future just by building SDKs for them.

Another question, looks like you eventually want to compete with Supabase but they are already experimenting with database level syncing and CRDTs in Postgres [2] and might catch up with your solution, any thoughts on that?

[0] https://www.youtube.com/playlist?list=PLTbD2QA-VMnXFsLbuPGz1...

[1] https://localfirstweb.dev/

[2] https://news.ycombinator.com/item?id=33931971

[+] matlin|1 year ago|reply
That's cool that you saw my presentation!

Re: Flutter (and other native support) it's something we've thought a lot about (Flutter specifically comes up often) but we decided to focus on pure-Typescript to start because it's a big enough market (I'm personally a big believer in the future of PWA's) and we know we can make the best experience by focusing on it. I do think that we'll eventually make something more platform agnostic but not sure when that will happen.

Re: ElectricSQL & Supabase: both teams are obviously very talented and thoughtful and, I believe, will continue growing in the SQL space which is the most fundamental difference between our approaches. I think Triplit can make the best experience for developers by avoiding SQL and think there's undoubtedly room for both philosophies.

[+] curtisblaine|1 year ago|reply
Since this is LWW, does it mean the amount of information on the client scale linearly with the number of operations? (meaning: the more users modify a database, the more the operations log grows)? Or do you do checkpoints? How does it scale space-wise when the user does millions of ops per day?
[+] matlin|1 year ago|reply
Triplit does persist the history of edits to a given attribute but indexes on the latest values makes querying stay fast. However, LWW Registers by nature doesn't actually require storing the history that's just our current implementation that allows clients to sync efficiently even after being offline for a while.

In terms of scaling to a million-ops per day we are probably not quite there yet but one advantage to having the server be the authority is that, in the future, the Triplit server could track the timestamps that each client last synced at and progressively prune the history similar to how Postgres handles VACUUM'ing dead tuples.

[+] munzman|1 year ago|reply
would be great to have Rust bindings so it could work with Tauri. With tauri growth and the upcoming mobile devices support coupled with SQLite recent hype, this could bridge the gap and solve many problems for offline-first apps, thus become the go to solutions for many dev shops.
[+] satvikpendem|1 year ago|reply
I'm looking to add Rust bindings to ElectricSQL as that is a similar sync solution but it acts on the database layer so it's language agnostic. I'm using Rust on the server but Rust bindings would work on both the client and server. If you're interested in joining me on developing those Rust bindings, let me know.
[+] matlin|1 year ago|reply
I thought Tauri was just using native web renderers? Seemingly Triplit should work out of the box.
[+] tanishqkanc|1 year ago|reply
I've been using Triplit on my React native app for a while and it works great. Highly recommend. It's the only local-first db that hits these points for me:

- Good sane query language (not SQL) - Great typescript support - Offline support - React native support

The cherries on top is that it's open source and self-hostable.

[+] matlin|1 year ago|reply
Thanks for the kind words and help identifying issues in Triplit as you've built your app!
[+] sagarjs|1 year ago|reply
So it’s not possible to use this with an existing postgresql database?
[+] arcticfox|1 year ago|reply
This is really cool. Feels like the future of app development.

But also I’m getting old, and I had the same feeling when RethinkDB came out. Do you guys have any thoughts on how your system compares to what they were doing and what eventually happened to them?

[+] matlin|1 year ago|reply
I can't speak to RethinkDB's history but we're really focused on a specific use case which I think helps. Specifically, Triplit is made for web developers so we have great TS support, React and Svelte bindings, etc which makes adoption and marketing much easier.
[+] jamil7|1 year ago|reply
RethinkDB was actually pretty good from memory, a little underbaked but expected for a new DB. I think they also kind of just lost out on the marketing war with MongoDB at the time.
[+] aitchnyu|1 year ago|reply
Cant wait for new PHP, in the sense we can stuff hundreds of apps (with dbs) in the filesystem and it takes negligible CPU if idle.
[+] kouohhashi|1 year ago|reply
I used to use the meteo framework before, and I think if it's similar. By the way, is it possible to use MongoDB on the server side and Triplit on the React side? Or do I have to use a new database called Triplit?"
[+] alex_lav|1 year ago|reply
This looks great! I would like to use this tool.

I'm looking through the docs for more info on this line:

> The server supports different storage adapters, such as SQLite

What are the other storage adapters? I may just be blind, so if so please forgive me!

[+] matlin|1 year ago|reply
There are adapters for LevelDB, LMDB, and File storage. We just use SQLite out of the box because it's fast and reliable. We'll make those other adapters more accessible in the future.
[+] 8mobile|1 year ago|reply
Wow, nice work. I will definitely try it out for my apps and backend. Do you have any benchmarks? Not sure about the AGPL licence
[+] terpimost|1 year ago|reply
Pretty cool guys. It would be nice to know the differences to other local first like solutions out there.
[+] matlin|1 year ago|reply
There are a lot of new local-first tools these days but broadly speaking Triplit lends itself to more typical client-server applications with a central, authoritative server where other tools in this space are oriented around decentralization and server-agnostic systems.
[+] mrtesthah|1 year ago|reply
What is Triplet’s syncing latency?
[+] matlin|1 year ago|reply
I don't have official measurements but it's fast enough for most things short of realtime cursors or anything updating on an sub-second interval--we'll be adding a presence API for things like that. It'll also depend on the complexity of the query.

Do you have a specific use case in mind?

[+] pantulis|1 year ago|reply
Remotely reminds of Joyent Slingshot. Oh, how times have changed.
[+] Jayakumark|1 year ago|reply
Is there a pre-existing demo page.
[+] matlin|1 year ago|reply
We've setup a demo environment to get a feel for it here: https://demo.triplit.dev/

It's actually running two clients and the server inside the browser window so even though it's simulating real-world, it's the actual code that powers Triplit.