Databases are about to change the way that React changed the webapps
In the next year we will celebrate the 10th anniversary of React. In the pre-react era, the dominant paradigm for UI development was the jQuery library.
In a nutshell, it was a world of manual binding, where the static markup contained some classes and the JavaScript hooked into the markup via various selectors:
<div>
<ul class="js-menu">
<li>home</li>
</ul>
<button class="js-toggle">
<i class="icon-hamburger" />
</button>
</div>
$(`.js-toggle`).click(function () {
$(".js-menu").toggle("fold", 300);
});
At that time one maintainability best practice was to prefix the ‘behavior’ classes with the js-
prefix. This was not only error prone, but also the synchronization of the classes in html and the script was boring.
With the dawn of React, we no longer needed to bind the scripts & markup manually. Instead we got the unified React.createClass
API, which enabled us to render the html with a behavior via JavaScript:
React.createClass({
onClick: function () {
console.log("clicked!")
},
render: function () {
return div({
children: [
ul({ children: [li()] }),
button({ onClick: this.onClick }),
],
});
},
});
At first, the html-tags-as-function calls did not stick with the developers, as they didn’t like the endless stream of closing brackets. Given there was a popular dialect of PHP with embeddable XML, the React team introduced the JSX, which had a familiar look and gained interest of the developers:
React.createClass({
render: function () {
return (
<div>
<Menu />
<ToggleButton />
</div>
);
},
});
The JSX enabled the developers to focus on the real React features like declarative & composable components, pure rendering and immutability of the props.
I believe it unified the duality of ‘markup’ and ‘script’ — it was not a templating language like other frontend frameworks used at that time. It was a powerful language extension, which enabled us to interleave it with plain JavaScript expressions.
If you are thinking “I came here for the databases, why all the React lore?”, hold on we are getting there!
When it comes to working with databases, I will argue that some of us live in a world similar to the pre-React/jQuery age. Similarly to writing html structure, we are defining the database tables with schema definition language (SDL). In practice this is done by some query builder & migration tooling. With modern tooling like Prisma, we can edit just a schema file, and get the migrations generated for us.
For illustrative purpose let’s imagine a simple task system where we assign several tasks to one or more users. Our database migration will consist of a few tables referencing each other by foreign keys:
CREATE TABLE user (
id uuid PRIMARY KEY,
name text,
);
CREATE TABLE task (
id uuid PRIMARY KEY,
title text,
dueAt datetime,
);
CREATE TABLE taskAssignee (
userId uuid REFERENCES user(id)
taskId uuid REFERENCES task(id),
);
Now to make the data alive in our application, we need to manually bind the tables to object models. For the sake of the argument, let’s forget that Prisma can do it for us. When we are left with some ‘traditional’ object-relational tooling we must give it hints of how our tables are named and referenced:
import { Model } from 'objection'
export class Task extends Model {
static get relationMappings() {
return {
assignees: {
relation: Model.ManyToManyRelation,
modelClass: User,
join: {
from: "task.id",
through: {
from: "taskAssignee.taskId",
to: "taskAssignee.userId",
},
to: "user.id",
},
},
};
}
}
I believe that careful reader can see the parallel I’m trying to make. Expressing the relation mapping feels just as boring as the decade old binding of the js-
class in the markup & jQuery we saw earlier.
Once again we are in a duality — this time of tables and objects. Perhaps we don’t have to use an ORM to transform one into another. Instead we can achieve their unification the React way — with a new language.
How would the hypothetical SQLX look like?
Our language will have a declarative schema, where we will no longer deal with table references via foreign keys, instead we just relate object types via links:
type User {
required property name -> str;
}
type Task {
required property title -> str;
required property dueAt -> datetime;
#✨higher-order link instead of low-level 'taskAssignee' join table
required multi link assignees -> User;
}
Moreover, the language will be composable, to permit nested data manipulation:
insert Task {
title := "Write a blog post",
dueAt:= datetime_current()
assignees := {
(select User filter .name = "Bob"),
(insert User {name := "Alice"}) #✨nested insert
}
}
And last but not least, SQLX will allow us to write expressive and intuitive queries:
with alice := (select User filter .name = "Alice")
select Task {title} filter alice in .assignees;
Look at that! The query reads almost like an English sentence! We can imagine that its SQL version with a JOIN
on foreign keys would be much more hairy and would conceal the business goal.
But hey, how does the elegant query language help me to get my data from the database into the application service? The answer is language-native SQLX query builder:
import q from "sqlx/query-builder"
const alice = q.select(q.User, (user) => ({
filter: q.op(user.name, '=', 'Alice'),
}))
q.with(
[alice],
q.select(q.Task, (task) => ({
title: true,
filter: q.op(alice, 'in', task.assignees),
}))
)
So with our SQLX by introducing the higher concept of links we have solved the boring manual binding between table names and ORM models. On top of that we got the full power of an elegant query language in our backend, without the limitations of ORMs!
Our hypothetical SQLX exists!
Such language indeed exists and is called EdgeQL and is part of the EdgeDB. Over the last few weeks I’ve been exploring its various features. To get a more realistic perspective on the language, I’ve created a task system to test it in a ‘real world’ scenario.
In the edgedb-vs-knex project I have compared an EdgeDB implementation of the task system with a traditional ORM setup of Knex & Objection.js. I evaluated the query building capabilities and the experience of testing both of the implementations in the Jest testing framework.
My overall conclusion from the experiment is that the EdgeDB is transcendental:
Our analogy with the JSX got us only so far, its one thing to experiment on the presentation layer and other on the data layer. Still there is one last thing. Similarly as React embraced the underlying DOM and created the Virtual DOM, EdgeDB builds on top of the rock solid & beloved PostgreSQL.
The project recently raised a funding. I congratulate the authors and I believe that they will further sharpen the product on its rough edges so more people can enjoy this juicy technology!