SQL integration
MeshQL’s buildSelectSql() (from @meshql/postgres or @meshql/sqlite) turns a JoinPlan into parameterized SQL.
You supply the database connection — MeshQL does not manage pools or clients. See Database connections.
Basic usage
Section titled “Basic usage”import { createMesh, type MeshSchema } from "@meshql/core";import { buildSelectSql } from "@meshql/postgres";import { Pool } from "pg";
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const schema: MeshSchema = { entities: { user: { type: {} as User, fields: ["id", "name"], table: "users" }, token: { type: {} as Token, fields: ["accessToken"], table: "tokens", columns: { accessToken: "access_token" }, }, }, joins: { "user.tokens": { entity: "token", on: "tokens.user_id = users.id", type: "many", table: "tokens", }, },};
const mesh = createMesh(schema);
mesh.resolve("user", async (plan) => { const { sql, params } = buildSelectSql(plan, schema); const result = await pool.query(sql, params); return result.rows;});Generated SQL
Section titled “Generated SQL”For a query requesting user.id, user.name, and user.tokens.accessToken, you get something like:
SELECT users.id AS user_id, users.name AS user_name, tokens.access_token AS tokens_accessTokenFROM usersLEFT JOIN tokens ON tokens.user_id = users.idWHERE users.id = $1Column aliases match what the shaper expects (entity_field or join_field). Multi-hop joins (e.g. post.comments.author.name) use distinct table aliases per path.
When to skip buildSelectSql
Section titled “When to skip buildSelectSql”You don’t have to use the SQL builder. Any resolver that returns correctly aliased flat rows works:
mesh.resolve("user", async (plan) => { if (!plan.fields.includes("name")) { // client didn't ask for name — skip that column } return customDataSource.fetch(plan);});ORM adapters (Prisma, Drizzle) return pre-shaped nested JSON instead. Kysely uses buildSelectSql under the hood. See ORM adapters.
Catch-all resolver
Section titled “Catch-all resolver”One handler for every entity:
import { buildSelectSql } from "@meshql/sqlite";
mesh.resolve("*", async (plan) => { const { sql, params } = buildSelectSql(plan, schema); return db.prepare(sql).all(...params);});A specific mesh.resolve("user", fn) always wins over "*".
Examples
Section titled “Examples”- express-sqlite — Node built-in SQLite
- express-postgres — optional
DATABASE_URL