Data
Relationships
Relationships are defined as instance methods on shared BaseModel subclasses. The relation object is itself a query builder, so you can chain filters, eager loads, pagination, and write helpers on top of it.
Supported Relation Types
| Relation | Factory |
|---|---|
| one-to-one | hasOne(Related, foreignKey?, localKey?) |
| one-to-many | hasMany(Related, foreignKey?, localKey?) |
| inverse belongs-to | belongsTo(Related, foreignKey?, ownerKey?) |
| many-to-many | belongsToMany(Related, pivotTable, foreignPivotKey?, relatedPivotKey?, parentKey?, relatedKey?) |
| polymorphic one | morphOne(Related, name, typeColumn?, idColumn?, localKey?) |
| polymorphic many | morphMany(Related, name, typeColumn?, idColumn?, localKey?) |
| inverse polymorphic | morphTo(name, typeColumn?, idColumn?, classResolver?) |
| polymorphic many-to-many | morphToMany(Related, name, pivotTable, ...) |
| inverse polymorphic many-to-many | morphedByMany(Related, name, pivotTable, ...) |
Quick Example
import { BaseModel } from "@admicaa/netpress";
import Post from "./Post.js";
import Profile from "./Profile.js";
import Image from "./Image.js";
import Tag from "./Tag.js";
export default class User extends BaseModel {
posts() {
return this.hasMany(Post);
}
profile() {
return this.hasOne(Profile);
}
image() {
return this.morphOne(Image, "imageable");
}
tags() {
return this.morphToMany(Tag, "taggable", "taggables");
}
}
Eager Loading
Netpress now supports all of these eager-load forms:
await User.with("posts").get();
await User.with(["posts", "profile"]).get();
await User.with("posts.author").get();
await User.with("posts", (q) => q.where("published", true)).get();
await User.with({ posts: (q) => q.orderBy("title", "desc") }).get();
Relation Existence Queries
The shared query builder also supports relation existence helpers:
await User.whereHas("posts").get();
await User.whereHas("posts", (q) => q.where("published", true)).get();
await User.orWhereHas("posts").get();
await User.whereDoesntHave("posts").get();
await User.orWhereDoesntHave("posts").get();
await User.has("posts").get();
await User.doesntHave("posts").get();
These helpers work across the shared query layer and are resolved into driver-safe filters before execution.
Common Usage
const user = await User.find(1);
await user.posts().get();
await user.posts().where("published", true).latest().get();
await user.profile().first();
await user.tags().attach([1, 2, 3]);
const post = await Post.find(42);
await post.author().first();
await post.comments().create({ body: "Nice post" });
Polymorphic Relations
Morph relations are now part of the implemented API, not just planned behavior.
Example:
class Comment extends BaseModel {
commentable() {
return this.morphTo("commentable");
}
}
To keep morph type values stable across refactors, register aliases:
BaseModel.registerMorphMap({
post: Post,
user: User,
comment: Comment,
});
Reset the map in tests with:
BaseModel.resetMorphMap();
Notes On Defaults
hasOneandhasManydefault to<parentClassName>IdbelongsTodefaults to<relatedClassName>Id- morph helpers default to
<name>Typeand<name>Id - many-to-many helpers still let you override every pivot and key column explicitly
Use the explicit arguments whenever your schema deviates from the default camelCase conventions.