I don't want an ORM, I want a data layer

I don't want an ORM, I want a data layer

What's missing in data management in React apps

Hi, I'm Erik. I have been maintaining large React apps professionally for about a decade. I've used all kinds of tools to try to shuttle data back and forth from a database into React state and back again. Tools like Redux, Apollo GraphQL, React Query, Prisma, tRPC, yada, yada, yada.

The problem is, while these tools do important jobs, I keep needing additional code to actually turn all of these tools into a workable development environment.

These seem like small things, but it really adds up:

  • Scripts to set up, migrate, and connect to a test database

  • Code to reset that database after tests

  • Factories for inserting test records and all of the relations needed for those records to have referential integrity

  • Client-side code for managing local state for an "optimistic UI"

  • Code across the stack for tracking changes and pushing "realtime" updates to clients when records and query results change

  • Some sort of access control regime

  • A mechanism for computing and possibly computed fields

... and honestly, that's just the start of it.

Sure, some of these features are covered by some of the libraries above, somewhat... Apollo Client propagates "realtime" changes to records across your app. But you still need to wire that up with a custom subscription. And it doesn't do live queries. Fishery helps you build test factories. But it doesn't create relations. And you still need to write a shim to get those attributes into your database. Redux lets you optimistically update state, but you have to write that code.

There are so many libraries out there that solve a piece of the puzzle, but I am left feeling like every new project I start, and every new company I join I have to rebuild years worth of code from scratch.

I'm tired of writing the same code over and over.

So I'm building it. It's going to be open source. And it might be a disaster. But it will be fun.

What is MultiplayerDB?

It's not an ORM. And it's not a database (it's just Sqlite underneath). Instead it is what I would call a data layer—a piece of code that spans all the way from the front end to the database and provides a nice, easy-to-use API for an application to use.

That means it's a combination of client and server libraries which coordinate to keep your client-side application up-to-date with all of the latest changes in your database.


Multiplayer — This is the marquee feature of MultiplayerDB. All queries are "live queries" which update within seconds when new data comes in from other tabs or devices. And transient data like cursor positions propagates in milliseconds.

Local test database — Spin up a local, in-memory test database as simply as importing a package:

import { MultiplayerTestDb } from "@multiplayerdb/your-project/test"
import { startApi } from "~/api"

test("users can sign up", () => {
  const db = new MultiplayerTestDb()
  await startApi({ db })
  // etc...

Typed, relational test factories — Set up preconditions for your tests, only setting the overrides you specifically care about:

test("comments are sorted chronologically", () => {
    const db = new MultiplayerTestDb()
    const { commenter, blog } = db.setUpComment({
      createdAt: new Date("2023-12-23T03:30Z"),
      text: "This comment should come first",
      createdAt: new Date("2023-12-23T03:29Z"),
      text: "This comment should come second",
    await signInWithPassword(commenter.email, commenter.password)
    // etc...

Relational client API — Query using GraphQL so you can fetch all the data you need in one request. This isn't anything special, GraphQL already enables it. But this is our reason for using GraphQL.

Reversible, versioned migrations — Migrations are composed from individual edits made in a schema editor, which allows them all to be reversible. And you can continue to edit a migration as you work: MutiplayerDB schemas use NPM package publishing and Semantic Versioning to allow you to target any schema version in different branches of your app:

yarn add @multiplayerdb/your-project@2.0.4-alpha.0

Or simply target a migration by name, and as you make edits simply upgrade to the latest version:

yarn upgrade @multiplayerdb/your-project@soc2-audit

Access control — Grant access to subgraphs either to registered users, teams, or unregistered guests.

"Backend as a Service" — Build your app as a pure client-side app deployed to the Edge if you like.

File uploads — Cheap file storage, with file size limits and access control built in.

Computed fields — Efficiently compute aggregate data and other derived data on the backend so your frontend can be simple and clean.

Seed data — Set up test accounts with realistic data for performance monitoring, QA, etc.

OK, so all of that is built?

No. None of that is built. It's just the plan. I have a day job and I'm working on this in my spare time.

Can I help?

If you'd like to help ensure MultiplayerDB gets built, consider supporting the project on Patreon. Soon enough we'll get a Github repo up to test out and contribute to as well.

And probably the best thing to do right now is to subscribe to the newsletter so you can stay abreast of project updates!

What's with the ermines?

Every open source project needs a mascot, right? Ours is a family of ermines. Cuz "multiplayer".