When you thought frontend development couldn't get harder, a new innovation brings the database to the client ๐ฌ
So you now have the burden of the database on you as a frontend dev.
But instead of making your life harder, everything becomes easier ๐
This is how and why ๐
Database on the client? Seriously?
For a while the client has been dominated by the server authority. Data is gold, and the gold was stored on the server, the client just a passive consumer.
Some things came along for the ride:
- Asynchronous data fetching (loading states, streaming,
Suspense
) - Required error handling
- Serialization and validation
For a while storing the data on the client was a hassle:
- Local/Session storage: limited, key-value, no relations, no queries
- IndexedDB: complex, asynchronous, no queries
- Cookies: please no ๐ซ
But not today, today is different. SQL finally landed on the client ๐ช
WASM
With WASM is possible to build and embed No-JavaScript programs in the browser.
This allows to bring things like postgres
inside the client.
File System Access
New Web APIs allow to access the local file system.
You can store and access any file from the local device. What about a SQLite database? That's right ๐ก
PGLite
My current exploration is on PGLite.
PGLite is a WASM build of
postgres
that runs on the browser ๐
PGlite on GitHub trending ๐๐ I've been so humbled by the response to our project. It's been a joy to see how excited everyone is about it!
It works on Node/Bun/Deno and in the browser. You can persist data in memory or on top of IndexDB.
I am using it on my upcoming project on Typeonce alongside Drizzle:
export class Pglite extends Effect.Service<Pglite>()("Pglite", {
effect: Effect.gen(function* () {
const indexDb = yield* Config.string("INDEX_DB");
const client = yield* Effect.tryPromise({
try: () =>
_PGlite.PGlite.create(`idb://${indexDb}`, {
extensions: { live },
}),
catch: (error) => new PgliteError({ cause: error }),
});
const orm = drizzle({ client });
const query = <R>(execute: (_: typeof orm) => Promise<R>) =>
Effect.tryPromise({
try: () => execute(orm),
catch: (error) => new PgliteError({ cause: error }),
});
return { client, orm, query };
}),
}) {}
It strips away all the complexity of HTTP requests to the server. And it works offline. And it's fast โก๏ธ
With the live
extension it also allows implementing reactive queries, so you can say goodbye to manual refreshing and most read requests. All with the power and speed of SQL:
Live queries with PGLite and @DrizzleORM ๐ Compose query using drizzle ๐ `toSQL` to extract raw query and params Exactly what it's needed inside `useLiveQuery` to make PGLite live sync with db changes โก๏ธ
export const usePlans = () => {
const orm = usePgliteDrizzle();
const query = orm
.select({
id: planTable.id,
calories: planTable.calories,
fatsRatio: planTable.fatsRatio,
carbohydratesRatio: planTable.carbohydratesRatio,
proteinsRatio: planTable.proteinsRatio,
isCurrent: planTable.isCurrent,
logs: count(dailyLogTable.date).as("logs"),
})
.from(planTable)
.groupBy(planTable.id)
.leftJoin(dailyLogTable, eq(planTable.id, dailyLogTable.planId));
const { params, sql } = query.toSQL();
return useLiveQuery<PlanWithLogsCount>(sql, params);
};
The major drawback for now is its async
API. Keeping it async
introduces all the complexity of loading states, error handling and Promise
s.
LiveStore
Another project I am following is livestore
.
๐ Sneak peek from the upcoming website: How LiveStore works locally
It stores SQLite databases inside the local file system, and it gives access to reactive queries as well.
The main selling point for me it's
livestore
's synchronous API ๐
I am planning to write the same project on Typeonce using livestore
instead of PGLite, and share with you how I like it.
Great, but why would you do that?
I am full sold on the idea of local-first. It's coming fast, but no fast enough ๐
I see a local-first future, and I want to make it happen as soon as possible.
Part of the advantages of local-first come from shifting the authority to the client. The first step is moving the database ๐ค
Be prepared for more content on the topic, as well as projects showing you how to use it (they will be easy to read since it strips away a lot of the client-server model complexity) ๐
Soon about to release the first preview of the local-only calories tracking app I am working on for Typeonce.
Experiments with database migrations in local app ๐ฌ Migrations generated using @DrizzleORM, loaded from @vite_js and applied to PGLite All inside initial @tan_stack Router loader
Once this lands you will see many new snippets landing on Typeonce, as well as a full project showing you how I did it.
See you next ๐