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

qarjs

v1.0.3

Published

MongoDB-style queries for plain JavaScript arrays.

Readme

GitHub License npm GitHub Repo stars Run tests codecov Sponsor

What is Qar?

MongoDB-style queries for plain JavaScript arrays. Simple, lightweight, and perfect for JS Apps, static sites, and JSON data.

Table of Contents

[Use Cases] [Why Qar?] [Installation] [Quick Start] [API Reference] [Query Operators] [Nested Properties] [Field Projection] [Aggregation Pipeline] [Examples] [Why Qar over alternatives?] [TypeScript] [Browser Support] [Guidelines] [License]

Sponsoring

If you find this project useful, please consider sponsoring me by:

[Donate via GitHub] [Donate via PayPal] [Give the repo a Star] [Follow me on GitHub] [Share the project on X]

Use Cases

  • Query static JSON data in modern JS apps
  • Next.js/React/Vue/Node developers working with static JSON data
  • Frontend developers who need pipeline analytics
  • Backend developers needing in-memory querying without a database
  • Filter and search arrays of objects without a database
  • Perform complex data transformations with aggregation pipelines

Why Qar?

When you have a JSON file or an array of objects and need to filter, search, or query them with MongoDB-style syntax - without setting up a database.

import Qar from 'qarjs';

const products = new Qar(productsData);

// Simple queries
products.find({ category: 'electronics', price: { $lt: 500 } }).toArray();

// Chainable API with sorting and pagination
products.find({ inStock: true }).sort({ price: -1 }).limit(10).toArray();

// Field projection
products.find({ category: 'phones' }, { name: 1, price: 1, _id: 0 }).toArray();

// Aggregation pipeline
products.aggregate([
  { $match: { inStock: true } },
  { $group: { _id: '$category', count: { $sum: 1 } } },
  { $sort: { count: -1 } },
]);

// Get unique values
products.distinct('category'); // => ['electronics', 'clothing', 'books']

Installation

npm install qarjs
yarn add qarjs
pnpm add qarjs

Quick Start

import Qar from 'qarjs';

const data = [
  { _id: 1, name: 'Alice', age: 20, role: 'user' },
  { _id: 2, name: 'Bob', age: 30, role: 'admin' },
  { _id: 3, name: 'Carol', age: 40, role: 'user' },
];

const users = new Qar(data);

// Find all users older than 25
users.find({ age: { $gt: 25 } }).toArray();
// => [{ _id: 2, ... }, { _id: 3, ... }]

// Find one admin
users.findOne({ role: 'admin' });
// => { _id: 2, name: 'Bob', age: 30, role: 'admin' }

// Count users
users.count({ role: 'user' });
// => 2

// Check if exists
users.exists({ name: 'Alice' });
// => true

API Reference

Constructor

const collection = new Qar(arrayOfObjects);

Creates a new Qar instance with your array of objects.

Methods

find(query, projection?)

Returns a cursor for chaining operations. Call .toArray() to execute.

// Basic query
users.find({ age: { $gte: 30 } }).toArray();

// With projection
users.find({ role: 'admin' }, { name: 1, email: 1 }).toArray();

// Chainable
users.find({ active: true }).sort({ name: 1 }).skip(10).limit(5).toArray();

findOne(query, projection?)

Returns the first object matching the query, or null if not found.

users.findOne({ name: 'Alice' });
// => { _id: 1, name: 'Alice', ... } or null

users.findOne({ role: 'admin' }, { name: 1, email: 1 });
// => { name: 'Bob', email: '[email protected]' }

count(query)

Returns the number of objects matching the query.

users.count({ role: 'admin' });
// => 1

exists(query)

Returns true if at least one object matches the query, false otherwise.

users.exists({ age: { $lt: 18 } });
// => false

distinct(field)

Returns an array of unique values for the specified field.

users.distinct('role');
// => ['user', 'admin']

products.distinct('category');
// => ['electronics', 'clothing', 'books']

aggregate(pipeline)

Execute an aggregation pipeline. See Aggregation Pipeline section.

users.aggregate([{ $match: { age: { $gte: 18 } } }, { $group: { _id: '$role', count: { $sum: 1 } } }]);

toArray()

Returns a deep copy of the raw array.

users.toArray();
// => [{ ... }, { ... }, { ... }]

Cursor Methods

When you call find(), you get a cursor that supports chaining:

sort(spec)

Sort results by one or more fields.

// Single field, ascending
users.find({}).sort({ name: 1 }).toArray();

// Single field, descending
users.find({}).sort({ age: -1 }).toArray();

// Multiple fields
users.find({}).sort({ role: 1, age: -1 }).toArray();

skip(n)

Skip the first n results (useful for pagination).

users.find({}).skip(10).toArray();

limit(n)

Limit results to n items.

users.find({}).limit(5).toArray();

project(spec)

Apply field projection (alternative to passing projection to find()).

users.find({}).project({ name: 1, email: 1 }).toArray();

toArray()

Execute the query and return results as an array.

users.find({ active: true }).sort({ createdAt: -1 }).limit(10).toArray();

Query Operators

Comparison Operators

| Operator | Description | Example | | -------- | --------------------- | -------------------------------------------- | | $eq | Equal to | { age: { $eq: 30 } } or { age: 30 } | | $ne | Not equal to | { role: { $ne: 'admin' } } | | $gt | Greater than | { price: { $gt: 100 } } | | $gte | Greater than or equal | { age: { $gte: 18 } } | | $lt | Less than | { stock: { $lt: 10 } } | | $lte | Less than or equal | { rating: { $lte: 3 } } | | $in | In array | { status: { $in: ['active', 'pending'] } } | | $nin | Not in array | { role: { $nin: ['guest', 'banned'] } } |

Logical Operators

| Operator | Description | Example | | -------- | ------------------------------ | ----------------------------------------------------- | | $and | All conditions must match | { $and: [{ age: { $gt: 18 } }, { role: 'user' }] } | | $or | At least one condition matches | { $or: [{ role: 'admin' }, { role: 'moderator' }] } | | $not | Inverts the query | { $not: { age: { $lt: 18 } } } | | $nor | None of the conditions match | { $nor: [{ status: 'deleted' }, { banned: true }] } |

Element Operators

| Operator | Description | Example | | --------- | --------------------- | ------------------------------ | | $exists | Field exists (or not) | { email: { $exists: true } } | | $type | Field type check | { age: { $type: 'number' } } |

Type values: 'string', 'number', 'boolean', 'array', 'object', 'null', 'undefined', 'date'

Array Operators

| Operator | Description | Example | | ------------ | ------------------------------- | ---------------------------------------------------- | | $all | Array contains all values | { tags: { $all: ['javascript', 'nodejs'] } } | | $size | Array has exact length | { tags: { $size: 3 } } | | $elemMatch | Array contains matching element | { items: { $elemMatch: { price: { $gt: 100 } } } } |

Evaluation Operators

| Operator | Description | Example | | -------- | ------------------------------- | -------------------------------------------- | | $regex | Regular expression match | { name: { $regex: '^A', $options: 'i' } } | | $mod | Modulo operation | { qty: { $mod: [4, 0] } } (divisible by 4) | | $expr | Compare fields with expressions | { $expr: { $gt: ['$price', '$cost'] } } |

Expression Operators (used in $expr)

Comparison

$eq, $ne, $gt, $gte, $lt, $lte

// Find products where price is less than MSRP
products.find({ $expr: { $lt: ['$price', '$msrp'] } });

Arithmetic

$add, $subtract, $multiply, $divide

// Total value greater than 1000
products.find({
  $expr: { $gt: [{ $multiply: ['$price', '$quantity'] }, 1000] },
});

String

$concat, $toLower, $toUpper, $substr, $strLenCP, $indexOfCP, $split

// Concatenate fields
users.find({
  $expr: { $eq: [{ $concat: ['$firstName', ' ', '$lastName'] }, 'John Doe'] },
});

Array

$size, $arrayElemAt, $filter, $map, $reduce

// Products with more than 5 tags
products.find({ $expr: { $gt: [{ $size: '$tags' }, 5] } });

Date

$year, $month, $dayOfMonth, $hour, $minute, $second

// Posts from 2024
posts.find({ $expr: { $eq: [{ $year: '$publishDate' }, 2024] } });

Conditional

$cond, $ifNull, $switch

// Using $cond (if-then-else)
products.find({
  $expr: {
    $cond: [{ $gt: ['$stock', 0] }, { $eq: ['$status', 'available'] }, { $eq: ['$status', 'sold-out'] }],
  },
});

Type Conversion

$toString, $toInt, $toDouble, $toDate

Nested Properties

Use dot notation to query nested objects:

const data = [
  { name: 'Alice', address: { city: 'New York', zip: '10001' } },
  { name: 'Bob', address: { city: 'Los Angeles', zip: '90001' } },
];

const collection = new Qar(data);

collection.find({ 'address.city': 'New York' }).toArray();
// => [{ name: 'Alice', ... }]

Field Projection

Control which fields are returned in results.

Include/Exclude Fields

// Include only specific fields
users.find({}, { name: 1, email: 1 });
// => [{ _id: 1, name: 'Alice', email: 'alice@...' }, ...]

// Exclude _id
users.find({}, { name: 1, email: 1, _id: 0 });
// => [{ name: 'Alice', email: 'alice@...' }, ...]

// Exclude specific fields
users.find({}, { password: 0, secretKey: 0 });
// => All fields except password and secretKey

Array Slicing with $slice

Limit array elements returned:

// Get only first 3 comments
posts.find({}, { title: 1, comments: { $slice: 3 } });

// Get last 5 items
posts.find({}, { title: 1, recentViews: { $slice: -5 } });

// Multiple slices
posts.find(
  {},
  {
    title: 1,
    comments: { $slice: 3 },
    tags: { $slice: 5 },
  },
);

Positional Operator ($)

Return only the first matching array element:

// Only return the first comment by Alice
posts.find({ 'comments.author': 'Alice' }, { 'comments.$': 1 });

$elemMatch Projection

Return only the first array element matching a condition:

// Only return comments with more than 10 likes
posts.find(
  {},
  {
    title: 1,
    comments: { $elemMatch: { likes: { $gt: 10 } } },
  },
);

Aggregation Pipeline

Perform advanced data processing with aggregation pipelines.

Stages

$match

Filter documents (same syntax as find()):

users.aggregate([{ $match: { age: { $gte: 18 } } }]);

$group

Group documents and calculate aggregated values:

// Count users by role
users.aggregate([
  {
    $group: {
      _id: '$role',
      count: { $sum: 1 },
    },
  },
]);

// Multiple aggregations
products.aggregate([
  {
    $group: {
      _id: '$category',
      totalProducts: { $sum: 1 },
      avgPrice: { $avg: '$price' },
      maxPrice: { $max: '$price' },
      minPrice: { $min: '$price' },
    },
  },
]);

Accumulator Operators:

  • $sum - Sum values
  • $avg - Calculate average
  • $max - Maximum value
  • $min - Minimum value
  • $push - Build array of values
  • $first - First value in group
  • $last - Last value in group

$sort

Sort documents:

users.aggregate([{ $group: { _id: '$city', count: { $sum: 1 } } }, { $sort: { count: -1 } }]);

$project

Reshape documents and create computed fields:

users.aggregate([
  {
    $project: {
      name: 1,
      email: 1,
      fullName: { $concat: ['$firstName', ' ', '$lastName'] },
    },
  },
]);

$limit

Limit number of documents:

users.aggregate([{ $group: { _id: '$role', count: { $sum: 1 } } }, { $sort: { count: -1 } }, { $limit: 5 }]);

$unwind

Deconstruct an array field into separate documents:

const posts = new Qar([
  { title: 'Post 1', tags: ['js', 'node'] },
  { title: 'Post 2', tags: ['react', 'js'] },
]);

posts.aggregate([{ $unwind: '$tags' }, { $group: { _id: '$tags', count: { $sum: 1 } } }]);
// => [{ _id: 'js', count: 2 }, { _id: 'node', count: 1 }, { _id: 'react', count: 1 }]

You may also pass an options object instead of a string. The runtime accepts path (or the aliases $path or field) and an optional preserveNullAndEmptyArrays boolean. When preserveNullAndEmptyArrays is true, documents without the array field or with empty arrays are preserved with the unwound field set to null.

// Using object form with aliases and preserveNullAndEmptyArrays
posts.aggregate([
  { $unwind: { path: '$tags', preserveNullAndEmptyArrays: true } },
  { $group: { _id: '$tags', count: { $sum: 1 } } },
]);

Complete Example

// Analyze product sales by category
const salesReport = products.aggregate([
  // Filter in-stock products
  { $match: { inStock: true } },

  // Calculate total value per product
  {
    $project: {
      category: 1,
      totalValue: { $multiply: ['$price', '$quantity'] },
    },
  },

  // Group by category
  {
    $group: {
      _id: '$category',
      totalRevenue: { $sum: '$totalValue' },
      productCount: { $sum: 1 },
      avgValue: { $avg: '$totalValue' },
    },
  },

  // Sort by revenue
  { $sort: { totalRevenue: -1 } },

  // Top 10 categories
  { $limit: 10 },
]);

Examples

JS App Static Data

Perfect for querying JSON data in JavaScript applications:

// app/products/page.tsx
import productsData from '@/data/products.json';
import Qar from 'qarjs';

const products = new Qar(productsData);

export default function ProductsPage({ searchParams }) {
  const filtered = products
    .find({
      category: searchParams.category,
      price: { $lte: Number(searchParams.maxPrice) || 1000 },
      inStock: true,
    })
    .sort({ price: 1 })
    .skip((Number(searchParams.page) - 1) * 20)
    .limit(20)
    .toArray();

  return <ProductList items={filtered} />;
}

API Routes

// app/api/search/route.ts
import data from '@/data/items.json';
import Qar from 'qarjs';

const items = new Qar(data);

export async function GET(request) {
  const q = request.nextUrl.searchParams.get('q');

  const results = items
    .find({
      $or: [{ name: { $regex: q, $options: 'i' } }, { description: { $regex: q, $options: 'i' } }],
    })
    .limit(50)
    .toArray();

  return Response.json(results);
}

Static Site Generation

import blogPosts from './posts.json';
import Qar from 'qarjs';

const posts = new Qar(blogPosts);

// Get published posts by category
const techPosts = posts
  .find({
    category: 'technology',
    published: true,
    publishDate: { $lte: new Date().toISOString() },
  })
  .sort({ publishDate: -1 })
  .toArray();

// Get featured post
const featured = posts.findOne({ featured: true });

// Count drafts
const draftCount = posts.count({ published: false });

// Get all categories with post counts
const categories = posts.aggregate([
  { $match: { published: true } },
  { $group: { _id: '$category', count: { $sum: 1 } } },
  { $sort: { count: -1 } },
]);

Why Qar over alternatives?

| Feature | Qar | mingo | sift.js | lodash | | --------------------- | ------- | ---------- | ------- | -------- | | MongoDB-style queries | ✅ | ✅ | ✅ | ❌ | | Chainable API | ✅ | ⚠️ Limited | ❌ | ✅ | | Aggregation pipeline | ✅ | ✅ | ❌ | ❌ | | Field projection | ✅ | ✅ | ❌ | ❌ | | Expression operators | ✅ 30+ | ✅ | ❌ | ❌ | | Zero dependencies | ✅ | ❌ | ✅ | ❌ | | Bundle size (gzipped) | ✅ ~5KB | ❌ ~15KB | ✅ ~2KB | ❌ ~24KB |

Qar is perfect when you:

  • Work with static JSON data in JavaScript applications or static sites
  • Want MongoDB-style queries without a database
  • Need aggregation and data transformation
  • Want a clean, intuitive API
  • Care about bundle size

Not recommended for:

  • Very large datasets - use a real database
  • Real-time data updates - use a database with subscriptions
  • Complex JOIN operations across multiple collections

Examples

Complex Queries

const users = new Qar(userData);

// Find active admins or moderators over 25
users
  .find({
    $and: [{ age: { $gt: 25 } }, { status: 'active' }, { $or: [{ role: 'admin' }, { role: 'moderator' }] }],
  })
  .toArray();

// Users NOT in specific roles
users
  .find({
    $nor: [{ role: 'guest' }, { role: 'banned' }],
  })
  .toArray();

// Regex search (case-insensitive)
users
  .find({
    email: { $regex: '@gmail\\.com$', $options: 'i' },
  })
  .toArray();

// Compare fields
products
  .find({
    $expr: { $lt: ['$currentPrice', '$originalPrice'] },
  })
  .toArray();

Pagination

const page = 2;
const pageSize = 20;

const results = products
  .find({ category: 'electronics' })
  .sort({ price: 1 })
  .skip((page - 1) * pageSize)
  .limit(pageSize)
  .toArray();

const total = products.count({ category: 'electronics' });
const totalPages = Math.ceil(total / pageSize);

Multiple Collections

const products = new Qar(productsData);
const orders = new Qar(ordersData);
const users = new Qar(usersData);

// Query each independently
const electronics = products.find({ category: 'electronics' }).toArray();
const recentOrders = orders.find({ date: { $gte: '2026-01-01' } }).toArray();
const admins = users.find({ role: 'admin' }).toArray();

TypeScript

Full TypeScript support included:

import Qar from 'qarjs';

interface User {
  id: number;
  name: string;
  age: number;
  role: 'user' | 'admin';
}

const users = new Qar<User>(userData);

// Type-safe queries
const result = users.find({ age: { $gt: 25 } }).toArray(); // User[]
const admin = users.findOne({ role: 'admin' }); // User | null
const roles = users.distinct('role'); // string[]

Browser Support

Works in all modern browsers and Node.js environments:

  • Chrome/Edge: ✅
  • Firefox: ✅
  • Safari: ✅
  • Node.js: ✅ (v14+)

Guidelines

See Code of Conduct, Contributing, and Security Policy.

License

MIT License © 2026 Zsolt Tövis


If you find this project useful, please consider sponsoring me by: Donate via GitHub / Donate via PayPal / Give the repo a Star / Follow me on GitHub / Share the project on X

Made with ❤️ for developers who love clean APIs and don't need a database for everything