Database
Model Relationships
Superflare allows you to define relationships between your models. You can then use these relationships to query related models or add related models to a model.
Relationship Types
One to One
A one-to-one relationship is a relationship between two models where one model belongs to another model. For example, a User
model might have a Profile
model which belongs to the User
model.
To define a one-to-one relationship, use the hasOne
method:
import { Model } from "superflare";
import { Profile } from "./Profile";
export class User extends Model {
profile!: Profile | Promise<Profile>;
$profile() {
return this.hasOne(Profile);
}
}
The first argument to the hasOne
method is the model class of the related model. The second argument is the name of the foreign key on the related model. If you don't specify a foreign key, Superflare will use the name of the model followed by Id
.
By default, Superflare does not load related models. You can load the relation on an existing instance by calling await
on the relation property which matches the name of the relation definition:
const user = await User.find(1);
const profile = await user.profile;
// is the same as:
const profile = await user.$profile();
Once you have loaded the profile relation once, it will be cached on the profile
property for future reference.
To define the inverse relationship, use the belongsTo
method:
import { Model } from "superflare";
export class Profile extends Model {
user!: User | Promise<User>;
$user() {
return this.belongsTo(User);
}
}
When invoking the $user()
method or awaiting the user
property, Superflare will attempt to find a Profile
model with a userId
that matches the id
of the User
model:
const profile = await Profile.find(1);
const user = await profile.user;
One to Many
A one-to-many relationship is a relationship between two models where one model has many related models. For example, a User
model might have many Post
models.
To define a one-to-many relationship, use the hasMany
method:
import { Model } from "superflare";
export class User extends Model {
posts!: Post[] | Promise<Post[]>;
$posts() {
return this.hasMany(Post);
}
}
You can then access the related models by awaiting the relation property:
const user = await User.find(1);
const posts = await user.posts;
for (const post of posts) {
console.log(post.title);
}
To define the inverse relationship, use the belongsTo
method:
import { Model } from "superflare";
export class Post extends Model {
user!: User | Promise<User>;
$user() {
return this.belongsTo(User);
}
}
When invoking the $user()
method or awaiting the user
property, Superflare will attempt to find a Post
model with a userId
that matches the id
of the User
model:
const post = await Post.find(1);
const user = await post.user;
Many to Many (soon!)
A many-to-many relationship is a relationship between two models where one model has many related models and the related models have many models. For example, a Post
model might have many Tag
models and a Tag
model might have many Post
models.
The table structure for a many-to-many relationship is often referred to as a "join table." The join table contains two foreign keys which reference the primary keys of the two models. The join table might look like this:
CREATE TABLE posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
body TEXT NOT NULL
);
CREATE TABLE tags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL
);
CREATE TABLE post_tags (
postId INTEGER NOT NULL,
tagId INTEGER NOT NULL,
PRIMARY KEY (postId, tagId),
FOREIGN KEY (postId) REFERENCES posts (id),
FOREIGN KEY (tagId) REFERENCES tags (id)
);
To define a many-to-many relationship, use the belongsToMany
method:
import { Model } from "superflare";
export class Post extends Model {
tags!: Tag[] | Promise<Tag[]>;
$tags() {
return this.belongsToMany(Tag);
}
}
You can then access the related models by awaiting the relation property:
const post = await Post.find(1);
const tags = await post.tags;
for (const tag of tags) {
console.log(tag.name);
}
When invoking the $tags()
method or awaiting the tags
property, Superflare will check an intermediate table, often referred to as a "join table," for rows which have a postId
that matches the id
of the Post
model. It will return all Tag
models which have a id
that matches the tagId
of the join table rows.
To define the inverse relationship, use the belongsToMany
method again:
import { Model } from "superflare";
export class Tag extends Model {
posts!: Post[] | Promise<Post[]>;
$posts() {
return this.belongsToMany(Post);
}
}
Eager Loading
Superflare allows you to eager load related models. This is useful when you want to load related models without having to make multiple queries, a problem often referred to as "N + 1".
To eager load related models, use the with
method:
const users = await User.with("profile").get();
You can also eager load multiple relations:
const users = await User.with("profile", "posts").get();
It is particularly important to eager-load related models when passing a model instance to a view.
Most front-end frameworks like Remix and Next.js will call JSON.stringify
on the model instance, but this will not load related models automatically.
If you don't eager load the related models, the related data will not be available in your view:
import { User } from "~/models/User";
import { json } from "@remix-run/cloudflare";
export async function loader() {
// ❌ The profile relation will not be loaded
const user = await User.find(1);
// ✅ This will load the profile relation for the view
const user = await User.with("profile").find(1);
return json({ user });
}
export default function UserView() {
const user = useLoaderData().user;
return (
<div>
<h1>{user.name}</h1>
<p>{user.profile.bio}</p>
</div>
);
}
Inserting and Updating Related Models
The save
Method
The save
method is used to add new models to a relationship:
const user = await User.find(1);
const post = new Post({ title: "Hello World" });
await user.$posts().save(post);
You must use the $
prefixed relationship function definition when saving or creating related models.
The create
Method
The create
method is used to create and add new models to a relationship:
const user = await User.find(1);
await user.$posts().create({ title: "Hello World" });
Belongs To Relationships
If you'd like to assign a child model to a parent model, you can use the associate
method:
const user = await User.find(1);
const profile = new Profile({ bio: "Hello World" });
profile.$user().associate(user);
await profile.save();
To remove the association, use the dissociate
method:
profile.$user().dissociate();
await profile.save();
Migrations and Relationships
Superflare expects you to manually define migrations which map to your relationships. For example, if your Post
model belongsTo
your User
model, you would need to include a column for userId
in a migration:
import { Schema } from "superflare";
export default () => {
return Schema.create("posts", (table) => {
table.increments("id");
table.string("title");
table.integer("userId");
});
};