# knex/knex: a multi-dialect SQL query builder for Node.js

> knex builds SQL for PostgreSQL, MySQL, MariaDB, CockroachDB, MSSQL, SQLite3 and Oracle from one JavaScript API. It is a query builder rather than an ORM, and the trade-offs show up in migrations and dialect-specific SQL.

**knex/knex** — A query builder for PostgreSQL, MySQL, CockroachDB, SQL Server, SQLite3 and Oracle, designed to be flexible, portable, and fun to use.

- Repository: https://github.com/knex/knex
- Website: https://knexjs.org/
- Stars: 20,345 · Forks: 2,224
- Language: JavaScript
- License: MIT
- Published: 2026-09-21 · Updated: 2026-09-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/knex-knex

## What knex/knex is for, and who it is not for

knex is a SQL query builder for Node.js. The README describes it as "a batteries-included, multi-dialect (PostgreSQL, MariaDB, MySQL, CockroachDB, MSSQL, SQLite3, Oracle (including Oracle Wallet Authentication)) query builder", and the package.json description names PostgresSQL, MySQL, CockroachDB, MSSQL and SQLite3. The pitch is that you write one JavaScript expression and knex emits the SQL your engine understands. It is aimed at backend engineers who are comfortable with SQL and want it generated rather than hand-concatenated, and who need the same application code to run against more than one engine. It is not aimed at people who want models, relations and lazy loading. The README says so directly: for a knex-based Object Relational Mapper it lists objection.js, mikro-orm and bookshelf. If you want an ORM, knex is the layer underneath one, not a replacement for one.

## How the builder, the pool and the dialect layer fit together

A knex instance is created by calling the exported function with a config object. That config carries a client name (the dialect) and a connection object, and the README's first example uses client: 'sqlite3' with connection: { filename: './data.db' }. From that instance you get three surfaces: a query builder you invoke as a function, knex('users'), a schema builder at knex.schema, and a migration runner at knex.migrate.

The builder is chainable and lazy. Calling knex('users').join('accounts', 'users.id', 'accounts.user_id').select(...) assembles an internal representation; the SQL string is produced when the query is awaited or executed. Because the dialect is chosen at instance creation, the same chain is compiled differently per client, which is where the portability claim comes from. The instance also owns a connection pool and a transaction API, and the README lists transactions, connection pooling, streaming queries and both a promise and a callback API as features. Those are properties of the instance, not of a single query, so how you create and share the instance determines your pooling behaviour.

The schema builder is separate from the query builder and is the part that most often leaks dialect differences. In the README's example, table.increments('id') is used for a primary key, and table.integer('user_id').unsigned().references('users.id') creates a foreign key. Auto-increment columns, unsigned integers and reference syntax are exactly the areas where engines diverge, and knex has to translate each one.

## Installing knex and running a first query

The README does not give an npm install line for consumers; it documents the local development setup for the repository itself, which begins with the prerequisites Node.js 16+ and Python 3.x with setuptools installed, then npm install. Consumers install the package from npm under the name knex, and the repository's package.json sets engines.node to ">=16".

The README's first complete example creates two tables, inserts into both, joins them and maps the result. It is written with await at the top level inside a try block, so it assumes an async context. The shape is worth reading closely because it shows the intended flow: schema first, then data, then a join.

```js
const knex = require('knex')({
  client: 'sqlite3',
  connection: {
    filename: './data.db',
  },
});

await knex.schema.createTable('users', (table) => {
  table.increments('id');
  table.string('user_name');
});
```

After the table exists, an insert into users returns the new id, and that id is used for the foreign key in accounts. The README's code does exactly this, taking insertedRows[0] as user_id. The join that follows selects 'users.user_name as user' and 'accounts.account_name as account'.

There is a TypeScript path as well. The README's TypeScript example declares a Knex.Config object with client: 'sqlite3', connection: { filename: './data.db' } and useNullAsDefault: true, then calls knex(config) and types the instance with a User interface. Note the useNullAsDefault key: it appears in the TypeScript example and not in the JavaScript one, and it is the kind of config detail you only find by reading the example rather than the prose. The README also notes that if you are not using TypeScript and want IDE IntelliSense, you can annotate the instance with a JSDoc @type {Knex} comment, and it shows database.migrate.latest() being called on that instance.

## Migrations, and where the abstraction stops paying off

knex ships a migration runner, and the README's IntelliSense example ends with database.migrate.latest(). That is the whole migration story the README tells: the runner exists and you call it. The UPGRADING.md file at the repository root is where the project points people upgrading from an older version, which is a signal that the migration runner and the builder API have both changed shape across major versions.

The real limitation is not the runner, it is the promise of portability. A query builder can normalise select, join, insert and where. It cannot normalise everything. Window functions, upserts, JSON operators, RETURNING clauses and auto-increment behaviour differ between PostgreSQL, MySQL, SQLite3, MSSQL, CockroachDB and Oracle, and knex exposes dialect-specific escape hatches for those cases. The moment you use one, your application is no longer portable, and the README does not pretend otherwise: it links a migration guide for upgrades and a wiki of recipes for "specific problems". If you only ever target one engine, a dialect-specific driver or a full ORM will often be less indirection than knex. The multi-dialect layer earns its keep when you genuinely run against more than one engine, or when you expect to.

A second constraint is the toolchain. The repository's prerequisites include Python 3.x with setuptools, and the README notes that Python 3.12+ removed the built-in distutils module, so a ModuleNotFoundError: No module named 'distutils' during npm install means you should run pip install setuptools. On Windows the README requires Visual Studio Build Tools with the "Desktop development with C++" workload. That is the cost of native dependencies such as better-sqlite3, and it lands on contributors and on anyone building the repository, not on ordinary consumers.

## knex compared with Prisma and with an ORM on top of knex

The obvious alternative is Prisma, which takes the opposite approach: you declare a schema in its own schema file, and it generates a typed client and manages migrations from that declaration. With knex you write the table definitions in JavaScript inside migration files and you write the queries yourself. Prisma gives you generated types and a migration workflow that is driven by a schema diff; knex gives you a builder whose output you can inspect, and the README points to Knex Query Lab as a way "to see the SQL that Knex will generate for a given query". If your team wants to read the SQL before it runs, that is the difference that matters.

The other alternative is not a competitor but a layer: objection.js, mikro-orm or bookshelf. These use knex as their query layer and add models, relations and hooks on top. Choosing one of them is not a rejection of knex; it is a decision about how much abstraction you want above it. The README frames the relationship that way, listing them as "knex-based Object Relational Mapper" options rather than as alternatives.

## Maintenance, licensing and upgrade cost

The repository is not archived. The most recent push recorded is 2026-06-26, and the latest release in the list is 3.3.0, published on 2026-06-26, following 3.2.10 on 2026-05-02 and 3.2.9 on 2026-04-03. That is a patch-heavy release cadence over the three releases listed, with the minor bump to 3.3.0 arriving at the same time as the latest push.

The licence is MIT, stated in the repository metadata and in the LICENSE file at the root. MIT is permissive, so it does not impose copyleft obligations on your application. That is a general property of the licence text, not advice about your situation; read the LICENSE file and your own organisation's rules if the distinction matters to you.

Upgrade cost is the part worth budgeting for. The presence of UPGRADING.md at the root, and the README's instruction to consult it "in case of upgrading from an older version", means major upgrades are not drop-in. For a project that other libraries depend on, an upgrade also has to be coordinated with objection.js, mikro-orm or bookshelf if you use one of them, because they sit on top of the knex API.

## Conclusion

Adopt knex when you want to write SQL-shaped queries in JavaScript across more than one database engine, and when you are willing to own the schema yourself. Do not adopt it expecting an ORM: the README points to objection.js, mikro-orm and bookshelf for that. Before committing, check the dialect you actually deploy against, because the README's Oracle support (including Oracle Wallet Authentication) and the package.json test script that excludes Oracle with DB="mssql mysql mysql2 mariadb postgres sqlite3" do not tell the same story about how evenly the engines are exercised.

## FAQ

### Which databases does knex support?

The README lists PostgreSQL, MariaDB, MySQL, CockroachDB, MSSQL, SQLite3 and Oracle, including Oracle Wallet Authentication. The package.json description names PostgresSQL, MySQL, CockroachDB, MSSQL and SQLite3.

### Is knex an ORM?

No. The README describes it as a query builder and lists objection.js, mikro-orm and bookshelf separately as knex-based Object Relational Mapper options.

### What Node.js version does knex require?

The README states that Node.js versions 16+ are supported, and package.json sets engines.node to ">=16".

### Does knex support transactions and connection pooling?

Yes. The README lists transactions, connection pooling, streaming queries and both a promise and a callback API among the features.

### How do I see the SQL that knex generates?

The README points to Knex Query Lab, described as a way to see the SQL that Knex will generate for a given query.

## Sources

- [knex/knex on GitHub](https://github.com/knex/knex)
- [License: MIT](https://github.com/knex/knex/blob/master/LICENSE)
- [Project website](https://knexjs.org/)
- [README](https://github.com/knex/knex/blob/master/README.md)
- [Releases](https://github.com/knex/knex/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/knex-knex
