Intro to EdgeDB - The 10x ORM
Intro to EdgeDB - The 10x ORM
Iāve written a couple of posts recently covering different TypeScript ORMs. One about Prisma, and another about Drizzle. ORMās are a controversial topic in their own right - some people think they are evil, and others think they are great. I enjoy them quite a bit. They make it easy to interact with your databases. What is more important and magical for an application than data? SQL without an ORM is amazing as well, but there are some pain points with that approach. Today Iām excited to write about EdgeDB, which isnāt _exactly_ an ORM or a database from my perspective (although they call themselves one). It is, however an incredibly impressive piece of technology that solves these common pain points in a pretty novel way.
So if itās not an ORM or a database, what exactly is it?
I donāt think I can answer that in one or two sentences, so we will explore the various pieces that make up EdgeDB in this article. From a high-level standpoint, though, an interface/query language that sits in front of PostgreSQL. This may seem like some less important implementation detail, but in my eyes, itās a feature and one of the most compelling selling points.
Data Modeling
EdgeDB advertises itself as a āgraph-relational databaseā. The core components of EdgeDB data modeling are the schema, type system, and relationship definitions. A schema will consist of objects that contain various typed attributes and links that connect the objects. In SQL, a table is analogous to an Object, and a foreign-key is associated with a link.
Hereās what a simple schema in EdgeDB looks like
`
Thereās a few things to highlight here
* We defined two different objects (tables) User and Post
* Each object contains properties with their types defined
* str is one of several scalar types (bool, int, float, json, datetime, etc)
* the author property is a required link to the User object
Defining relations / associations
In our example above we defined a one-to-many relationship between a user and posts. All the relation types that you can define in traditional SQL are available. One interesting feature though is called backward links. These can be defined in your schema and it allows you to access related data from both sides of a relationship.
`
likes are a many-to-many relationship between Tweet and User. With a backlink defined multi link likers := Tweet.<likes[is User]; - we can access likes from a User and likers from a Tweet.
`
That's how we can access these relations in our queries. You might be looking at these queries and thinking it looks a lot like GraphQL. This is why they call it a āGraph-relationalā database.
Weāve only scratched the surface of EdgeDB schemaās. Hopefully, Iāve at least managed to pique your interest.
Computed properties
Computed properties are a super powerful feature that can be added to your schema or queries. This example user schema creates a computed discriminator and username property. The discriminator uses an EdgeDB standard library function to generate a discriminator number and the username property is a combination between the name and discriminator properties.
`
Globals and Access Policies
EdgeDB allows you to define global variables as part of your schema. The most common use case Iāve seen for this is to power the access policy feature.
You can define a global variable as part of your schema: global current_user: uuid;
With a global variable defined, you can provide the value as a sort of context from your application by providing it into your EdgeDB driver/client.
`
You can then add access policies directly to your schema, for example, to provide fine-grained access control to blog posts in your blogging application.
`
Aside from access policies, you can use your global variables in your queries as well. For example, if you wanted a query to select the current user.
`
Types and Sets
EdgeDB is very type-centric. All of your data is strongly typed. Weāve touched on some of the types already.
* Scalars - There are a lot of scalar types available out of the box
* Custom scalars - Custom scalar types are user-defined extensions of existing types
* Enums - Are supported out of the box - enum<Admin, Moderator, Member>
* Arrays - Defined by passing the singular value type - array<str>;
* Tuples - In EdgeD, tuples can contain more than 2 elements and come in named and unnamed varieties - <str, number>; tuple<name: str, jersey_number: float64, active: bool>;
All queries return a Set which is a collection of values of a given type. In the query language, all values are Sets. Sets are a collection of values of a given type. A comma-separated list of values inside a set of {curly braces}. A query with no results will return an empty or singleton set. If we have no User values stored yet - select User returns {}.
Paired with the query language types and set provides an incredibly powerful and expressive system for interacting with your data. If you thought TypeScript was cool, wait until you start writing EdgeQL! š
EdgeQL
Now to the fun stuff: the query language. Weāll use a schema from the docs and start by looking at some of those queries and build on those.
The example schema has an abstract type Person with two sub-types based on it Hero and Villian. This is known as a polymorphic type in EdgeDB. The Movie type includes a 1:m association with Person
`
Selecting properties / data
Before we dig into some real queries we should just touch on how we select actual data from a query. Itās pretty obvious and GraphQL-like but worth mentioning. To specify properties to select, you attach a shape. This works for getting nested link / association data as well.
Based on our schema, hereās how we could select fields from Movie, including data from the collection of related characters.
`
There is also a feature called a splats that allows you to select all fields and/or all linked fields without specifying them individually.
`
If you donāt specify any properties or splats, only idās get returned select Movie; .
Adding some objects with insert
To get started, we can use insert to add objects to our database.
Weāll start big by looking at the nested insert example. This example is interesting because it shows the creation of two objects in a single query. Youāll notice the simplicity of the syntax. Even though this is the first EdgeQL query weāre looking at, in my experience, itās like this across the board. Iāve found EdgeQL queries to be simple and intuitive to the point where Iāve been able to intuit how to accomplish things in my head without having to reference the docs or ask the AI.
This example adds a new Villian and a new Hero which gets assigned as a link or association to the nemesis field on our Villian. To accomplish this we see that we can nest queries by wrapping them in ().
`
The next example is pretty similar, but instead of creating the linked property, we are select ing and adding several potential objects to the characters list of Movie since it is a multi link. This is a pretty complex query that is doing a lot of different things. Itās deceivingly succinct. To accomplish the same thing with SQL this would probably be about 3 different queries. This query finds the objects to add to the characters multi link by filtering on a collection of different strings to match against the name property.
`
The last thing weāll cover for insert is bulk inserts. This is particularly useful for things like seed scripts.
In this example, you can just imagine that you have a JSON array of objects with hero names that gets passed in as an argument to your query
`
Querying data with select
Weāve already seen subqueries and a select in the last section where we found a collection of Person records with a filter. Weāll build on that and see what tools are available to us when it comes to querying data.
This one covers a lot of ground. Very similar to SQL we have order and limit, and offset operators to support sorting and pagination. Also there is a whole standard library of functions and operators like count that can be used in our queries. This example returns a collection of villian names, excluding the first and last result.
`
Most commonly, you will want to filter by an id
`
Hereās another common example filtering by datetime. Since weāre using a string value here we need to cast it to the EdgeDB datetime type.
`
You get a pretty similar toolbox to SQL when it comes to filtering with your common operators and things. Combined with all the tools in the standard library, you can get pretty creative with it.
Changing values and links with update
The update..filter..set statement is how we can update existing data with EdgeQL. set is followed by a shape with assignments of properties to be updated.
`
You can replace links for an object
`
or add additional ones
`
An even more interesting example is removing links matched on a type. Since Villian is a sub-type of Person , this query will remove all characters linked of the Villian type.
`
Deleting objects with delete
Deleting is pretty straight forward. Using the delete command you can just filter for the objects that you would like to remove.
`
When the EdgeQL pieces fall into place
As you become more familiar with the EdgeQL query language chances are youāll start writing very complex queries fluently because everything just makes sense once youāve learned the building blocks.
Domain and business concerns
I donāt think they explicitly mention this as a goal anywhere but itās something that I picked up on pretty quickly. EdgeDB nudges you to move more of what might have traditionally been application logic into your database layer. This is a topic that can bring a lot of division since even things like foreign keys and constraints in SQL are frowned upon in some circles. EdgeDB goes as far as providing constraints, global variables, contexts, and authorization support built into the database. I think that the ability to bake some of these concerns into your EdgeDB Schema is great. The way you model your schema and database in EdgeDB map to your domain in a much more intuitive way where domain concerns donāt really feel out of place there.
Database Clients and Query Builders and Generators
Weāve covered a lot so far to highlight what EdgeDB is and how to handle common use cases with the query language. To use it in your project though, you will need a client/driver library. There are clients available in several different languages. The one that they clearly have put the most investment into is the TypeScript query builder. Weāll briefly look at both options: simple driver/client and query builder. Whichever you end up choosing you will need to instantiate a driver and make sure you have a connection to your database instance configured.
Basic client
Although the TS query builder is very popular and pretty amazing, I couldnāt get away from just writing EdgeQL queries. In my application, I composed queries using template strings, and it worked great. The clients all have a set of methods available for passing in EdgeQL queries and parameters.
querySingle is a method for queries where you are only expecting a single result. If your query will have multiple results you would use query instead. There is also a queryRequiredSingle which will throw an error if no results are found. There are some other methods available as well including one for running queries in a transaction
`
The first argument is the query, and the second is a map of parameters. In this example we include the title parameter and it is accessed in our query via $title.
TypeScript query builder
If you have a TypeScript app and type-safety is important, you might prefer using the query builder. It is a pretty incredible feat of TypeScript magic initially developed by the same developer behind the popular library Zod. We canāt cover it in very much depth here but weāll look at an example just to have an idea of what the query builder looks like in an application.
`
The query builder is able to infer the result type automatically. It knows which fields youāve selected, it knows that the result will be a single item.
Query generator
There are generators for queries and types. So even if you opt out of using the query builder you can still have queries that are strongly typed. Itās nice to have this option if you want to just write your queries as EdgeQL in .edgeql files.
`
We end up with an exported function named getUser that is strongly typed.
`
Tools and Utilities
The team at EdgeDB puts a big emphasis on developer experience. It shows up all over the place. Weāve already seen some utilities with the generators that are available. There are some other tools available as well that help complete the entire experience.
EdgeDB CLI
The first and most important tool to mention is the CLI. If youāve started using EdgeDB then youāve most likely already installed and used it. The CLI is pretty extensive. It includes commands for things like migrations, managing EdgeDB versions and installations, managing projects and local/cloud database instances, dumps and restores, a repl, and more. The CLI makes managing EdgeDB a breeze.
Admin UI
The CLI includes a command to launch an admin UI for any project or database. The Admin UI includes a awesome interactive diagram of your database schema, a repl for running queries, and a table to inspect and make changes to the data stored in your database.
Summary
Adopting newer database technology is a tough sales pitch. Replacing your applicationās database technology at any point in its lifecycle is not a problem that anyone wants to have. This is one of the reasons why EdgeDB being a superset of PostgreSQL is a huge feature in my opinion. The underlying database technology is tried and true, and EdgeDB is open-source. Based on this, I would feel confident using EdgeDB if it aligned well from a technical and business perspective.
Weāve covered a lot of ground in this post. EdgeDB is feature-packed and powerful. Databases is a tough nut to crack, and I commend the team for all their hard work to help continue pushing forward one of the most important components of almost any application. Iām typically pretty conservative when it comes to databases, but EdgeDB took a great approach, in my opinion. I recommend at least giving it a try. You might catch the EdgeDB bug like I did!...