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

usql

v1.0.5

Published

Tiny, zero-dependency SQL query generator

Downloads

46

Readme

µSQL

An easy-to-use super tiny flexible and 0-dependency SQL query builder with Knex compatible API! Works both in the browser and as a Node.js package.

Installation

yarn

yarn add usql

npm

npm install usql

Quick Start

import USql from "usql";

const sql = new USql("table").where({ column: "5", column2: "4" });

Then sql.toString() will produce:

SELECT * FROM `table` WHERE `column` = "5" AND `column2` = "4"

Security

Parameterised queries (recommended)

The safest way to execute a query is via .toSQL(), which returns a { sql, bindings } object. Pass sql and bindings directly to your database driver so it handles value escaping:

const { sql, bindings } = new USql("users").where("id", userId).toSQL();
// sql      → 'SELECT * FROM `users` WHERE `id` = ?'
// bindings → [userId]

await db.execute(sql, bindings); // driver binds values safely

.toString() is still available for logging and debugging, but it relies on inline string escaping. Prefer .toSQL() for any code that talks to a real database.

DB.raw() — use with care

DB.raw() inserts its argument verbatim into the generated SQL with no escaping or validation. Only pass hard-coded string literals or values you have already fully validated yourself:

// ✅ Safe — hard-coded fragment
DB.raw("COUNT(*) as total");

// ❌ UNSAFE — never pass user input
DB.raw(req.query.column);

Input validation

The following methods throw on invalid input rather than silently producing injectable SQL:

| Method | Throws | When | | --------------------- | ------------ | ---------------------------------------- | | where(col, op, val) | RangeError | op is not in the allowed operator set | | join(…, op, …) | RangeError | op is not in the allowed operator set | | orderBy(col, dir) | RangeError | dir is not 'ASC' or 'DESC' | | limit(n) | TypeError | n cannot be parsed as a finite integer | | offset(n) | TypeError | n cannot be parsed as a finite integer | | where(null, …) | TypeError | column argument is null or undefined |

Allowed comparison operators: =, !=, <>, <, >, <=, >=, LIKE, NOT LIKE, IN, NOT IN, IS, IS NOT.


API

Column selection

select — .select([...columns])

new USql("books").select("title", "author", "year");

Result:

SELECT `title`, `author`, `year` FROM `books`

select is optional — when omitted, * is used:

new USql("books");

Result:

SELECT * FROM `books`

Where Methods

where — .where(~mixed~)

Object syntax:

new USql("users")
  .where({
    first_name: "Test",
    last_name: "User",
  })
  .select("id");

Result:

SELECT `id` FROM `users` WHERE `first_name` = "Test" AND `last_name` = "User"

Key/value (defaults to =):

new USql("users").where("id", 1).where("info", null);

Result:

SELECT * FROM `users` WHERE `id` = "1" AND `info` IS NULL

Three-argument form (explicit operator):

new USql("users").where("age", ">=", 18);

Result:

SELECT * FROM `users` WHERE `age` >= "18"

Can be chained:

new USql("table")
  .where("id", 1)
  .whereNot("role", "admin")
  .orWhere({ created_at: Date.now() })
  .where({ is_deleted: 0 });

Result:

SELECT * FROM `table` WHERE `id` = "1" AND `role` != "admin" OR `created_at` = "1576417577608" AND `is_deleted` = "0"

whereNot — .whereNot(~mixed~)

Object syntax:

new USql("users")
  .whereNot({
    first_name: "Test",
    last_name: "User",
  })
  .select("id");

Result:

SELECT `id` FROM `users` WHERE `first_name` != "Test" AND `last_name` != "User"

Key/value:

new USql("users").whereNot("id", 1).whereNot("name", null);

Result:

SELECT * FROM `users` WHERE `id` != "1" AND `name` IS NOT NULL

orWhere — .orWhere(~mixed~)

Object syntax:

new USql("users")
  .orWhere({
    first_name: "Test",
    last_name: "User",
  })
  .select("id");

Result:

SELECT `id` FROM `users` WHERE `first_name` = "Test" OR `last_name` = "User"

Key/value:

new USql("users").orWhere("id", 1).orWhere("name", null);

Result:

SELECT * FROM `users` WHERE `id` = "1" OR `name` IS NULL

whereGroup — .whereGroup(callback)

orWhereGroup — .orWhereGroup(callback)

Group conditions in parentheses to control AND/OR precedence. The callback receives a fresh builder; call .where() / .orWhere() on it to populate the group.

new USql("users")
  .where("active", 1)
  .whereGroup((q) => q.where("role", "admin").orWhere("role", "moderator"));

Result:

SELECT * FROM `users` WHERE `active` = "1" AND (`role` = "admin" OR `role` = "moderator")
new USql("users")
  .where("is_deleted", 0)
  .orWhereGroup((q) => q.where("role", "superadmin").where("active", 1));

Result:

SELECT * FROM `users` WHERE `is_deleted` = "0" OR (`role` = "superadmin" AND `active` = "1")

Why does this matter? Without grouping, SQL evaluates AND before OR, so .where('a', 1).orWhere('b', 2).where('c', 3) produces a = 1 OR b = 2 AND c = 3, which is a = 1 OR (b = 2 AND c = 3) — almost never what you want in an authorization check. Use whereGroup / orWhereGroup to make precedence explicit.


Join method

join — .join(table, first, [operator], second)

new USql("table")
  .join("contacts", "users.id", "=", "contacts.user_id")
  .select("id");

Result:

SELECT `id` FROM `table` JOIN `contacts` ON `users`.`id` = `contacts`.`user_id`

You can omit the operator (defaults to =):

new USql("table").join("contacts", "users.id", "contacts.user_id").select("id");

Result:

SELECT `id` FROM `table` JOIN `contacts` ON `users`.`id` = `contacts`.`user_id`

Ordering

orderBy — .orderBy(column, [direction])

Direction defaults to ASC and is normalised to uppercase.

new USql("table").orderBy("table1.column1_value", "DESC");

Result:

SELECT * FROM `table1` ORDER BY `table1`.`column1_value` DESC

Multiple columns:

new USql("table")
  .orderBy("table1.column1_value", "DESC")
  .orderBy("table1.column2_value", "ASC");

Result:

SELECT * FROM `table1` ORDER BY `table1`.`column1_value` DESC, `table1`.`column2_value` ASC

Pagination

limit — .limit(value)

new USql("table").limit(2);

Result:

SELECT * FROM `table` LIMIT 2

offset — .offset(value)

Requires limit() to be set; ignored otherwise.

new USql("table").limit(2).offset(5);

Result:

SELECT * FROM `table` LIMIT 5, 2

Aliasing

as — .as(name)

Alias a sub-query. Ignored when the query is used at the top level.

new USql("table").select("column").as("subquery");

Result:

(SELECT `column` FROM `table`) as `subquery`

Full sub-query example:

const subquery = new USql("groups")
  .select("groups.name")
  .where("users.group_id", USql.raw("`groups`.`id`"))
  .as("group_name");

const sql = new USql("users").select("users.*", subquery);

Result:

SELECT `users`.*, (SELECT `groups`.`name` FROM `groups` WHERE `users`.`group_id` = `groups`.`id`) as `group_name` FROM `users`

Cloning

clone — .clone()

Returns a deep copy of the builder. Use this when you want to reuse a base query across multiple code paths without risk of shared-state mutation:

const base = new USql("users").where("active", 1);

const admins = base.clone().where("role", "admin").limit(10);
const mods = base.clone().where("role", "moderator");

// base is unchanged

Parameterised output

toSQL — .toSQL()

Returns { sql, bindings } with ? placeholders instead of inline values. Pass both to your database driver for safe parameterised execution.

const { sql, bindings } = new USql("users")
  .where("email", userEmail)
  .where("active", 1)
  .toSQL();

// sql      → 'SELECT * FROM `users` WHERE `email` = ? AND `active` = ?'
// bindings → [userEmail, 1]

await connection.execute(sql, bindings);

Raw queries

raw — USql.raw(statement)

Inserts a raw SQL fragment verbatim. See the Security section for important warnings before use.

new USql("users").select(USql.raw("count(*) as item_number"));

Result:

SELECT count(*) as item_number FROM `users`

raw is supported in select, where, join, and orderBy.