Project Layout #
After scaffolding, your project has this structure:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
my-lakeql-project/
├── src/
│ ├── index.ts # Entry point — starts the API server
│ ├── config.ts # defineConfig with runtime options
│ ├── auth.ts # Custom getUser resolver
│ ├── permissions.ts # Permission rules for technical users
│ ├── env.ts # Environment variable validation (t3-env + Zod)
│ ├── config-registry.ts # Auto-generated config index (empty initially)
│ └── schemas/ # Generated schemas go here after pull
│ └── generated/
├── .env.example # Template environment variables
├── .nvmrc # Node.js major version used by the template
├── package.json # Dependencies and scripts
├── tsconfig.json # TypeScript configuration
├── tsdown.config.ts # Build configuration
└── lakeql.config.json # LakeQL CLI configuration
File Details #
src/index.ts #
The entry point that starts the API server using the defined configuration:
1
2
3
4
import { config } from "./config"
await config.startServer()
src/config.ts #
Configures the API server with defineConfig. This is where you wire together auth, permissions, and schema loading:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { defineConfig } from "@lakeql/api/config"
import { getUser } from "./auth"
import { allConfigs } from "./config-registry"
import { permissions } from "./permissions"
const baseDir = import.meta.dirname
export const config = defineConfig({
allConfigs,
baseDir,
getUser,
graphqlPath: "/graphql",
healthCheckEndpoint: "/live",
permissions,
port: 4000,
schemaPath: "./schemas",
})
The schemaPath is resolved relative to baseDir (the src/ directory). Generated schemas placed in src/schemas/ are loaded automatically at startup.
src/auth.ts #
A starter authentication resolver. By default, it supports mock auth for local development:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import type { GetUserResolver } from "@lakeql/api/types"
import { env } from "./env"
export const getUser: GetUserResolver = async (request) => {
const authHeader = request.headers.get("authorization")
if (authHeader) {
if (env.AUTH_MOCK && authHeader === env.AUTH_MOCK_TOKEN) {
return { userName: "testuser" }
}
return null
}
return null
}
Replace this with real JWT verification (e.g. using jose) for production.
src/permissions.ts #
Defines per-user permission rules for technical users. Initially empty — add rules as needed:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { createPermission as createPermissionFromApi } from "@lakeql/api/helpers"
import { allConfigs } from "./config-registry"
const createPermission = createPermissionFromApi(allConfigs)
export const permissions = [
// {
// name: "testuser",
// useSystemUser: false,
// permissions: {
// Query: [createPermission("hive", "schema_name", ["table_name"])],
// Mutation: [],
// },
// },
]
src/env.ts #
Validates environment variables at startup using @t3-oss/env-core with Zod schemas. If a required variable is missing or invalid, the process exits with a clear error message:
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
26import { createEnv } from "@t3-oss/env-core"
import { coerce, number, string, enum as zodEnum } from "zod"
export const env = createEnv({
runtimeEnv: process.env,
server: {
API_LOGGER: zodEnum(["debug", "info", "warn", "error", "silent"]).default(
"warn"
),
API_PORT: coerce.number().default(4000),
AUTH_MOCK: coerce.boolean().default(false),
AUTH_MOCK_TOKEN: string().optional(),
HIVE_CATALOG: string().min(1),
HIVE_HOST: string(),
HIVE_PASSWORD: string().min(1),
HIVE_PORT: string()
.transform((s) => Number.parseInt(s, 10))
.pipe(number()),
HIVE_SOURCE: string().optional(),
HIVE_USERNAME: string().min(1),
NODE_ENV: zodEnum(["development", "production", "test"]).default(
"development"
),
},
})
src/config-registry.ts #
Auto-generated by lakeql-cli create-registry (or automatically after pull). Initially empty — populated once you pull schemas:
1
2
3
4
5
6
export const allConfigs = [] as const
export type AvailableCatalogs = (typeof allConfigs)[number]["catalog"]
export type AvailableSchemas = (typeof allConfigs)[number]["schema"]
export type AvailableTables = (typeof allConfigs)[number]["tableName"]
After pulling schemas, it looks like:
1
2
3
4
import { ordersConfig } from "./schemas/generated/hive/sales/orders/config"
export const allConfigs = [ordersConfig] as const
.env.example #
Template for required environment variables:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22######
# Hive Database
######
HIVE_HOST="http://localhost"
HIVE_PORT=8080
HIVE_CATALOG=hive
HIVE_USERNAME=admin
HIVE_PASSWORD="secret123"
HIVE_SOURCE="lakeql"
######
# Mock
######
AUTH_MOCK=true
AUTH_MOCK_TOKEN="1234"
######
# GraphQL API
######
API_PORT=4000
API_LOGGER="warn"
package.json #
Pre-configured with LakeQL packages and development tooling:
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{
"name": "lakeql-app",
"type": "module",
"scripts": {
"build": "tsdown",
"dev": "pnpm with-env tsx ./src/index.ts",
"preview": "pnpm with-env node ./dist/index.mjs",
"start": "node ./dist/index.mjs",
"typecheck": "tsc --noEmit",
"with-env": "dotenv -e ./.env --",
"cli": "pnpm with-env lakeql-cli"
},
"dependencies": {
"@lakeql/api": "^latest",
"@lakeql/query-builder": "^latest",
"@lakeql/trino-client": "^latest",
"@t3-oss/env-core": "^latest",
"zod": "^latest"
},
"devDependencies": {
"@lakeql/cli": "^latest",
"dotenv-cli": "^latest",
"tsdown": "^latest",
"tsx": "^latest",
"typescript": "^latest"
},
"engines": {
"node": ">=24"
}
}
Key scripts:
| Script | Command | Purpose |
|---|---|---|
dev | pnpm with-env tsx ./src/index.ts | Development with env vars loaded |
build | tsdown | Production build |
start | node ./dist/index.mjs | Run production build |
preview | pnpm with-env node ./dist/index.mjs | Run production build with .env |
cli | pnpm with-env lakeql-cli | LakeQL CLI with env vars loaded |
lakeql.config.json #
Configures the CLI's code generation output path:
1
2
3
4
{
"sourcePath": "src"
}
This tells the CLI to write generated files relative to src/, resulting in paths like src/schemas/generated/{catalog}/{schema}/{table}/.