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.
Schema
Section titled “Schema”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.
Query formats
Section titled “Query formats”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.
JoinPlan
Section titled “JoinPlan”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.
Shaper
Section titled “Shaper”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" }]}createMesh
Section titled “createMesh”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.
Errors
Section titled “Errors”MeshQL returns structured JSON errors (ValidationError, TransportError, etc.). See the HTTP reference.