Developer Defined Replication

Open source database replication defined from your code

Install
npm install -g @rejot-dev/cli Click to Copy
Quickstart Guide →
Source Database
Postgres database
customers table
orders table
Change Data Capture active
Destination Database
MySQL database
users table
orders table
Change Data Capture active
Public Schema
Credit Line name
customer_id bigint
amount bigint
currency short
Status active
Consumer Schema
Credit Line name
users table
orders table
Status active
YCombinator logo Backed by Y Combinator

Asynchronous

Stop chaining fragile service calls. ReJot decouples communication by letting data flow asynchronously between services, using your database's changelog as the queue. It's reliable, resilient, and naturally tolerant to failure unlike synchronous APIs.

Lightweight by Design

No brokers, no partitions, no outboxes. ReJot avoids the operational weight of event streaming platforms by reusing your existing infrastructure. You get the benefits of asynchronous communication, without the overhead or complexity.

Developer-Defined

With ReJot, you define what to publish and consume using your database's query language, similar to defining your tables or writing a migration. It lives in your codebase and fits naturally into your dev workflow.

ReJot vs. Internal APIs

ReJot shines in a service-oriented architecture where distinct teams are working on separate products, where each team owns their own database. In this comparison, we compare ReJot to communicating using internal synchronous APIs.

AppAppAppAPI Router

Synchronous

To serve a single user request, it typically flows through multiple chained services, the likelihood of failure increases with each service added. This is a disaster for reliability.


Tight Coupling

Each service is tightly coupled to the others, making it difficult to change or evolve without breaking other services.

Public Schema Definition

Public Schemas are defined by the data owner. They describe the shape of the data that is published to other teams. They are referenced by the combination of their name, data store, and manifest.


createPublicSchema("publish-account", {
  source: { dataStoreSlug: "account-service-db" },
  outputSchema: z.object({
    id: z.number(),
    emails: z.array(z.string()),
    firstName: z.string(),
    lastName: z.string(),
  }),
  config: new PostgresPublicSchemaConfigBuilder()
    .addTransformation(
      createPostgresPublicSchemaTransformations(
        "insertOrUpdate",
        "account",
        `SELECT 
          a.id, 
          (
            SELECT ARRAY_AGG(e.email) 
            FROM account_emails e 
            WHERE e.account_id = a.id
          ) as emails,
          a.first_name as "firstName",
          a.last_name as "lastName"
        FROM 
          account a WHERE a.id = :id`,
      ),
    ).build(),
  version: { major: 1, minor: 0 },
});
    
Source (Line 2)
The source specifies the originating data store (i.e., database) on which the schema is based.
Output Schema (Lines 3 - 8)
The output schema describes the shape of the data that is published to other teams. In JSON Schema format.
Config (Lines 9 - 13)
Database system specific configuration. In this case the Postgres specific configuration defines what table we'll listen to for changes and what type of events we'll react to.
Transformation (Lines 14 - 24)
SQL-based transformation that encapsulates the internal schema to the public schema. Named placeholders are based on the column names of the source table.
Version (Line 27)
Schemas are explicitly versioned to ease the complexity of schema evolution.

Consumer Schema Definition

Consumer Schemas are defined by the consuming team. They contain the query to execute when a change is pushed from the source Public Schema.


createConsumerSchema("consume-account", {
  source: {
    manifestSlug: "rejot",
    publicSchema: {
      name: "publish-account",
      majorVersion: 1,
    },
  },
  config: createPostgresConsumerSchemaConfig(
    "default-postgres",
    `INSERT INTO account_destination 
        (id, full_name, email)
      VALUES (
        :id, 
        :firstName || ' ' || :lastName, 
        (:emails::text[])[1] -- select first
      ) ON CONFLICT (id) DO UPDATE
        SET full_name = :firstName || ' ' || :lastName,
            email = (:emails::text[])[1];`,
    {
      deleteSql: `
        DELETE FROM 
          account_destination 
        WHERE id = :id`,
    },
  ),
});
Source (Lines 2 - 8)
The reference to the Public Schema that is being consumed.
Config (Lines 9 - 10)
Database system specific configuration. In this case we specify which Postgres data store we'll write to.
Transformation (Lines 11 - 19)
SQL-based transformation that encapsulates the internal schema to the public schema. Named placeholders are based on the column names of the source table.
Delete Transformation (Lines 21 - 24)
Optional transformation to execute when a row is deleted.

How we integrate

Bring-your-own-infrastructure

We integrate with the databases you already use and communicate using the infrastructure that is already present. We don’t force you to integrate with yet another data store, instead we extract the most out of what you already have.

Your data, your network

ReJot operates using either a hybrid or self-hosted deployment model. Critical data never has to leave your infrastructure.

An Essay on the Burdens of Data

Data is valuable to organizations, but also a burden to software engineers. This essay is core to the philosophy of ReJot, it explores how to unburden software engineers while extracting value from data.

Read the essay

Get in touch

Let us know how we can help you!

Contact Us
- or -

Get notified

Receive email updates from ReJot