Quickstart
Get a minimal MeshQL server running in a few minutes — then test with curl.
For the full step-by-step guide (Express, Hono, Bun, curl examples), see Run example.
Install
Section titled “Install”MeshQL is published on JSR (@meshql/*) and npm (meshql-*).
npm:
npm install meshql-core meshql-http meshql-clientnpm i expressnpm i -D typescript tsx @types/node @types/expressJSR:
npx jsr add @meshql/core @meshql/http @meshql/clientnpm i expressnpm i -D typescript tsx @types/node @types/expressBun: bun add meshql-core meshql-http meshql-client express or bunx jsr add @meshql/core @meshql/http @meshql/client && bun add express
Deno: deno add jsr:@meshql/core jsr:@meshql/http jsr:@meshql/client
npm imports use package names (
meshql-core,meshql-http,meshql-client). JSR imports use@meshql/*. Examples below use JSR paths; swap names if you installed from npm.
Server
Section titled “Server”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", tokens_accessToken: "tok_abc" },]);
const app = express();app.use(express.json());app.use(meshExpressRouter(mesh, "/mesh"));app.listen(3001, () => console.log("http://localhost:3001/mesh"));Join keys are always {entityKey}.{ref} (here user.tokens). Nested relations, many-to-many, and polymorphic associations are covered in Relations.
Start: npx tsx index.ts
Test with curl
Section titled “Test with curl”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"Client (optional)
Section titled “Client (optional)”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);Persisted queries (production)
Section titled “Persisted queries (production)”For production apps, register queries once and send a ~10-byte X-Mesh-Query-Id header instead of a ~300-byte X-Mesh-Query payload.
Server — swap meshExpressRouter for the persisted-queries router:
import { meshPersistedQueriesExpressRouter } from "@meshql/persisted-queries/express";
app.use(meshPersistedQueriesExpressRouter(mesh, {}, "/mesh"));Register (once per query shape):
curl -s -X POST http://localhost:3001/mesh/queries \ -H "Content-Type: application/json" \ -d '{"query":"{\"user\":{\"$select\":{\"id\":true,\"name\":true,\"tokens\":{\"$select\":{\"accessToken\":true}}}}}","format":"json"}'# → { "id": "q_a3f1b2c8" }Execute with the ID:
curl -s "http://localhost:3001/mesh/user/1" \ -H "X-Mesh-Query-Id: q_a3f1b2c8" \ -H "X-Mesh-Format: json"Client — transparent mode (auto-registers on first use):
const client = createClient({ url: "http://localhost:3001/mesh", persistedQueries: true,});
const user = await client.query( { user: { $select: { id: true, name: true, tokens: { $select: { accessToken: true } } } } }, { entityId: "1" },);Details: persisted-queries package · HTTP wire spec
Next steps
Section titled “Next steps”- Run example — full 5-step guide with curl
- Concepts — schema, JoinPlan, shaper
- Integrations — Express, Fastify, Hono, Deno, Bun
- HTTP reference — routes, headers, errors
Monorepo example
Section titled “Monorepo example”The full express-postgres example supports in-memory and Postgres modes.