Skip to content

Introduction

MeshQL is a small TypeScript library for when you want GraphQL-style “give me these fields” queries, but you’d rather keep REST and write normal SQL.

Clients send a query (what fields they want, including nested stuff like user.tokens.accessToken). You get a JoinPlan with exactly those fields and joins. Write one query, return flat rows, MeshQL shapes the JSON. No resolver per field, no dataloader dance, no codegen eating your types.

Published on JSR under the @meshql scope.

Terminal window
# Node
npx jsr add @meshql/core @meshql/http @meshql/client
# Bun
bunx jsr add @meshql/core @meshql/http @meshql/client
# Deno
deno add jsr:@meshql/core jsr:@meshql/http jsr:@meshql/client
PackageJSRPurpose
@meshql/corejsr.io/@meshql/coreParser, planner, shaper, createMesh()
@meshql/postgresjsr.io/@meshql/postgresPostgres buildSelectSql ($1, $2, … placeholders)
@meshql/sqlitejsr.io/@meshql/sqliteSQLite buildSelectSql for Node 22.5+ node:sqlite / Bun / D1
@meshql/httpjsr.io/@meshql/httpExpress, Fastify, Hono adapters
@meshql/clientjsr.io/@meshql/clientTyped client SDK
@meshql/uploadjsr.io/@meshql/uploadFile uploads (optional)
@meshql/integrityjsr.io/@meshql/integrityRequest signing and integrity tokens
@meshql/accessjsr.io/@meshql/accessEntity, row, and field access control
@meshql/persisted-queriesjsr.io/@meshql/persisted-queriesPersisted query IDs, X-Mesh-Query-Id transport (v0.8.0)
@meshql/access-cachejsr.io/@meshql/access-cacheCache permission results per user (v0.8.0)
@meshql/prismajsr.io/@meshql/prismaPrisma catch-all resolver (nested select)
@meshql/drizzlejsr.io/@meshql/drizzleDrizzle relational query resolver
@meshql/kyselyjsr.io/@meshql/kyselyKysely + buildSelectSql flat-row resolver

Core stack (most apps — pick a DB adapter):

Terminal window
# SQLite (zero setup, built into Node 22.5+)
npx jsr add @meshql/core @meshql/sqlite @meshql/http @meshql/client
# Postgres
npx jsr add @meshql/core @meshql/postgres @meshql/http @meshql/client
# Prisma (catch-all ORM resolver)
npx jsr add @meshql/core @meshql/prisma @meshql/http @meshql/client

With security (signing + access):

Terminal window
npx jsr add @meshql/core @meshql/http @meshql/integrity @meshql/access

Packages publish on npm as @meshqljs/* (compiled dist/) and on JSR as @meshql/* (TypeScript source). Requires "type": "module" (or .mjs).

Core stack:

Terminal window
npm install @meshqljs/core @meshqljs/http @meshqljs/client

Full stack (uploads + security):

Terminal window
npm install @meshqljs/core @meshqljs/http @meshqljs/client @meshqljs/upload @meshqljs/integrity @meshqljs/access
PackagenpmPurpose
@meshqljs/corenpmjs.com/package/@meshqljs/coreParser, planner, shaper, createMesh()
@meshqljs/postgresnpmjs.com/package/@meshqljs/postgresPostgres buildSelectSql
@meshqljs/sqlitenpmjs.com/package/@meshqljs/sqliteSQLite buildSelectSql for node:sqlite / Bun / D1
@meshqljs/httpnpmjs.com/package/@meshqljs/httpExpress, Fastify, Hono adapters
@meshqljs/clientnpmjs.com/package/@meshqljs/clientTyped client SDK
@meshqljs/uploadnpmjs.com/package/@meshqljs/uploadFile uploads (optional)
@meshqljs/integritynpmjs.com/package/@meshqljs/integrityRequest signing and integrity tokens
@meshqljs/accessnpmjs.com/package/@meshqljs/accessEntity, row, and field access control
@meshqljs/persisted-queriesnpmjs.com/package/@meshqljs/persisted-queriesPersisted query IDs, X-Mesh-Query-Id (v0.8.0)
@meshqljs/access-cachenpmjs.com/package/@meshqljs/access-cacheCache permission results per user (v0.8.0)
@meshqljs/prismanpmjs.com/package/@meshqljs/prismaPrisma catch-all resolver
@meshqljs/drizzlenpmjs.com/package/@meshqljs/drizzleDrizzle relational query resolver
@meshqljs/kyselynpmjs.com/package/@meshqljs/kyselyKysely + SQL builder resolver

Imports use the npm package names:

import { createMesh } from "@meshqljs/core";
import { meshExpressRouter } from "@meshqljs/http/express";
import { createClient } from "@meshqljs/client";
import { integrityPlugin } from "@meshqljs/integrity";
import { accessPlugin } from "@meshqljs/access";
import { createMesh } from "@meshql/core";
import { meshExpressRouter } from "@meshql/http/express";
import { createClient } from "@meshql/client";
import { integrityPlugin } from "@meshql/integrity";
import { accessPlugin } from "@meshql/access";

Or install from a GitHub Release tarball (per-package tags like npm/core/v*):

Terminal window
npm install https://github.com/meshql/meshql/releases/download/npm/core/v0.1.4/meshqljs-core-0.1.4.tgz

See CONTRIBUTING.md for the release workflow (Changesets → per-package tags).

SQLite is first-class. @meshql/sqlite runs on Node 22.5+‘s built-in node:sqlite — zero native deps, zero Docker. Try the express-sqlite example. Postgres works via @meshql/postgres and the express-postgres example.


Full walkthrough with Express, Hono, and Bun: docs/run-example.md

Terminal window
mkdir my-meshql-app && cd my-meshql-app
npm init -y && npm pkg set type=module
npm i -D typescript tsx @types/node
npx jsr add @meshql/core @meshql/http
npm i express
mkdir src
import { createMesh, type MeshSchema } from "@meshqljs/core";
import { meshExpressRouter } from "@meshqljs/http/express";
import express from "express";
const schema: MeshSchema = {
entities: {
user: { fields: ["id", "name"], table: "users" },
token: {
fields: ["accessToken"],
table: "tokens",
columns: { accessToken: "access_token" },
},
},
joins: {
"user.tokens": {
entity: "token",
on: "tokens.user_id = users.id",
type: "many",
},
},
};
const mesh = createMesh(schema);
mesh.resolve("user", async () => [
{ user_id: 1, user_name: "Ada Lovelace", tokens_accessToken: "tok_ada" },
]);
const app = express();
app.use(express.json());
app.use(meshExpressRouter(mesh, "/mesh"));
app.listen(3001, () => console.log("http://localhost:3001/mesh"));
import { createMesh, type MeshSchema } from "@meshql/core";
import { meshExpressRouter } from "@meshql/http/express";
import express from "express";
const schema: MeshSchema = {
entities: {
user: { fields: ["id", "name"], table: "users" },
token: {
fields: ["accessToken"],
table: "tokens",
columns: { accessToken: "access_token" },
},
},
joins: {
"user.tokens": {
entity: "token",
on: "tokens.user_id = users.id",
type: "many",
},
},
};
const mesh = createMesh(schema);
mesh.resolve("user", async () => [
{ user_id: 1, user_name: "Ada Lovelace", tokens_accessToken: "tok_ada" },
]);
const app = express();
app.use(express.json());
app.use(meshExpressRouter(mesh, "/mesh"));
app.listen(3001, () => console.log("http://localhost:3001/mesh"));
Terminal window
npx tsx src/index.ts
Terminal window
Q=$(echo -n '{"user":{"$select":{"id":true,"name":true,"tokens":{"$select":{"accessToken":true}}}}}' | base64 | tr -d '\n')
curl -s "http://localhost:3001/mesh/user/1" \
-H "X-Mesh-Query: $Q" \
-H "X-Mesh-Format: json"
Terminal window
npx jsr add @meshql/client
import { createClient } from "@meshqljs/client";
const client = createClient({ url: "http://localhost:3001/mesh" });
const user = await client.query(
{
user: {
$select: {
id: true,
name: true,
tokens: { $select: { accessToken: true } },
},
},
},
{ entityId: "1" },
);
console.log(user);
import { createClient } from "@meshql/client";
const client = createClient({ url: "http://localhost:3001/mesh" });
const user = await client.query(
{
user: {
$select: {
id: true,
name: true,
tokens: { $select: { accessToken: true } },
},
},
},
{ entityId: "1" },
);
console.log(user);

Collection queries and catch-all resolvers

Section titled “Collection queries and catch-all resolvers”

Collection reads — use the same canonical query object as the wire payload:

const users = await client.query(
{
user: {
$select: { id: true, name: true },
$page: { first: 10 },
$orderBy: [{ field: "name", direction: "asc" }],
$where: { field: "role", op: "eq", value: "admin" },
},
},
);
console.log(users.items, users.pageInfo);

Catch-all resolver — one handler for every entity (the pattern ORM adapters use):

mesh.resolve("*", async (plan) => {
const { sql, params } = buildSelectSql(plan, schema);
return db.query(sql, params);
});

A specific mesh.resolve("user", fn) always wins over the "*" fallback.

ORM adapters (v0.6.0+) and schema inference (v0.7.0)

Section titled “ORM adapters (v0.6.0+) and schema inference (v0.7.0)”

Use your existing ORM client — MeshQL does not create database connections. See docs/database-connections.md.

Prisma (infer schema from schema.prisma):

import { PrismaClient } from "@prisma/client";
import { createMesh } from "@meshqljs/core";
import { schemaFromPrisma, withPrisma } from "@meshqljs/prisma";
const prisma = new PrismaClient();
const schema = await schemaFromPrisma("./prisma/schema.prisma");
const mesh = createMesh(schema);
withPrisma(mesh, prisma, { schema });
import { PrismaClient } from "@prisma/client";
import { createMesh } from "@meshql/core";
import { schemaFromPrisma, withPrisma } from "@meshql/prisma";
const prisma = new PrismaClient();
const schema = await schemaFromPrisma("./prisma/schema.prisma");
const mesh = createMesh(schema);
withPrisma(mesh, prisma, { schema });

Drizzle — schemaFromDrizzle(tables) + withDrizzle(mesh, db, { schema }).

Kysely — withKysely(mesh, db, { schema, dialect: "postgres" }) runs buildSelectSql via executeQuery.

Full guide: docs/orm-adapters.md. Runnable demo: express-prisma.


Interactive full-stack blog (React + @meshql/client) on SQLite — no Docker:

Terminal window
git clone https://github.com/meshql/meshql.git
cd meshql
pnpm install && pnpm build
pnpm --filter showcase start

Open http://localhost:3010/ — the browser app uses @meshql/client against /mesh/* for login, reads, writes, and uploads. Check DevTools → Network to see signed MeshQL requests.

Optional CLI tour: pnpm --filter showcase demo

See examples/showcase/README.md. Examples: express-sqlite, express-postgres, express-prisma.


Pick the adapter that matches your database. Both expose the same API.

SQLite (Node 22.5+ built-in, no Docker, no native deps):

import { DatabaseSync } from "node:sqlite";
import { createMesh } from "@meshqljs/core";
import { buildSelectSql } from "@meshqljs/sqlite";
import { meshExpressRouter } from "@meshqljs/http/express";
import express from "express";
const db = new DatabaseSync(":memory:");
const mesh = createMesh(schema);
mesh.resolve("user", async (plan) => {
const { sql, params } = buildSelectSql(plan, schema);
return db.prepare(sql).all(...params);
});
express()
.use(express.json())
.use(meshExpressRouter(mesh, "/mesh"))
.listen(3001);
import { DatabaseSync } from "node:sqlite";
import { createMesh } from "@meshql/core";
import { buildSelectSql } from "@meshql/sqlite";
import { meshExpressRouter } from "@meshql/http/express";
import express from "express";
const db = new DatabaseSync(":memory:");
const mesh = createMesh(schema);
mesh.resolve("user", async (plan) => {
const { sql, params } = buildSelectSql(plan, schema);
return db.prepare(sql).all(...params);
});
express()
.use(express.json())
.use(meshExpressRouter(mesh, "/mesh"))
.listen(3001);

Postgres (via pg):

import { createMesh } from "@meshqljs/core";
import { buildSelectSql } from "@meshqljs/postgres";
import { meshExpressRouter } from "@meshqljs/http/express";
import express from "express";
import { Pool } from "pg";
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const mesh = createMesh(schema);
mesh.resolve("user", async (plan) => {
const { sql, params } = buildSelectSql(plan, schema);
return (await pool.query(sql, params)).rows;
});
express()
.use(express.json())
.use(meshExpressRouter(mesh, "/mesh"))
.listen(3001);
import { createMesh } from "@meshql/core";
import { buildSelectSql } from "@meshql/postgres";
import { meshExpressRouter } from "@meshql/http/express";
import express from "express";
import { Pool } from "pg";
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const mesh = createMesh(schema);
mesh.resolve("user", async (plan) => {
const { sql, params } = buildSelectSql(plan, schema);
return (await pool.query(sql, params)).rows;
});
express()
.use(express.json())
.use(meshExpressRouter(mesh, "/mesh"))
.listen(3001);
PackagenpmPurpose
@meshql/core@meshqljs/coreParser, join planner, response shaper, createMesh()
@meshql/postgres@meshqljs/postgresbuildSelectSql for Postgres
@meshql/sqlite@meshqljs/sqlitebuildSelectSql for node:sqlite / Bun / D1
@meshql/http@meshqljs/httpHeader transport + Express, Fastify, Hono adapters
@meshql/client@meshqljs/clientTyped client, sets query headers for you
@meshql/upload@meshqljs/uploadFile uploads (optional)
@meshql/integrity@meshqljs/integritySigning token lifecycle and request integrity
@meshql/access@meshqljs/accessEntity, row, and dynamic field access
@meshql/prisma@meshqljs/prismaPrisma catch-all resolver
@meshql/drizzle@meshqljs/drizzleDrizzle relational query resolver
@meshql/kysely@meshqljs/kyselyKysely + SQL builder resolver

HTTP adapter docs (routes, headers, curl): docs/http-adapters.md

ORM adapters: docs/orm-adapters.md · DB connections: docs/database-connections.md

Protocol specs (for language ports): specs/ · docs.meshql.dev/specs

Client SDK (browser, auth, uploads): docs/client.md

Built-in limits (depth, complexity, rate) live in @meshql/core/builtins. Custom plugins use MeshPlugin from @meshql/core and mesh.use(). For signed requests and access control, add the dedicated packages:

Terminal window
npm install @meshqljs/integrity @meshqljs/access
Terminal window
npx jsr add @meshql/integrity @meshql/access

Runnable demo: samples/npm-access in the meshql_stack repo.

JSR: integrity and access require one-time package setup on jsr.io before first publish. See CONTRIBUTING.md.

Node 22+, pnpm 11. Monorepo uses Turborepo.

packages/core engine (DB-agnostic)
packages/postgres buildSelectSql for Postgres
packages/sqlite buildSelectSql for node:sqlite / Bun / D1
packages/http adapters
packages/client SDK
packages/upload uploads
packages/integrity signing tokens
packages/access access control
packages/prisma Prisma adapter
packages/drizzle Drizzle adapter
packages/kysely Kysely adapter
examples/ runnable demos (express-sqlite, express-postgres, express-prisma)

260+ unit tests across the monorepo (pnpm test). The engine is the focus:

PackageTestsCoverage highlights
@meshql/core158JSON/QL parser, join planner, response shaper, spec conformance fixtures
@meshql/postgres19buildSelectSql, nested joins, cursor keyset
@meshql/sqlite31Same SQL builder contract as Postgres
Other packages52+HTTP transport, ORM adapters, integrity, access, persisted-queries, …

Core tests exercise the full parse → plan → shape pipeline, including golden queries from specs/fixtures/ so alternative implementations can match the published protocol.

Terminal window
pnpm test # all package unit tests (CI)
pnpm test:integration # Postgres integration (requires Docker)
pnpm --filter @meshql/core test # engine only

PRs welcome. See CONTRIBUTING.md.

Evaluating MeshQL? See the FAQ for GraphQL comparison, security, ORMs, and migration questions.

MIT. Security issues: SECURITY.md.