Onyx Database Relationships

One-to-One Relationship Tutorial

Learn how to map one-to-one relationships in Onyx Database. Toggle between the open source ORM flow and the cloud-native resolver approach to see how both environments connect a Sailboat to aSkipper.

Switch between Onyx Open Source and Onyx Cloud to view the relevant steps and code samples.

Onyx Open Source Tutorial

Switch to Onyx Open Source to view the ORM-focused walkthrough and code samples.

Onyx Cloud Tutorial

Resolver-backed relationships model one-to-one lookups without direct foreign-key constraints. Define resolvers for each side, cascade saves through graph paths, and resolve relationships on demand.

1

Capture the schema revision

Export or edit the schema JSON to include resolver attributes for skipper and sailboat. Each resolver uses a fluent query to fetch the related record.
1{
2  "tables": [
3    {
4      "name": "Sailboat",
5      "identifier": {
6        "name": "registrationCode",
7        "type": "String",
8        "generator": "None"
9      },
10      "partition": "",
11      "attributes": [
12        {
13          "name": "registrationCode",
14          "type": "String"
15        },
16        {
17          "name": "name",
18          "type": "String"
19        }
20      ],
21      "resolvers": [
22        {
23          "name": "skipper",
24          "resolver": "return await db.from('Skipper').where(eq('sailboatId', this.registrationCode)).firstOrNull();"
25        }
26      ],
27      "indexes": []
28    },
29    {
30      "name": "Skipper",
31      "identifier": {
32        "name": "id",
33        "type": "String",
34        "generator": "None"
35      },
36      "partition": "",
37      "attributes": [
38        {
39          "name": "id",
40          "type": "String"
41        },
42        {
43          "name": "firstName",
44          "type": "String"
45        },
46        {
47          "name": "lastName",
48          "type": "String"
49        },
50        {
51          "name": "sailboatId",
52          "type": "String"
53        }
54      ],
55      "resolvers": [
56        {
57          "name": "sailboat",
58          "resolver": "return await db.from('Sailboat').where(eq('registrationCode', this.sailboatId)).firstOrNull();"
59        }
60      ],
61      "indexes": []
62    }
63  ],
64  "revisionDescription": "Initial schema for resolver-based one-to-one example"
65}
2

Shape the object graph

Create a plain JavaScript object for the sailboat and embed the skipper payload. Provide stable identifiers so repeated saves update rather than insert.
1const sailboat = {
2  registrationCode: "NCC1701",
3  name: "Wind Passer",
4  skipper: {
5    id: "skipper_001",
6    firstName: "Martha",
7    lastName: "McFly"
8  }
9};
3

Cascade save the skipper

Use db.cascade() with the graph path skipper:Skipper(sailboatId, registrationCode) to connect the skipper's foreign key to the sailboat's identifier during persistence.
1await db
2  .cascade("skipper:Skipper(sailboatId, registrationCode)")
3  .save("Sailboat", sailboat);
4
5console.log("Skipper " + sailboat.skipper.firstName + " now commands " + sailboat.name);
4

Resolve both sides of the relationship

Query the sailboat and resolve the skipper to verify the cascade save, then fetch the skipper and resolve the sailboat to confirm the inverse.
1const savedSailboat = await db
2  .from("Sailboat")
3  .where(eq("registrationCode", "NCC1701"))
4  .resolve("skipper")
5  .firstOrNull();
6
7const savedSkipper = await db
8  .from("Skipper")
9  .where(eq("id", "skipper_001"))
10  .resolve("sailboat")
11  .firstOrNull();
12
13console.log("Sailboat skipper: " + (savedSailboat?.skipper?.firstName ?? "unknown"));
14console.log("Skipper's boat: " + (savedSkipper?.sailboat?.name ?? "unassigned"));

Need Help?

If you have any questions or need assistance: