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

Database

Netpress no longer treats the database layer as “SQL first with optional extras.” The starter now supports MongoDB and SQL through one shared model, migration, and seeder structure, while the framework resolves the correct runtime internally.

Quick Example

// config/database.js
export default {
  default: process.env.DB_CONNECTION || "mongo",
  // Opened at boot. Empty = fully lazy (connections open on first use).
  preload: [],
  connections: {
    mongo: {
      uri: process.env.MONGO_URI || "mongodb://localhost:27017/netpress",
    },
    mysql: {
      host: process.env.DB_HOST || "127.0.0.1",
      port: parseInt(process.env.DB_PORT, 10) || 3306,
      database: process.env.DB_DATABASE || "netpress",
      username: process.env.DB_USERNAME || "root",
      password: process.env.DB_PASSWORD || "",
      dialect: "mysql",
    },
    postgres: {
      host: process.env.DB_HOST || "127.0.0.1",
      port: parseInt(process.env.DB_PORT, 10) || 5432,
      database: process.env.DB_DATABASE || "netpress",
      username: process.env.DB_USERNAME || "root",
      password: process.env.DB_PASSWORD || "",
      dialect: "postgres",
    },
    sqlite: {
      storage: process.env.DB_SQLITE_PATH || ":memory:",
      dialect: "sqlite",
    },
  },
};

How The Starter Boots The Database

DatabaseServiceProvider registers the manager with registerApplicationDatabase(...) during boot. This:

  • Seeds placeholders in the connection registry for every configured connection
  • Discovers shared models from app/Models/ (file imports only — no query runs)
  • Does not open any real database connections unless a name appears in preload

Real connections are opened lazily on first use (see Hybrid Bootstrap):

  • Model.query() / Model.find(...) / etc.
  • DB.connection(name) / DB.table(...) / DB.collection(...)
  • DB.transaction(...)
  • Migration and seeder CLI commands

The starter also exposes a small helper object:

  • db.type
  • db.connection
  • db.model(name)
  • db.transaction(callback, name)
  • db.table(name)
  • db.collection(name)

db.model(name) resolves the same model class you would normally import, but by name at runtime. The other methods proxy into the shared core DB facade, so you can still reach transactions or ad-hoc table and collection queries from starter code when needed. For named connections, use the core DB.connection(name) facade directly.

Shared Application Structure

These are the default database-facing folders now:

  • app/Models/
  • database/migrations/
  • database/seeders/

Legacy driver-specific folders are fallback-only and should not be used for new work.

Connection Resolution

Each model resolves its own driver and connection:

  1. If the model sets static connection, Netpress uses that connection or driver.
  2. Otherwise the model resolves against the active DB_CONNECTION.
  3. SQL connections normalize to the SQL runtime.
  4. mongo normalizes to the MongoDB runtime.

That means the same application API works across drivers:

import User from "../app/Models/User.js";

const user = await User.find(1);
const admins = await User.where({ role: "admin" }).paginate(1, 20);

DB Facade

Most application code should stay on shared models, but Netpress now exposes a first-class core DB facade for ad-hoc table access, named connections, raw SQL, and transactions.

import { DB } from "@admicaa/netpress";

const activeUsers = await DB.table("users")
  .where("status", "active")
  .latest("createdAt")
  .get();

const rows = await DB.select(
  "select count(*) as total from users where role = ?",
  ["admin"],
);

The ad-hoc table() and collection() helpers use the same hardened shared query builder as models, so they inherit the existing identifier and value safety checks.

Transactions

DB.transaction() scopes model writes and ad-hoc table queries to the active transaction.

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

await DB.transaction(async (trx) => {
  const user = await User.create({
    name: "Ada Lovelace",
    email: "ada@example.com",
    password: "secret",
  });

  await trx.table("users")
    .where("id", user.id)
    .update({ role: "admin" });
});

You can also target a named connection explicitly:

await DB.transaction(async () => {
  // ...
}, "mysql");

On SQL connections, the callback runs inside a real Knex transaction. On MongoDB connections, Netpress starts a session transaction and pushes that session through the shared model and query runtime.

Safety Notes

  • DB.table() and DB.collection() reject unsafe table, collection, column, operator, sort, and pagination expressions.
  • DB.select(), DB.statement(), DB.update(), and DB.delete() keep parameter binding separate from SQL and reject obvious unsafe fragments such as statement chaining and SQL comments.
  • Query builder protections still apply to Model.where(...), DB.table(...).where(...), and Mongo filters built through the shared runtime.

Lazy vs Preloaded Connections

By default the starter opens zero database sockets at boot. That is correct for most apps — connections get built on first use, only for the databases actually touched by the current request or CLI command.

To fail-fast at boot (e.g. in production, on a specific primary DB), add names to preload:

// config/database.js
export default {
  default: "mysql",
  preload: ["mysql"],                 // warm the primary at boot
  connections: { /* ... */ },
};

Or via environment variable:

DB_PRELOAD=mysql,analytics node server.js

Multi-connection apps behave the same way: each named connection is opened independently on first use, or eagerly when listed in preload.

class AuditLog extends BaseModel {
  static table = "audit_logs";
  static connection = "analytics";    // opened on first AuditLog query
}

SQLite For Tests

The starter treats in-memory SQLite as a first-class test runtime. When DB_CONNECTION=sqlite and the configured storage is :memory:, add sqlite to preload so the connection opens at boot — migrations are auto-run against in-memory SQLite during preload so tests can execute without an external database service.

// config/database.js (test env)
export default {
  default: "sqlite",
  preload: ["sqlite"],
  connections: {
    sqlite: { storage: ":memory:", dialect: "sqlite" },
  },
};

Database Commands

npm run artisan -- migrate
npm run artisan -- migrate --seed
npm run artisan -- migrate:rollback --step=1
npm run artisan -- migrate:fresh --seed
npm run artisan -- migrate:refresh --seed
npm run artisan -- migrate:status
npm run artisan -- db:seed
npm run artisan -- db:seed --seeder=UserSeeder

These commands now read the configured connections and migration plan. They do not take runtime --connection flags the way the older docs described.