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
Package JSR Purpose
@meshql/core jsr.io/@meshql/core Parser, planner, shaper, createMesh()
@meshql/postgres jsr.io/@meshql/postgres Postgres buildSelectSql ($1, $2, … placeholders)
@meshql/sqlite jsr.io/@meshql/sqlite SQLite buildSelectSql for Node 22.5+ node:sqlite / Bun / D1
@meshql/http jsr.io/@meshql/http Express, Fastify, Hono adapters
@meshql/client jsr.io/@meshql/client Typed client SDK
@meshql/upload jsr.io/@meshql/upload File uploads (optional)
@meshql/integrity jsr.io/@meshql/integrity Request signing and integrity tokens
@meshql/access jsr.io/@meshql/access Entity, row, and field access control
@meshql/persisted-queries jsr.io/@meshql/persisted-queries Persisted query IDs, X-Mesh-Query-Id transport (v0.8.0)
@meshql/access-cache jsr.io/@meshql/access-cache Cache permission results per user (v0.8.0)
@meshql/prisma jsr.io/@meshql/prisma Prisma catch-all resolver (nested select)
@meshql/drizzle jsr.io/@meshql/drizzle Drizzle relational query resolver
@meshql/kysely jsr.io/@meshql/kysely Kysely + 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

Until the @meshql npm org is available, packages publish as unscoped meshql-* with compiled dist/. Requires "type": "module" (or .mjs).

Core stack:

Terminal window
npm install meshql-core meshql-http meshql-client

Full stack (uploads + security):

Terminal window
npm install meshql-core meshql-http meshql-client meshql-upload meshql-integrity meshql-access
Package npm Purpose
meshql-core npmjs.com/package/meshql-core Parser, planner, shaper, createMesh()
meshql-postgres npmjs.com/package/meshql-postgres Postgres buildSelectSql
meshql-sqlite npmjs.com/package/meshql-sqlite SQLite buildSelectSql for node:sqlite / Bun / D1
meshql-http npmjs.com/package/meshql-http Express, Fastify, Hono adapters
meshql-client npmjs.com/package/meshql-client Typed client SDK
meshql-upload npmjs.com/package/meshql-upload File uploads (optional)
meshql-integrity npmjs.com/package/meshql-integrity Request signing and integrity tokens
meshql-access npmjs.com/package/meshql-access Entity, row, and field access control
meshql-persisted-queries npmjs.com/package/meshql-persisted-queries Persisted query IDs, X-Mesh-Query-Id (v0.8.0)
meshql-access-cache npmjs.com/package/meshql-access-cache Cache permission results per user (v0.8.0)
meshql-prisma npmjs.com/package/meshql-prisma Prisma catch-all resolver
meshql-drizzle npmjs.com/package/meshql-drizzle Drizzle relational query resolver
meshql-kysely npmjs.com/package/meshql-kysely Kysely + SQL builder resolver

Imports use the npm package names:

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/meshql-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 "@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 "@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 "@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 });

DrizzleschemaFromDrizzle(tables) + withDrizzle(mesh, db, { schema }).

KyselywithKysely(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 "@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 "@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);
Package npm Purpose
@meshql/core meshql-core Parser, join planner, response shaper, createMesh()
@meshql/postgres meshql-postgres buildSelectSql for Postgres
@meshql/sqlite meshql-sqlite buildSelectSql for node:sqlite / Bun / D1
@meshql/http meshql-http Header transport + Express, Fastify, Hono adapters
@meshql/client meshql-client Typed client, sets query headers for you
@meshql/upload meshql-upload File uploads (optional)
@meshql/integrity meshql-integrity Signing token lifecycle and request integrity
@meshql/access meshql-access Entity, row, and dynamic field access
@meshql/prisma meshql-prisma Prisma catch-all resolver
@meshql/drizzle meshql-drizzle Drizzle relational query resolver
@meshql/kysely meshql-kysely Kysely + 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
npm install meshql-integrity meshql-access
# JSR
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:

Package Tests Coverage highlights
@meshql/core 158 JSON/QL parser, join planner, response shaper, spec conformance fixtures
@meshql/postgres 19 buildSelectSql, nested joins, cursor keyset
@meshql/sqlite 31 Same SQL builder contract as Postgres
Other packages 52+ 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.