npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

nn-model

v1.3.7

Published

A useful model for NodeJS

Readme

NN-Model

A TypeScript ORM built on top of Knex.js with decorator-based model definitions and powerful eager loading support.

Features

  • 🎯 Decorator-based model definitions - Clean, type-safe model definitions using TypeScript decorators
  • 🔗 Eager loading - Load related data with include().thenInclude() chains
  • Field validation - Automatic validation based on schema decorators
  • 🔄 Transaction support - Full transaction support for CRUD operations
  • 🗄️ Multiple connections - Manage multiple database connections
  • 📦 Auto migrations - Generate migrations from model definitions

Installation

npm install ../model knex

# Install your database driver
npm install mysql2     # MySQL
npm install pg         # PostgreSQL
npm install sqlite3    # SQLite
npm install mssql      # SQL Server

Quick Start

1. Setup Connection

import "reflect-metadata";
import { Model, ConnectionManager } from "../model";
import Knex from "knex";

// Configure connections
const connections = new Map<string, Knex.Config>();
connections.set("default", {
  client: "mysql2",
  connection: {
    host: "localhost",
    user: "root",
    password: "password",
    database: "mydb",
  },
});

// Initialize
ConnectionManager.setConnections(connections);

2. Define Models

import {
  Model,
  ModelDecorator,
  PrimaryKey,
  String,
  Number,
  HasMany,
  BelongsTo,
} from "../model";

@ModelDecorator()
export class User extends Model<User> {
  protected tableName = "users";
  public primaryKey = "id";

  @PrimaryKey({ type: "increments" })
  public id?: number;

  @String({ max: 100 })
  public name?: string;

  @String({ max: 255 })
  public email?: string;

  @HasMany({ tableName: "orders", fk: "user_id", lk: "id" })
  public orders?: Order[];
}

@ModelDecorator()
export class Order extends Model<Order> {
  protected tableName = "orders";
  public primaryKey = "id";

  @PrimaryKey({ type: "increments" })
  public id?: number;

  @Number()
  public user_id?: number;

  @Decimal({ precision: 10, scale: 2 })
  public total?: number;

  @BelongsTo({ tableName: "users", fk: "user_id", lk: "id" })
  public user?: User;

  @HasMany({ tableName: "order_details", fk: "order_id", lk: "id" })
  public details?: OrderDetail[];
}

3. TypeScript Configuration

Add to your tsconfig.json:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

Decorators

Class Decorator

| Decorator | Description | | ------------------- | --------------------------------------- | | @ModelDecorator() | Required. Registers the model class |

Property Decorators

| Decorator | Options | Description | | --------------- | ---------------------------------------- | ---------------------- | | @PrimaryKey() | { type: 'increments' \| 'uuid' } | Primary key field | | @String() | { max?: number, nullable?: boolean } | String field | | @Number() | { nullable?: boolean } | Integer field | | @Decimal() | { precision?: number, scale?: number } | Decimal field | | @Json() | { nullable?: boolean } | JSON field | | @ForeignKey() | { table, column, onDelete? } | Foreign key constraint |

Relation Decorators

| Decorator | Options | Description | | -------------- | ----------------------- | ------------------------ | | @HasMany() | { tableName, fk, lk } | One-to-many relationship | | @HasOne() | { tableName, fk, lk } | One-to-one relationship | | @BelongsTo() | { tableName, fk, lk } | Inverse relationship |

Options:

  • tableName: Related table name
  • fk: Foreign key column in related table
  • lk: Local key column (defaults to primary key)

CRUD Operations

Create

const user = new User();

// Single insert
const newUser = await user.create({
  name: "John Doe",
  email: "[email protected]",
});

// Bulk insert
const users = await user.createBulk([
  { name: "User 1", email: "[email protected]" },
  { name: "User 2", email: "[email protected]" },
]);

Read

const user = new User();

// Get all
const allUsers = await user.all();

// Get with conditions
const users = await user.where("name", "like", "%John%").get();

// Get first match
const firstUser = await user.where("id", 1).first();

// Find by ID
const foundUser = await user.find(1);

Update

const user = new User();

// Update by ID
await user.update({ name: "Jane Doe" }, 1);

// Save (insert or update)
await user.save({ id: 1, name: "Jane Doe" });

// Upsert
await user.upsert({ id: 1, name: "Jane" }, "id");

Delete

const user = new User();

// Delete by ID
await user.delete(1);

Eager Loading (Relations)

Basic Include

const user = new User();

// Load single relation
const usersWithOrders = await user.include("orders").get();

Nested Includes

// 2-level nesting
const users = await user.include("orders").thenInclude("details").get();

// 3-level nesting
const users = await user
  .include("orders")
  .thenInclude("details")
  .thenInclude("product")
  .get();

// 4-level nesting
const users = await user
  .include("orders")
  .thenInclude("details")
  .thenInclude("product")
  .thenInclude("category")
  .get();

Multiple Relations

// Parallel includes (back to root after thenInclude chain)
const users = await user
  .include("orders")
  .thenInclude("details")
  .include("payments") // Resets to root (User)
  .thenInclude("paymentDetails")
  .include("coupons") // Another root relation
  .get();

With Query Conditions

// Filter nested relations
const users = await user
  .include("orders", (qb) => qb.where("total", ">", 100))
  .thenInclude("details", (qb) => qb.where("quantity", ">", 1))
  .get();

// Combine with root conditions
const users = await user
  .where("name", "like", "%John%")
  .include("orders", (qb) => qb.orderBy("created_at", "desc"))
  .get();

Access Loaded Data

const users = await user.include("orders").get();

for (const u of users) {
  console.log(u.name);
  console.log(u.orders); // Already loaded

  // Get model instance for further operations
  const userInstance = u.instance();
  await userInstance.update({ name: "Updated" });
}

Transactions

import { ConnectionManager } from "../model";

await ConnectionManager.transaction(async (trx) => {
  const user = new User();
  const order = new Order();

  const newUser = await user.create(
    {
      name: "John",
      email: "[email protected]",
    },
    trx,
  );

  await order.create(
    {
      user_id: newUser.id,
      total: 99.99,
    },
    trx,
  );

  // Transaction automatically commits
  // If error thrown, automatically rolls back
});

Multiple Connections

const connections = new Map<string, Knex.Config>();

connections.set("default", {
  client: "mysql2",
  connection: {
    /* ... */
  },
});

connections.set("analytics", {
  client: "pg",
  connection: {
    /* ... */
  },
});

ConnectionManager.setConnections(connections);

// Switch connection
await ConnectionManager.switchConnection("analytics");
const analyticsData = await new AnalyticsModel().all();

// Switch back
await ConnectionManager.switchConnection("default");

Migrations

The model can auto-generate and run migrations based on decorators:

const user = new User();

// Create table from model definition
await user.migrate();

// Drop table
await user.drop();

Query Builder Methods

All Knex query builder methods are available:

const user = new User();

// Where clauses
user.where("id", 1);
user.where("name", "like", "%John%");
user.whereIn("id", [1, 2, 3]);
user.whereNull("deleted_at");
user.whereBetween("age", [18, 65]);

// Ordering
user.orderBy("created_at", "desc");

// Limiting
user.limit(10).offset(20);

// Selecting specific columns
user.select("id", "name", "email");

// Chaining
const results = await user
  .where("active", true)
  .orderBy("name")
  .limit(10)
  .include("orders")
  .get();

Full Example

import "reflect-metadata";
import {
  Model,
  ModelDecorator,
  ConnectionManager,
  PrimaryKey,
  String,
  Decimal,
  HasMany,
  BelongsTo,
  ForeignKey,
} from "../model";

// Setup connection
const connections = new Map();
connections.set("default", {
  client: "mysql2",
  connection: {
    host: "localhost",
    user: "root",
    password: "password",
    database: "shop",
  },
});
ConnectionManager.setConnections(connections);

// Define models
@ModelDecorator()
class Category extends Model<Category> {
  protected tableName = "categories";
  @PrimaryKey({ type: "increments" }) id?: number;
  @String({ max: 100 }) name?: string;
  @HasMany({ tableName: "products", fk: "category_id", lk: "id" })
  products?: Product[];
}

@ModelDecorator()
class Product extends Model<Product> {
  protected tableName = "products";
  @PrimaryKey({ type: "increments" }) id?: number;
  @ForeignKey({ table: "categories", column: "id" }) category_id?: number;
  @String({ max: 200 }) name?: string;
  @Decimal({ precision: 10, scale: 2 }) price?: number;
  @BelongsTo({ tableName: "categories", fk: "category_id", lk: "id" })
  category?: Category;
}

// Usage
async function main() {
  const category = new Category();
  const product = new Product();

  // Create tables
  await category.migrate();
  await product.migrate();

  // Insert data
  const electronics = await category.create({ name: "Electronics" });
  await product.create({
    name: "iPhone 15",
    price: 999.99,
    category_id: electronics.id,
  });

  // Query with relations
  const categories = await category.include("products").get();

  for (const cat of categories) {
    console.log(`${cat.name}: ${cat.products?.length} products`);
  }

  // Cleanup
  await ConnectionManager.destroy();
}

main();

License

MIT