DEV Community

Alex Spinov
Alex Spinov

Posted on

EdgeDB Has a Free Database — Here's How to Query Relational Data Without SQL Pain

SQL was designed in the 1970s. It works, but writing a 3-level nested join with aggregations feels like wrestling an octopus. EdgeDB speaks modern syntax that reads like English.

What EdgeDB Offers

EdgeDB Cloud free tier:

  • 1 GB storage on managed cloud
  • EdgeQL — modern query language (no JOINs needed)
  • TypeScript query builder — type-safe, no strings
  • Built-in auth — email, OAuth, WebAuthn
  • HTTP API — query via REST or GraphQL
  • Migrations — auto-generated from schema changes
  • Open source — self-host unlimited

Quick Start

curl --proto '=https' -sSf1 https://sh.edgedb.com | sh
edgedb project init
Enter fullscreen mode Exit fullscreen mode

Schema (Readable)

# dbschema/default.esdl
module default {
  type User {
    required name: str;
    required email: str { constraint exclusive; };
    multi posts := .<author[is Post];
    created_at: datetime { default := datetime_current(); };
  }

  type Post {
    required title: str;
    content: str;
    published: bool { default := false; };
    required author: User;
    multi tags: Tag;
    created_at: datetime { default := datetime_current(); };
  }

  type Tag {
    required name: str { constraint exclusive; };
  }
}
Enter fullscreen mode Exit fullscreen mode
edgedb migration create
edgedb migrate
Enter fullscreen mode Exit fullscreen mode

EdgeQL (No JOINs!)

# Get posts with author — no JOIN syntax
SELECT Post {
  title,
  content,
  published,
  author: { name, email },
  tags: { name }
}
FILTER .published = true
ORDER BY .created_at DESC
LIMIT 10;

# Insert with nested creation
INSERT Post {
  title := 'EdgeDB is Amazing',
  content := 'Here is why...',
  author := (SELECT User FILTER .email = 'alice@example.com'),
  tags := {
    (INSERT Tag { name := 'database' } UNLESS CONFLICT ON .name ELSE Tag),
    (INSERT Tag { name := 'edgedb' } UNLESS CONFLICT ON .name ELSE Tag)
  }
};

# Aggregation
SELECT User {
  name,
  post_count := count(.posts),
  published_count := count(.posts FILTER .published = true)
}
ORDER BY .post_count DESC;
Enter fullscreen mode Exit fullscreen mode

TypeScript Query Builder (Fully Typed)

import { createClient } from 'edgedb';
import e from './dbschema/edgeql-js'; // Auto-generated

const client = createClient();

// Fully typed query — autocomplete for every field
const posts = await e.select(e.Post, (post) => ({
  title: true,
  content: true,
  author: { name: true, email: true },
  tags: { name: true },
  filter: e.op(post.published, '=', true),
  order_by: { expression: post.created_at, direction: e.DESC },
  limit: 10
})).run(client);

// Type of posts is automatically inferred!
// { title: string; content: string | null; author: { name: string; email: string }; ... }[]
Enter fullscreen mode Exit fullscreen mode

HTTP API

# Query via HTTP
curl -X POST 'http://localhost:10700/branch/main/edgeql' \
  -H 'Content-Type: application/json' \
  -d '{"query": "SELECT Post { title, author: { name } } LIMIT 5"}'
Enter fullscreen mode Exit fullscreen mode

Why EdgeDB

EdgeDB PostgreSQL + ORM
No JOINs needed JOIN syntax
Schema = readable DDL + ORM models
Auto-generated types Manual types
Built-in auth Add auth library
Auto migrations Write migrations

Need structured data from websites? Check out my web scraping actors on Apify.

Need database architecture help? Email me at spinov001@gmail.com.

Top comments (0)