| name | server-setup |
| description | Set up drizzle-cube API server with Express, Fastify, Hono, or Next.js framework adapters. Use when configuring the semantic layer server, setting up API endpoints, extracting security context, or initializing drizzle-cube with different web frameworks. |
Drizzle Cube Server Setup
This skill helps you set up a Drizzle Cube API server using framework adapters for Express, Fastify, Hono, or Next.js. These adapters provide Cube.js-compatible API endpoints for your semantic layer.
Core Concept
Drizzle Cube provides framework adapters that:
- Expose Cube.js-compatible REST API endpoints
- Handle security context extraction from requests
- Integrate with your existing web framework
- Support
/load, /sql, and /meta endpoints
- Create the semantic layer compiler internally from cubes array
Express Adapter
Installation
npm install express drizzle-cube
Basic Setup
import express from 'express'
import { createCubeRouter } from 'drizzle-cube/adapters/express'
import { drizzle } from 'drizzle-orm/postgres-js'
import postgres from 'postgres'
import * as schema from './schema'
import { employeesCube, departmentsCube } from './cubes'
const queryClient = postgres(process.env.DATABASE_URL!)
const db = drizzle(queryClient, { schema })
const app = express()
app.use(express.json())
const cubeRouter = createCubeRouter({
cubes: [employeesCube, departmentsCube],
drizzle: db,
schema: schema,
extractSecurityContext: async (req, res) => {
return {
organisationId: req.user?.organisationId || 'default-org',
userId: req.user?.id
}
}
})
app.use('/cubejs-api/v1', cubeRouter)
app.listen(3000, () => {
console.log('Drizzle Cube API listening on port 3000')
})
With Authentication Middleware
import express from 'express'
import { createCubeRouter } from 'drizzle-cube/adapters/express'
import { authenticateJWT } from './auth'
import { drizzle } from 'drizzle-orm/postgres-js'
import postgres from 'postgres'
import * as schema from './schema'
import { employeesCube, departmentsCube } from './cubes'
const queryClient = postgres(process.env.DATABASE_URL!)
const db = drizzle(queryClient, { schema })
const app = express()
app.use(express.json())
app.use('/cubejs-api', authenticateJWT)
const cubeRouter = createCubeRouter({
cubes: [employeesCube, departmentsCube],
drizzle: db,
schema: schema,
extractSecurityContext: async (req, res) => {
if (!req.user) {
throw new Error()
}
{
: req..,
: req..,
: req..
}
},
: ,
: { : }
})
app.(, cubeRouter)
app.()
Express Adapter Options
interface ExpressAdapterOptions {
cubes: Cube[]
drizzle: DrizzleDatabase
schema?: any
extractSecurityContext: (req, res) => SecurityContext | Promise<SecurityContext>
engineType?: 'postgres' | 'mysql' | 'sqlite'
cors?: CorsOptions
basePath?: string
jsonLimit?: string
}
Fastify Adapter
Installation
npm install fastify drizzle-cube
Basic Setup
import Fastify from 'fastify'
import { registerCubeRoutes } from 'drizzle-cube/adapters/fastify'
import { drizzle } from 'drizzle-orm/postgres-js'
import postgres from 'postgres'
import * as schema from './schema'
import { employeesCube, departmentsCube } from './cubes'
const queryClient = postgres(process.env.DATABASE_URL!)
const db = drizzle(queryClient, { schema })
const fastify = Fastify({
logger: true
})
await registerCubeRoutes(fastify, {
cubes: [employeesCube, departmentsCube],
drizzle: db,
schema: schema,
extractSecurityContext: async (request) => {
return {
organisationId: request.user?.organisationId || 'default-org',
userId: request.user?.id
}
}
})
fastify.({ : })
With JWT Authentication
import Fastify from 'fastify'
import fastifyJWT from '@fastify/jwt'
import { registerCubeRoutes } from 'drizzle-cube/adapters/fastify'
import { drizzle } from 'drizzle-orm/postgres-js'
import postgres from 'postgres'
import * as schema from './schema'
import { employeesCube, departmentsCube } from './cubes'
const queryClient = postgres(process.env.DATABASE_URL!)
const db = drizzle(queryClient, { schema })
const fastify = Fastify({ logger: true })
await fastify.register(fastifyJWT, {
secret: process.env.JWT_SECRET!
})
fastify.decorate('authenticate', async (request, reply) => {
try {
await request.jwtVerify()
} catch (err) {
reply.send(err)
}
})
(fastify, {
: [employeesCube, departmentsCube],
: db,
: schema,
: (request) => {
(!request.) {
()
}
{
: request..,
: request..,
: request..
}
},
:
})
fastify.(, fastify.)
fastify.({ : })
Hono Adapter
Installation
npm install hono drizzle-cube
Basic Setup
import { Hono } from 'hono'
import { createCubeApp } from 'drizzle-cube/adapters/hono'
import { drizzle } from 'drizzle-orm/postgres-js'
import postgres from 'postgres'
import * as schema from './schema'
import { employeesCube, departmentsCube } from './cubes'
const queryClient = postgres(process.env.DATABASE_URL!)
const db = drizzle(queryClient, { schema })
const app = new Hono()
const cubeApp = createCubeApp({
cubes: [employeesCube, departmentsCube],
drizzle: db,
schema: schema,
extractSecurityContext: async (c) => {
const authHeader = c.req.header('Authorization')
const token = authHeader?.replace('Bearer ', '')
const user = (token)
{
: user.,
: user.
}
}
})
app.(, cubeApp)
app
With JWT Middleware
import { Hono } from 'hono'
import { jwt } from 'hono/jwt'
import { createCubeApp } from 'drizzle-cube/adapters/hono'
import { drizzle } from 'drizzle-orm/postgres-js'
import postgres from 'postgres'
import * as schema from './schema'
import { employeesCube, departmentsCube } from './cubes'
const queryClient = postgres(process.env.DATABASE_URL!)
const db = drizzle(queryClient, { schema })
const app = new Hono()
app.use('/cubejs-api/*', jwt({
secret: process.env.JWT_SECRET!
}))
const cubeApp = createCubeApp({
cubes: [employeesCube, departmentsCube],
drizzle: db,
schema: schema,
extractSecurityContext: async (c) => {
const payload = c.get('jwtPayload')
{
: payload.,
: payload.,
: payload.
}
}
})
app.(, cubeApp)
app
Edge Runtime (Cloudflare Workers)
import { Hono } from 'hono'
import { createCubeApp } from 'drizzle-cube/adapters/hono'
import { drizzle } from 'drizzle-orm/d1'
import * as schema from './schema'
import { employeesCube, departmentsCube } from './cubes'
const app = new Hono<{ Bindings: { DB: D1Database } }>()
const cubeApp = createCubeApp({
cubes: [employeesCube, departmentsCube],
drizzle: drizzle(c.env.DB),
schema: schema,
extractSecurityContext: async (c) => {
const authHeader = c.req.header('Authorization')
const user = await verifyEdgeToken(authHeader)
return {
organisationId: user.orgId,
userId: user.sub
}
}
})
app.route('/cubejs-api/v1', cubeApp)
export default app
Next.js Adapter
Installation
npm install next drizzle-cube
API Route Setup (App Router)
import { NextRequest } from 'next/server'
import { createCubeHandlers } from 'drizzle-cube/adapters/nextjs'
import { drizzle } from 'drizzle-orm/postgres-js'
import postgres from 'postgres'
import * as schema from '@/lib/schema'
import { employeesCube, departmentsCube } from '@/lib/cubes'
import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth'
const queryClient = postgres(process.env.DATABASE_URL!)
const db = drizzle(queryClient, { schema })
const handlers = createCubeHandlers({
cubes: [employeesCube, departmentsCube],
drizzle: db,
schema: schema,
extractSecurityContext: async (request) => {
const session = await getServerSession(authOptions)
if (!session?.user) {
()
}
{
: session..,
: session..
}
}
})
() {
params = context.
endpoint = params.[]
(endpoint === ) {
handlers.(request, context)
} (endpoint === ) {
handlers.(request, context)
}
(, { : })
}
() {
params = context.
endpoint = params.[]
(endpoint === ) {
handlers.(request, context)
}
(, { : })
}
Alternative: Individual Handler Creation
You can also create individual handlers:
import { createLoadHandler } from 'drizzle-cube/adapters/nextjs'
import { drizzle } from 'drizzle-orm/postgres-js'
import postgres from 'postgres'
import * as schema from '@/lib/schema'
import { employeesCube, departmentsCube } from '@/lib/cubes'
const queryClient = postgres(process.env.DATABASE_URL!)
const db = drizzle(queryClient, { schema })
const loadHandler = createLoadHandler({
cubes: [employeesCube, departmentsCube],
drizzle: db,
schema: schema,
extractSecurityContext: async (request) => {
return {
organisationId: 'org-1',
userId: 'user-1'
}
}
})
export const POST = loadHandler
import { createMetaHandler } from 'drizzle-cube/adapters/nextjs'
const metaHandler = createMetaHandler({
cubes: [employeesCube, departmentsCube],
: db,
: schema,
: (request) => {
{ : , : }
}
})
= metaHandler
Next.js Adapter Options
interface NextAdapterOptions {
cubes: Cube[]
drizzle: DrizzleDatabase
schema?: any
extractSecurityContext: (request, context?) => SecurityContext | Promise<SecurityContext>
engineType?: 'postgres' | 'mysql' | 'sqlite'
cors?: NextCorsOptions
runtime?: 'edge' | 'nodejs'
}
Security Context Patterns
Session-Based Authentication
extractSecurityContext: async (req) => {
const session = await getSession(req)
if (!session) {
throw new Error('Unauthorized: No session')
}
return {
organisationId: session.organisationId,
userId: session.userId,
role: session.role
}
}
JWT Token Authentication
import jwt from 'jsonwebtoken'
extractSecurityContext: async (req) => {
const authHeader = req.headers.authorization
const token = authHeader?.replace('Bearer ', '')
if (!token) {
throw new Error('Unauthorized: No token provided')
}
try {
const payload = jwt.verify(token, process.env.JWT_SECRET!) as any
return {
organisationId: payload.orgId,
userId: payload.sub,
tenantId: payload.tenantId
}
} catch (error) {
throw new Error('Unauthorized: Invalid token')
}
}
API Key Authentication
extractSecurityContext: async (req) => {
const apiKey = req.headers['x-api-key']
if (!apiKey) {
throw new Error('Unauthorized: No API key')
}
const keyInfo = await db
.select()
.from(apiKeys)
.where(eq(apiKeys.key, apiKey))
.limit(1)
if (!keyInfo[0]) {
throw new Error('Unauthorized: Invalid API key')
}
return {
organisationId: keyInfo[0].organisationId,
userId: keyInfo[0].userId,
scope: keyInfo[0].scope
}
}
Multi-Tenant with Sub-domains
extractSecurityContext: async (req) => {
const host = req.headers.host
const subdomain = host?.split('.')[0]
const org = await db
.select()
.from(organisations)
.where(eq(organisations.subdomain, subdomain))
.limit(1)
if (!org[0]) {
throw new Error('Invalid subdomain')
}
const session = await getSession(req)
return {
organisationId: org[0].id,
userId: session?.userId,
tenantId: org[0].tenantId
}
}
Available Endpoints
All adapters expose these Cube.js-compatible endpoints:
POST /cubejs-api/v1/load
Execute semantic queries and return results.
POST /cubejs-api/v1/load
Content-Type: application/json
Authorization: Bearer <token>
{
"measures": ["Employees.count"],
"dimensions": ["Departments.name"]
}
{
"data": [
{
"Departments.name": "Engineering",
"Employees.count": 50
}
],
"annotation": { ... },
"requestId": "req-123",
"slowQuery": false
}
POST /cubejs-api/v1/sql
Generate SQL without executing (dry-run).
POST /cubejs-api/v1/sql
Content-Type: application/json
Authorization: Bearer <token>
{
"measures": ["Employees.count"],
"dimensions": ["Departments.name"]
}
{
"sql": {
"sql": ["SELECT departments.name, COUNT(employees.id) FROM ..."],
"params": ["org-123"]
}
}
GET /cubejs-api/v1/meta
Get cube metadata (dimensions, measures, types).
GET /cubejs-api/v1/meta
Authorization: Bearer <token>
{
"cubes": [
{
"name": "Employees",
"title": "Employees",
"measures": [...],
"dimensions": [...]
}
]
}
Environment Configuration
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
JWT_SECRET=your-secret-key
PORT=3000
DB_TYPE=postgres
Complete Example: Express with TypeScript
import express from 'express'
import cors from 'cors'
import helmet from 'helmet'
import { createCubeRouter } from 'drizzle-cube/adapters/express'
import { drizzle } from 'drizzle-orm/postgres-js'
import postgres from 'postgres'
import * as schema from './schema'
import { employeesCube, departmentsCube } from './cubes'
import { authenticateJWT } from './middleware/auth'
const queryClient = postgres(process.env.DATABASE_URL!)
const db = drizzle(queryClient, { schema })
const app = express()
app.use(helmet())
app.use(cors())
app.use(express.json())
app.get('/health', (req, res) => {
res.json({ status: })
})
cubeRouter = ({
: [employeesCube, departmentsCube],
: db,
: schema,
: (req, res) => {
(!req.) {
()
}
{
: req..,
: req..,
: req..,
: req..
}
},
: {
: process..?.()
}
})
app.(, authenticateJWT, cubeRouter)
app.( {
.(err.)
res.().({
: err.,
: req.
})
})
= process.. ||
app.(, {
.()
})
Defining Cubes
Cubes are defined separately and imported into the adapter:
import { defineCube } from 'drizzle-cube'
import { eq } from 'drizzle-orm'
import { employees } from '../schema'
export const employeesCube = defineCube('Employees', {
sql: (ctx) => ({
from: employees,
where: eq(employees.organisationId, ctx.securityContext.organisationId)
}),
dimensions: {
id: {
type: 'number',
sql: () => employees.id,
primaryKey: true
},
name: {
type: 'string',
sql: () => employees.name
}
},
measures: {
count: {
type: 'count',
sql: () => employees.id
}
}
})
export { employeesCube } from './employees'
export { departmentsCube }
Best Practices
- Always validate security context - Never trust client input
- Use HTTPS in production - Protect API traffic
- Implement rate limiting - Prevent abuse
- Log queries - Monitor performance and usage
- Handle errors gracefully - Return meaningful error messages
- Validate environment variables - Check configuration on startup
- Pass cubes as array - Let adapters create the semantic layer internally
- Provide schema for type safety - Enables better TypeScript inference
Common Pitfalls
- Missing authentication - Always protect Cube API endpoints
- Wrong function names - Use
createCubeRouter for Express, not createCubeApi
- Passing compiler instead of cubes - Adapters expect
cubes array, NOT a semanticLayer parameter
- Exposing internal errors - Sanitize error messages in production
- No security context validation - Verify context contains required fields
- Incorrect CORS configuration - Configure CORS for your client domains
- Missing database connection pooling - Use connection pools for production
Next Steps
- Define cubes with the
cube-definition skill
- Build queries with the
queries skill
- Create dashboards with the
dashboard skill
- Configure charts with the chart-specific skills