LakeQL
Introduction
  • Overview
  • Why LakeQL
  • Key Concepts
Getting Started
  • Prerequisites
  • Quickstart
  • Environment
  • First Run
Architecture
  • System Overview
  • Data Flow
  • Request Lifecycle
Configuration
  • Config File
  • Runtime Lifecycle
  • Environment Variables
create-app
  • Usage
  • Template Structure
  • Post-Creation
Guides
  • Custom Resolvers
  • Extending the Schema
  • Mutations
  • Set Up a Trino Mutation
  • Deploying
Endpoints
  • Definition Format
  • Field Types
  • Nested Fields
  • Mutations
  • Glossary
Contributing
  • Local Development
  • Contribution Guide
  • Architecture
GitHub
LakeQL
  1. LakeQL
  2. Introduction
  3. Why LakeQL

On this page

  1. What LakeQL Adds Across Adapters
  2. LakeQL and the Native Fabric API for GraphQL
    1. Consolidating Several Fabric Sources
  3. LakeQL and Direct Trino Access

Why LakeQL

When a managed GraphQL service is enough, and what LakeQL adds across data engines.

Many platforms ship a GraphQL layer of their own. When that layer covers a single source and the consumers of the API own their own client typing, the built-in option is often enough. LakeQL earns its place when a project spans several sources, needs a typed client workflow, or needs an authorization layer that it owns.

This page frames that trade-off honestly. It starts with the value that holds across every adapter, then narrows to a per-adapter comparison.

What LakeQL Adds Across Adapters #

LakeQL is a universal data access layer. The same four properties hold whether a data source points at Trino or Microsoft Fabric.

  • One unified schema across sources. A project registers several named dataSources in lakeql.config.ts and serves them through one GraphQL endpoint. The sources can mix engines, so a Trino source and a Fabric source live in the same schema. A consumer configures one endpoint rather than one per source.
  • A typed codegen workflow. The CLI introspects each source and writes typed artifacts into the repository: TypeScript interfaces, GraphQL query schemas, and the configuration that registers each endpoint. The typed layer is committed and regenerated when a table changes, so client typing comes from the same pull that builds the schema.
  • A self-hosted application. createLakeQLApp composes a server from explicit building blocks that deploy anywhere a Node.js service runs. The runtime, not a hosted control plane, owns the data-source connections.
  • An authorization layer in front of the engine. LakeQL checks per-table read and write permissions before a resolver runs, then resolves the identity the query runs under. See the authorization model and permissions .

LakeQL and the Native Fabric API for GraphQL #

Microsoft Fabric includes an API for GraphQL that auto-generates a schema from attached sources. It is a capable managed service, and for a single Fabric source consumed by clients that supply their own typing, it covers the common case. This comparison is about scope, not quality. Details on the native service come from Microsoft Learn.

CapabilityLakeQLNative Fabric API for GraphQL
Sources behind one endpointSeveral dataSources, mixing enginesOne GraphQL item per set of attached sources; Microsoft recommends multiple items for multiple sources
Supported enginesTrino and Fabric today, adapter-agnosticFabric Data Warehouses, Lakehouses, Mirrored Databases, Fabric SQL Databases, and Azure SQL Databases
Client typingTyped artifacts generated by the CLI and committedServer-side schema; client typing left to the consumer
HostingSelf-hosted application deployed anywhereManaged service inside Fabric
AuthenticationPer-user pass-through identity and service-principal authPer-user pass-through identity and service-principal auth
WritesMutations where the adapter and source support themQueries and mutations, with read-only sources behind SQL analytics endpoints
AuthorizationLakeQL permission rules compose with the engine's own access controlThe source's own access control

A few rows deserve context so the table reads fairly.

Authentication is equivalent. Both sides support a caller that is a user principal or a service principal, and both can pass the caller's identity through to the data source. The native service uses Microsoft Entra ID with single sign-on or saved credentials; LakeQL resolves identity through its impersonation model, and a Fabric source carries the on-behalf-of flow on its connection. Neither side holds an advantage here.

Writes depend on the source on both sides. The native service supports queries and mutations, but sources exposed through SQL analytics endpoints, such as Lakehouses and Mirrored Databases, are read-only, and auto-generated mutations on a Warehouse need a primary key. LakeQL supports mutations where the adapter and source support them. Confirm the write path for the source in question before relying on it.

Resolvers. The native service does not expose customizable resolvers; custom logic is handled through stored procedures. LakeQL composes its schema from generated endpoints and supports custom resolvers in the application.

The native service also documents operational limits a reader should size against: a default page of 100 rows up to 100,000 paginated, a 64 MB response ceiling, a 100-second request timeout, a maximum query depth of 10, and up to 1,000 attached objects per GraphQL item. The 1,000-object-per-item limit is the reason Microsoft's own guidance points to multiple GraphQL items for multiple sources, which is the point the first table row compares against.

Consolidating Several Fabric Sources #

The clearest Fabric case for LakeQL is a project that spans several Fabric workspaces. Each workspace, warehouse, or lakehouse becomes a separate Fabric data source with its own endpoint and credentials, and the sources merge into one LakeQL schema. A consumer then configures one endpoint instead of one per source.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42// lakeql.config.ts
import { defineConfig } from "@lakeql/config"
import { fabricAdapterDefinition } from "@lakeql/adapter-fabric"

import { env } from "./src/env"

export default defineConfig({
  adapters: [fabricAdapterDefinition],
  dataSources: {
    salesReporting: {
      adapter: "fabric",
      namePrefix: "sales",
      connection: {
        host: env.FABRIC_ENDPOINT_WS1,
        port: 1433,
        catalog: "sales_warehouse",
        auth: {
          type: "oauth2",
          tenantId: "...",
          clientId: "...",
          clientSecret: "...",
        },
      },
    },
    financeReporting: {
      adapter: "fabric",
      namePrefix: "finance",
      connection: {
        host: env.FABRIC_ENDPOINT_WS2,
        port: 1433,
        catalog: "finance_warehouse",
        auth: {
          type: "oauth2",
          tenantId: "...",
          clientId: "...",
          clientSecret: "...",
        },
      },
    },
  },
})

Each source carries its own host, catalog, and auth, driven by separate environment variables, so the two workspaces stay fully independent. Generated files are organized by data source, so tables never collide on disk. Two sources that expose a table with the same schema.tableName would otherwise produce colliding GraphQL names, so set a namePrefix on each source to disambiguate them. The prefix is the mechanism that keeps a merged, multi-workspace schema unambiguous.

LakeQL and Direct Trino Access #

Trino has no GraphQL layer of its own, so the comparison here is different from the Fabric one. The alternative to LakeQL is reaching the Trino coordinator directly: through an ODBC or JDBC driver, or by calling the Trino REST API from application code. LakeQL stands in for the work that path requires, and the following points describe that work rather than a shortcoming in Trino.

CapabilityLakeQLDirect Trino access
InterfaceGraphQL over HTTPSQL over the REST API, or an ODBC/JDBC driver
Driver dependencyNone; the adapter is an HTTP client over the REST APIA driver to install and maintain, itself an HTTP client over the same REST API
Result handlingPagination and column decoding handled by the clientCaller follows nextUri pages and decodes column-oriented rows
Query authoringSQL generated from the GraphQL queryCaller writes and maintains SQL
AuthenticationRequest-scoped identity resolved by LakeQL in front of the connectionDriver or REST credentials supplied per connection

Three points carry most of the difference.

No driver to maintain. The Trino adapter reaches the coordinator through an HTTP client over the Trino REST API, so a project adds no ODBC or JDBC driver. The honest nuance: those drivers are themselves HTTP clients over the same REST API, so the saving is one fewer dependency to install and track, not a protocol-level gain.

No hand-built result handling. A Trino query returns results across several pages linked by a nextUri, with rows delivered as positional arrays alongside separate column metadata. The client follows that pagination to completion and maps each positional row onto its column names, so application code receives keyed records. A direct REST consumer writes that pagination loop and column decoding.

SQL comes from the GraphQL query. The Trino endpoint accepts SQL and nothing else, so a direct consumer authors and maintains the SQL for every query. LakeQL compiles the SQL from the incoming GraphQL query, so the GraphQL schema is the interface a consumer works against.

Authentication differs from the Fabric case and the table reflects that. Direct Trino access authenticates with the credentials the driver or REST call carries. LakeQL resolves a request-scoped identity in front of the connection and can impersonate the caller against Trino. See Trino user impersonation for how that identity reaches the coordinator.

See Getting Started to scaffold a project and the data-source overview for adapter details.

Previous page

Overview

Next page

Key Concepts

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42