Netpress Laravel-inspired backend framework for Node.js
Frameworkv0.1.14 Starterv0.1.12 Docsv1.0.3
Overview Installation Architecture CLI
Data

Models

Netpress ships with an Eloquent-style ORM that now works across SQL and MongoDB through one shared model API. Application models live in app/Models/ and resolve their driver at query time.

Quick Example

// app/Models/Post.js
import { BaseModel } from "@admicaa/netpress";
import User from "./User.js";

export default class Post extends BaseModel {
  static connection = "mysql";

  static fillable = ["userId", "title", "slug", "body", "status", "publishedAt"];

  static casts = {
    publishedAt: "date",
    createdAt: "date",
    updatedAt: "date",
  };

  author() {
    return this.belongsTo(User, "userId");
  }
}
const post = await Post.create({
  userId: 1,
  title: "Shared models",
  body: "One API across SQL and Mongo.",
  status: "published",
});

const rows = await Post.whereLike("title", "Shared%")
  .with("author")
  .latest()
  .get();

How Model Resolution Works

BaseModel keeps the application-facing behavior shared and delegates storage details to runtime adapters:

  • SqlModelAdapter handles table introspection and SQL writes
  • MongoModelAdapter builds schemas from the migration manifest and handles Mongo writes
  • Model.query() resolves to SqlQueryBuilder or MongoQueryBuilder

You do not create different model classes per driver anymore.

Static Configuration

StaticPurposeDefault
connectionPin the model to a connection or driveractive DB_CONNECTION
tableForce a table namepluralized snake_case of the class name
collectionForce a Mongo collection namenull
primaryKeyPrimary key field"id"
schemaLegacy Mongo schema overridenull
schemaOptionsExtra Mongoose schema options merged onto the inferred schema{}
hiddenFields stripped by toJSON()[]
castsPer-field coercion on hydration{}
fillableMass-assignment allow listnull
guardedMass-assignment deny list[]
softDeletesEnable soft deletesfalse
deletedAtColumnSoft-delete column name"deletedAt"

Supported Casts

CastEffect
int, integerparseInt(value, 10)
float, numberNumber(value)
boolean, boolcoercion to boolean
stringString(value)
date, datetimenew Date(value)
json, objectJSON.parse(...) when the stored value is a string

Query Entry Points

The model exposes both explicit and convenience entry points:

Post.query();
Post.newQuery();
Post.where("status", "published");
Post.whereLike("title", "Shared%");
Post.whereFullText(["title", "body"], "shared models");
Post.with("author");
Post.latest();
Post.withTrashed();

Query Builder Surface

Filtering

MethodNotes
where(column, value)equality shorthand
where(column, operator, value)explicit operator
where({ ... })object shorthand
where(q => ...)nested group
orWhere(...)OR variant
whereNot(column, value)!=
whereIn(column, values)includes orWhereIn and whereNotIn
whereNull(column)includes orWhereNull and whereNotNull / orWhereNotNull
whereBetween(column, [a, b])includes whereNotBetween
whereRaw(sql, bindings)SQL only; Mongo throws
whereLike(column, value)SQL uses native LIKE behavior; Mongo compiles to $regex
whereFullText(columns, value)SQL compiles per dialect; Mongo compiles to $text
whereHas(name, constraint?)relation existence
orWhereHas(name, constraint?)OR relation existence
whereDoesntHave(name, constraint?)negative relation existence
orWhereDoesntHave(name, constraint?)OR negative relation existence
has(name)alias of whereHas(name)
doesntHave(name)alias of whereDoesntHave(name)

Eager Loading And Flow

MethodNotes
with("posts")eager load one relation
with(["posts", "profile"])eager load many
with("posts.author")nested eager loading
with("posts", q => q.where(...))constrained eager loading
with({ posts: q => q.where(...) })object syntax
when(condition, cb, otherwise?)conditional builder branch
tap(cb)inspect or mutate the builder inline

Ordering, Paging, And Soft Deletes

MethodNotes
orderBy(column, direction)base ordering
latest(column)descending order
oldest(column)ascending order
reorder(column?, direction?)clears existing ordering first
limit(n), take(n)limit rows
offset(n), skip(n)offset rows
forPage(page, perPage)page slice
withTrashed()include soft-deleted rows
onlyTrashed()only soft-deleted rows
withoutTrashed()reset to default behavior

Terminal Methods

MethodResult
get()array of model instances
first()single instance or null
find(id)primary-key lookup
find([ids])whereIn(primaryKey, ids)
firstWhere(...)where(...).first()
firstOrFail(where?)throws ModelNotFoundError
findOrFail(id)throws ModelNotFoundError
findOr(id, fallback)resolves fallback when not found
value(column)first row value
pluck(column, key?)values or keyed object
count()count of matching rows
exists()boolean
doesntExist()boolean
sum, avg, min, maxaggregates
paginate(page, perPage){ data, total, page, perPage, lastPage }
chunk(size, callback)batch iteration
each(callback)row iteration
firstOrCreate(attrs, values?)find or insert
firstOrNew(attrs, values?)find or instantiate unsaved
updateOrCreate(attrs, values)upsert-style helper
update(data)bulk update
delete()bulk delete
forceDelete()bulk hard delete
restore()bulk restore

Static Convenience Methods

Most common builder methods are also available directly on the model:

await Post.all();
await Post.find(1);
await Post.findOrFail(1);
await Post.first({ status: "draft" });
await Post.firstWhere("slug", "hello-world");
await Post.whereIn("id", [1, 2, 3]).get();
await Post.whereLike("title", "Shared%");
await Post.whereFullText(["title", "body"], "shared");
await Post.count({ status: "published" });
await Post.sum("views");
await Post.paginate({ status: "published" }, 1, 15);
await Post.destroy(1, 2, 3);
await Post.truncate();

Instance Methods

Hydrated rows are real model instances, not plain objects. That gives you instance-level helpers:

const post = await Post.find(1);

post.title = "Renamed";
await post.save();
await post.update({ status: "draft" });
await post.delete();
await post.forceDelete();
await post.restore();

post.trashed();
post.getKey();
post.getKeyName();
post.is(otherPost);
post.isNot(otherPost);

const fresh = await post.fresh();
await post.refresh();
const copy = post.replicate(["slug"]);

Mass Assignment And Writes

All write paths filter payloads through the model's writable fields:

  • if fillable is set, only those fields are written
  • if guarded contains "*", nothing is mass assignable
  • otherwise unknown fields are dropped automatically

That behavior applies across create, update, relation writes, and bulk builder updates.

Hooks

BaseModel.on(event, handler) supports:

  • creating
  • created
  • updating
  • updated
  • deleting
  • deleted
  • forceDeleting
  • forceDeleted
  • restoring
  • restored

Returning false from the “before” hooks stops the operation.

Soft Deletes

Enable soft deletes with:

static softDeletes = true;

The model must have a nullable deletedAt column, or whatever name you set in static deletedAtColumn.

Relations

Relation factories still live as instance methods on the model:

  • hasOne
  • hasMany
  • belongsTo
  • belongsToMany
  • morphOne
  • morphMany
  • morphTo
  • morphToMany
  • morphedByMany

See Relationships for the detailed guide.