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"));