When to Use Record Links vs. Graph Edges in SurrealDB
A practical guide to choosing between SurrealDB’s record links and graph edges for modeling relationships, with a worked example, trade‑offs, and verification steps.
25 Apr 2026, 12:36 UTC

Problem: Modeling relationships without over‑engineering
When you start a new SurrealDB schema you often face a choice: should a post record simply store a reference to its author, or should you model the connection as a first‑class edge record? The decision affects query simplicity, payload size, and the ability to attach metadata to the relationship itself.
Thesis
Start with record links for straightforward ownership or one‑to‑many references. Introduce graph edges only when you need relationship‑level attributes, many‑to‑many semantics, or fine‑grained permissions on the connection.
Record links: lightweight references
A record link is a field that stores the ID of another record, e.g. author: person:jane. SurrealDB can dereference it in a single query using FETCH:
DEFINE TABLE person SCHEMAFULL;
DEFINE TABLE post SCHEMAFULL;
The result nests the author object inside each post row. Because the link is just an ID, storage overhead is minimal and the query planner can resolve it with a simple lookup.
Graph edges: first‑class relationship records
When you need to store data on the relationship itself — such as a role, timestamp, or permission — SurrealDB lets you create an edge table and relate two records with RELATE:
DEFINE TABLE follows SCHEMAFULL;
DEFINE FIELD role ON follows TYPE string;
DEFINE FIELD created_at ON follows TYPE datetime;
RELATE person:jane->follows->person:john SET role = 'mentor', created_at = time::now();
-- Find who follows john
SELECT * FROM <-follows<-person WHERE out = person:john;
Edges are regular records, so they appear in change feeds, support table‑level PERMISSIONS, and can be indexed like any other table. Traversal syntax (<-follows->) avoids explicit joins, but each hop touches an extra record, which can increase write amplification.
Worked example: choosing the right tool
Imagine a blogging platform where each post has exactly one author, but users can also follow each other. The author relationship is a simple ownership case; the follow relationship is many‑to‑many and may need metadata like created_at.
- Author link – use a record link on
post.author. Fetching a post with its author is one query and returns a compact nested object. - Follow edge – create an
followstable withcreated_at. To get a user’s followers you traverse the edge table.
If you later decide you need to store a post.reviewed_by list where each reviewer can leave a comment, you would replace a simple link with an edge table to hold the comment and timestamp.
Trade‑offs and limitations
| Aspect | Record Link | Graph Edge |
|---|---|---|
| Storage overhead | Only the ID (few bytes) | Full record (ID + fields + metadata) |
| Query simplicity | FETCH returns nested object in one step | Requires traversal syntax; extra hop per edge |
| Relationship metadata | Not possible without extra tables | Native support for fields on the edge |
| Permissions | Apply to the source/target record only | Can restrict who can create, read, or delete the edge itself |
| Change feed impact | Only source/target mutations generate events | Edge mutations also generate events, potentially more noise |
Deeply nested FETCH chains can inflate response size; limit fetched fields or use SELECT ... FETCH author.name to keep payloads small. Conversely, over‑using edges for simple lookups adds write overhead and makes trivial queries more verbose.
Actionable closing
- Audit your current schema: mark each relationship as “ownership/one‑to‑many” or “needs metadata/many‑to‑many”.
- For ownership links, add a record link field and verify with
SELECT * FROM table FETCH link. - For relationships that may grow attributes, prototype an edge table, insert a test edge with
RELATE, and confirm traversal withSELECT <-edge-> FROM .... - Monitor query size and change‑event volume in your development environment; adjust the model if you notice excessive payloads or noise.
By matching the relationship type to the SurrealDB primitive that fits its semantics, you keep queries efficient, storage lean, and your schema evolves cleanly as requirements change.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.