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

knex-tools

v3.2.1

Published

A utility library that extends Knex.js with query building capabilities, filtering, pagination, sorting, and transaction management.

Readme

knex-tools

Query builder extension for Node.js - Extends Knex.js with filtering, GraphQL-style data fetching, exists clause filtering, and JOIN capabilities.

npm version Node.js Version License Coverage

📦 Installation

npm install knex-tools

🔥 Why knex-tools?

🎯 Filtering

Rich filtering with the applyWhereClauses method. Model definition is not required for standard operators (see Operators Reference).

const { applyWhereClauses } = require('knex-tools')

const query = knex('users as u').select('*')

// Apply filtering - no model definition needed for standard operators
applyWhereClauses(query, 'u', {
  where: {
    age: { gte: 21, lte: 65 }, // Range queries
    name: { contains: 'John' }, // Text search
    email: { endsWith: '@company.com' }, // Pattern matching
    role: { in: ['admin', 'manager'] }, // List queries
    deletedAt: { isNull: true }, // Null checks
    department: {
      in: ['IT', 'Sales'],
      _condition: user.canViewAll // Dynamic conditions
    }
  }
})

const users = await query

🔒 Exists Clause Filtering

The _exists operator in applyWhereClauses enables row-level security. Model definition with relations is required for this operator.

const { applyWhereClauses } = require('knex-tools')

const query = knex('posts as p').select('*')

// _exists requires model definition with relations
applyWhereClauses(
  query,
  'p',
  {
    where: {
      _exists: {
        // Only show posts user can access
        user: {
          id: currentUser.id,
          role: { in: ['admin', 'owner'] }
        }
      }
    }
  },
  postModel.relations // Model relations required for _exists
)

const posts = await query

📊 GraphQL-Style Data Fetching

The buildQuery method enables efficient nested data loading without N+1 queries.

const { buildQuery } = require('knex-tools')

// Nested data fetching with metadata
const result = await buildQuery(knex, userModel, {
  projection: 'details',
  where: {
    role: 'admin',
    active: true,
    lastLogin: { gte: '2024-01-01' }
  },
  each: {
    posts: {
      projection: 'summary',
      where: { published: true }
    }
  },
  orderBy: { created_at: 'desc' },
  take: 10,
  metadata: {
    counts: { total: true, filtered: true }
  }
})

// Result:
// {
//   data: [
//     {
//       id: 1,
//       name: 'John Doe',
//       email: '[email protected]',
//       role: 'admin',
//       posts: {
//         data: [{ id: 10, title: 'Post Title', user_id: 1 }]
//       }
//     }
//   ],
//   metadata: { counts: { total: 500, filtered: 12 } }
// }

// Efficient nested loading (no N+1 queries)
const posts = await buildQuery(knex, postModel, {
  each: {
    author: { projection: 'profile' },
    comments: {
      projection: 'details',
      orderBy: { created_at: 'desc' },
      take: 5
    },
    tags: { projection: 'name' }
  }
})

📈 Metadata & Count Tracking

const { buildQuery } = require('knex-tools')

// Get counts alongside your data
const result = await buildQuery(knex, userModel, {
  projection: 'details',
  where: { active: true },
  metadata: {
    counts: {
      total: true, // Total users in database
      filtered: true // Users matching where clause
    }
  },
  each: {
    posts: {
      projection: 'summary',
      metadata: { counts: { total: true } } // Also count user's posts
    }
  }
})

// Result structure:
// {
//   data: [{
//     id: 1, name: 'John',
//     posts: {
//       data: [...],
//       metadata: { counts: { total: 5 } }
//     }
//   }],
//   metadata: { counts: { total: 1000, filtered: 25 } }
// }

✅ Existence Checks

When you only need to know if records exist (without fetching them), use exists for optimal performance. It uses SELECT 1 LIMIT 1 internally.

const { exists } = require('knex-tools')

// Check if any users exist
const hasUsers = await exists(knex, userModel, {})
// Returns: true or false

// Check if specific records exist
const hasAdmins = await exists(knex, userModel, {
  where: { role: 'admin' }
})
// Returns: true or false

// Combine filters and modifiers
const hasActiveAdmins = await exists(knex, userModel, {
  where: {
    role: 'admin',
    lastLogin: { gte: '2024-01-01' }
  },
  modifiers: {
    activeOnly: {}
  }
})
// Returns: true or false

🔢 Count-Only Queries

When you only need counts without fetching data, use counts for better performance.

const { counts } = require('knex-tools')

// Get total and filtered counts
const counts = await counts(knex, userModel, {
  where: { active: true },
  counts: {
    total: true, // Total records in table
    filtered: true // Records matching where clause
  }
})
// Returns: { total: 1000, filtered: 250 }

// Use with modifiers for custom counts
const counts = await counts(knex, userModel, {
  where: { role: 'admin' },
  counts: {
    total: true,
    filtered: true,
    modifiers: {
      inactive: { active: false } // Count inactive users
    }
  }
})
// Returns: { total: 1000, filtered: 50, inactive: 100 }

🔢 Related Counts

Get counts of related records per parent record efficiently with withRelatedCounts. This performs a single optimized GROUP BY query per relation instead of N+1 queries.

const { buildQuery } = require('knex-tools')

// Get counts of related items for each record
const result = await buildQuery(knex, userModel, {
  projection: 'details',
  withRelatedCounts: {
    posts: true, // Count all posts per user
    folders: true // Count all folders per user
  }
})
// Result:
// {
//   data: [
//     { id: 1, name: 'John', _counts: { posts: 15, folders: 3 } },
//     { id: 2, name: 'Jane', _counts: { posts: 8, folders: 5 } }
//   ]
// }

// Filter related counts with where conditions
const result = await buildQuery(knex, userModel, {
  projection: 'details',
  withRelatedCounts: {
    posts: { where: { published: true } } // Only count published posts
  }
})
// Result:
// {
//   data: [
//     { id: 1, name: 'John', _counts: { posts: 10 } }, // Only published posts counted
//     { id: 2, name: 'Jane', _counts: { posts: 0 } }   // No published posts
//   ]
// }

// Combine with nested relations and their counts
const result = await buildQuery(knex, userModel, {
  projection: 'details',
  withRelatedCounts: {
    folders: true
  },
  each: {
    posts: {
      projection: 'summary',
      withRelatedCounts: {
        comments: true,
        tags: { where: { active: true } }
      }
    }
  }
})
// Result:
// {
//   data: [
//     {
//       id: 1,
//       name: 'John',
//       _counts: { folders: 3 },
//       posts: {
//         data: [
//           { id: 10, title: 'Post 1', _counts: { comments: 5, tags: 2 } },
//           { id: 11, title: 'Post 2', _counts: { comments: 3, tags: 1 } }
//         ]
//       }
//     }
//   ]
// }

Supported Relations:

  • hasMany - Counts related records (e.g., user → posts)
  • manyToMany - Counts through junction table (e.g., post → tags)

🏗️ Horizontal Table Partitioning

Modifiers are reusable query functions defined in your model. The special default modifier is automatically applied by buildQuery to every query, making it perfect for creating logical data partitions (e.g., active-only views, tenant isolation).

const { buildQuery } = require('knex-tools')

// Create logical views with default modifiers
const activeUserModel = {
  ...userModel,
  modifiers: {
    // Special 'default' modifier - automatically applied to all queries
    default: (query, alias) => {
      query.where(`${alias}.active`, true).whereNull(`${alias}.deleted_at`)
    }
  }
}

// Automatically filtered - no parameters needed
const activeUsers = await buildQuery(knex, activeUserModel, {
  projection: 'details'
})
// Only returns active, non-deleted users automatically

📖 Core Concepts

Query Building

const {
  applyWhereClauses,
  applySortingClauses,
  applyPagingClauses
} = require('knex-tools')

const query = knex('users as u').select('*')

// Rich filtering
applyWhereClauses(
  query,
  'u',
  {
    where: {
      age: { gte: 18 },
      OR: [{ name: { startsWith: 'John' } }, { email: { contains: '@admin' } }]
    }
  },
  userModel.relations
)

// Multi-field sorting
applySortingClauses(query, 'u', {
  role: 'asc',
  created_at: 'desc'
})

// Pagination
applyPagingClauses(query, {
  skip: 20,
  take: 10
})

const users = await query

Model Definition Structure

const userModel = {
  tableName: 'users',
  alias: 'u',

  // Function-based projections with alias support (REQUIRED for buildQuery)
  projections: {
    details: (knexInstanceOrQuery, alias, relationName = null) => [
      `${alias}.id`,
      `${alias}.name`,
      `${alias}.email`,
      `${alias}.role`
    ],
    summary: (knexInstanceOrQuery, alias, relationName = null) => [
      `${alias}.id`,
      `${alias}.name`
    ]
  },

  // Relations for data fetching and security
  relations: {
    posts: {
      type: 'hasMany',
      model: 'post',
      table: 'posts',
      foreignKey: 'user_id',
      primaryKey: 'id',
      modelDefinition: () => require('./post.model')
    },
    tags: {
      type: 'manyToMany',
      model: 'tag',
      table: 'tags',
      primaryKey: 'id',
      through: {
        table: 'user_tags',
        alias: 'ut', // Junction table alias (required)
        foreignKey: 'user_id',
        otherKey: 'tag_id'
      },
      modelDefinition: () => require('./tag.model')
    }
  },

  // Modifiers for reusable query logic
  modifiers: {
    forRole: (query, knex, alias, { role }) => {
      query.where(`${alias}.role`, role)
    },
    withMinPosts: (query, knex, alias, { minCount }) => {
      query
        .join('posts', `${alias}.id`, 'posts.user_id')
        .groupBy(`${alias}.id`)
        .having(knex.raw('COUNT(posts.id)'), '>=', minCount)
    }
  }
}

Using Named Modifiers

// Apply named modifiers with parameters
const adminUsers = await buildQuery(knex, userModel, {
  projection: 'details',
  modifiers: {
    forRole: { role: 'admin' }
  }
})

// Combine multiple modifiers
const prolificAdmins = await buildQuery(knex, userModel, {
  projection: 'details',
  modifiers: {
    forRole: { role: 'admin' },
    withMinPosts: { minCount: 10 }
  }
})

⚡ Fluent API

Build queries with a chainable, type-safe interface as an alternative to plain object configuration:

QueryBuilder

Fluent interface for buildQuery:

const { query } = require('knex-tools')

// Basic query
const result = await query(userModel)
  .projection('details')
  .where(w =>
    w.equals('active', true).in('role', ['admin', 'manager']).gte('age', 18)
  )
  .orderBy('created_at', 'desc')
  .take(10)
  .execute(knex)

// Nested data fetching with relations
const posts = await query(postModel)
  .projection('details')
  .with('author', 'profile')
  .with('comments', q =>
    q
      .projection('summary')
      .where(w => w.equals('approved', true))
      .orderBy('created_at', 'desc')
      .take(5)
  )
  .withCounts('tags')
  .execute(knex)

CountsBuilder

Fluent interface for counts:

const { countQuery } = require('knex-tools')

// Get total and filtered counts
const result = await countQuery(userModel)
  .where(w => w.equals('role', 'admin'))
  .total()
  .filtered()
  .execute(knex)
// Returns: { total: 1000, filtered: 50 }

// With modifiers
const result = await countQuery(userModel)
  .where(w => w.gte('age', 18))
  .total()
  .filtered()
  .modifier('inactive', { active: false })
  .execute(knex)
// Returns: { total: 1000, filtered: 800, inactive: 200 }

ExistsBuilder

Fluent interface for exists:

const { existsQuery } = require('knex-tools')

// Check if records exist
const hasAdmins = await existsQuery(userModel)
  .where(w => w.equals('role', 'admin'))
  .execute(knex)
// Returns: true or false

// With modifiers
const hasActiveAdmins = await existsQuery(userModel)
  .where(w => w.equals('role', 'admin'))
  .modifier('activeOnly')
  .execute(knex)
// Returns: true or false

WhereBuilder

Build complex where conditions for any query function:

const { where, applyWhereClauses } = require('knex-tools')

// Build conditions with fluent API
const filters = where()
  .equals('verified', true)
  .or(or =>
    or.contains('email', '@company.com').contains('email', '@partner.com')
  )
  .and(and => and.gte('age', 18).lte('age', 65))
  .build()

// Use with applyWhereClauses
const query = knex('users as u').select('*')
applyWhereClauses(query, 'u', { where: filters }, userModel.relations)
const users = await query

// Or use directly with query builders
const result = await query(userModel)
  .where(w =>
    w
      .equals('active', true)
      .or(or => or.equals('role', 'admin').equals('role', 'manager'))
  )
  .execute(knex)

See: Full Fluent API Documentation

🏢 Advanced Features

🔗 JOINs

// Complex data fetching with JOINs
const result = await processJoins(
  knex('users as u').select(['u.*', 'p.title', 't.name as tag_name']),
  userModel,
  {
    posts: {
      type: 'enforce', // INNER JOIN - only users with posts
      on: { published: true, status: 'approved' }
    },
    tags: {
      type: 'include', // LEFT JOIN - all users, with/without tags
      on: { active: true, category: { not: 'private' } }
    },
    profile: {
      on: { verified: true },
      where: { updated_at: { gte: '2024-01-01' } } // Post-JOIN filtering
    }
  },
  userModel.relations
)

// Advanced: Nested JOINs for hierarchical data
const nestedResult = await processJoins(
  knex('posts as p').select('*'),
  postModel,
  {
    author: {
      join: {
        department: {
          // author.department relation
          type: 'enforce',
          on: { active: true }
        }
      }
    }
  },
  postModel.relations
)
// SQL: INNER JOIN users as u ON p.author_id = u.id
//      INNER JOIN departments as d ON u.department_id = d.id AND d.active = true

Performance Optimization

  • Smart batching - Eliminates N+1 queries
  • Efficient projections - Only fetch needed columns
  • Optimized JOINs - Minimal database round trips
  • Alias management - Prevents SQL conflicts

🔧 API

| Function | Purpose | | ---------------------- | ------------------------------- | | exists | Lightweight existence checks | | counts | Lightweight count-only queries | | buildQuery | GraphQL-style data fetching | | applyWhereClauses | Rich filtering with operators | | applySortingClauses | Multi-field sorting | | applyPagingClauses | Pagination with skip/take | | processJoins | JOIN operations with conditions | | buildMakeTransaction | Transaction management |

📚 Documentation

🧪 Test Coverage

knex-tools maintains high test coverage to ensure reliability and stability.

Query Execution (knex-tools.js)

Core query building, filtering, sorting, pagination, and data fetching logic.

| Metric | Coverage | | ---------- | -------- | | Statements | 100% | | Branches | 91.38% | | Functions | 100% | | Lines | 100% |

Query Building (query-builder.js)

Fluent API for WhereBuilder, QueryBuilder, CountsBuilder, and ExistsBuilder.

| Metric | Coverage | | ---------- | -------- | | Statements | 100% | | Branches | 100% | | Functions | 100% | | Lines | 100% |

Test Suite: 248 tests passing

Run coverage report:

npm run test:coverage

📄 License

Apache 2.0 © Hassaan