Skip to content

Concepts

MeshQL connects client queries to SQL-friendly resolvers and nested JSON responses.

New to MeshQL? Start with Thinking in MeshQL for the mental model. Coming from GraphQL? See From GraphQL.

Your schema defines entities, fields, and joins:

const schema = {
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",
},
},
};
  • entities — root types clients can query
  • fields — allowed scalar fields per entity
  • joins — how entities relate (used to build JoinPlan joins). Keys are always {entityKey}.{ref} — see Relations for one, many, many-to-many, nested, and polymorphic examples.
  • table / columns — optional SQL mapping for buildSelectSql()
  • computed — virtual fields derived from physical fields after fetching

Computed fields are selected like scalars but are not database columns. MeshQL expands their physical dependencies into the plan and hides unrequested dependencies from the response. See Computed fields.

JSON is the canonical and default format on every public surface (@meshql/client, GET/PUT headers, POST body, and mesh.execute()):

{ "user": { "$select": { "id": true, "name": true, "tokens": { "$select": { "accessToken": true } } } } }

QL brace syntax is an explicit, selection-only shorthand. It cannot carry $where, $orderBy, $page, or aggregates — use JSON for those. Opt in with format: "ql" / X-Mesh-Format: ql:

{ user { id name tokens { accessToken } } }

Clients send queries in X-Mesh-Query (base64-encoded). See HTTP adapters.

When a request arrives, MeshQL parses the query and builds a JoinPlan:

mesh.resolve("user", async (plan) => {
plan.fields; // only fields the client asked for
plan.joins; // only joins the client requested
plan.context.requestId; // correlation id for your logger
// ...
});

Use plan to fetch only what was requested — no over-fetching.

You bring the database client. MeshQL does not create or pool connections. Register a resolver that closes over your PrismaClient, pg.Pool, Drizzle db, or SQLite handle. See Database connections and ORM adapters.

Language ports and alternate implementations: see the protocol specs.

Resolvers return flat rows with aliased columns:

{ user_id: 1, user_name: "Ada", tokens_accessToken: "tok_1" }

MeshQL’s shaper converts these into nested JSON:

{
"id": 1,
"name": "Ada",
"tokens": [{ "accessToken": "tok_1" }]
}
import { createMesh } from "@meshql/core";
const mesh = createMesh(schema);
mesh.resolve("user", async (plan) => { /* ... */ });
await mesh.execute(query, { format: "json" });

Attach HTTP adapters from @meshql/http or call mesh.execute() directly in tests.

MeshQL returns structured JSON errors (ValidationError, TransportError, etc.). See the HTTP reference.