DEV Community

Luke
Luke

Posted on

Getting Started with ClickHouse in TypeScript using hypequery.

ClickHouse has become the go-to choice for high-performance analytics, powering everything from real-time dashboards to complex data warehouses. As TypeScript continues to dominate the JavaScript ecosystem, combining these two technologies creates a powerful foundation for modern data applications. In this guide, we'll get you from zero to running your first ClickHouse query in TypeScript in under 10 minutes.

For type-safe ClickHouse queries, check out hypequery, the TypeScript SDK for Clickhouse

Why ClickHouse + TypeScript?

Before diving into the implementation, let's understand why this combination is so compelling:

ClickHouse's Strengths:

  • Blazing Fast Analytics: Designed for OLAP workloads, ClickHouse can process billions of rows in seconds
  • Columnar Storage: Optimised for analytical queries with incredible compression ratios
  • SQL Compatibility: Familiar SQL syntax with powerful analytical functions
  • Horizontal Scaling: Easily handles petabyte-scale datasets

TypeScript's Benefits:

  • Type Safety: Catch errors at compile time rather than runtime
  • Developer Experience: Superior IDE support with autocomplete and refactoring
  • Maintainability: Self-documenting code with clear interfaces
  • Modern Tooling: Excellent ecosystem for testing, building, and deploying

Together, they provide a robust foundation for building analytics applications that are both performant and maintainable.

Quick Setup with @clickhouse/client

Let's start with the basics. First, install the official ClickHouse client:

npm install @clickhouse/client 

Enter fullscreen mode Exit fullscreen mode

Here's a minimal setup to connect to ClickHouse:


import { createClient } from '@clickhouse/client'

const client = createClient({
  host: 'http://localhost:8123',
  username: 'default',
  password: '',
  database: 'default'
})

// Test the connection
async function testConnection() {
  try {
    const result = await client.query({
      query: 'SELECT version()',
      format: 'JSON'
    })
    console.log('Connected to ClickHouse:', await result.json())
  } catch (error) {
    console.error('Connection failed:', error)
  }
}

testConnection()
Enter fullscreen mode Exit fullscreen mode

Creating Your First Table & Query

async function createTable() {
  await client.command(`
    CREATE TABLE IF NOT EXISTS user_events (
      id UUID DEFAULT generateUUIDv4(),
      user_id UInt64,
      event_type String,
      timestamp DateTime DEFAULT now(),
      properties Map(String, String)
    ) ENGINE = MergeTree()
    ORDER BY (user_id, timestamp)
  `)
}

async function insertSampleData() {
  await client.insert({
    table: 'user_events',
    values: [
      { user_id: 1, event_type: 'page_view', properties: { page: '/home' } },
      { user_id: 1, event_type: 'click', properties: { button: 'signup' } },
      { user_id: 2, event_type: 'page_view', properties: { page: '/pricing' } }
    ],
    format: 'JSONEachRow'
  })
}

async function queryEvents() {
  const result = await client.query({
    query: `
      SELECT 
        event_type,
        count() as event_count
      FROM user_events 
      WHERE timestamp >= now() - INTERVAL 1 DAY
      GROUP BY event_type
      ORDER BY event_count DESC
    `,
    format: 'JSON'
  })

  const data = await result.json()
  console.log('Event counts:', data)
}
Enter fullscreen mode Exit fullscreen mode

The Type Safety Gap

While the above code works, you'll quickly notice some significant limitations:

  • No Query Type Safety: The SQL strings are just strings – no autocomplete, no validation.
  • Unknown Result Types: TypeScript doesn't know what structure your queries return.
  • Manual Type Definitions: You need to manually define interfaces for every query result
  • Runtime Errors: Typos in column names or table names only surface at runtime

Here's a typical issue:

// This compiles but fails at runtime
const result = await client.query({
  query: 'SELECT non_existent_column FROM user_events', // Typo!
  format: 'JSON'
})

// TypeScript doesn't know what this contains
const data = await result.json() // data is 'any'
Enter fullscreen mode Exit fullscreen mode

Introducing Type Safety with hypequery

This is where hypequery comes in. hypequery is a TypeScript SDK specifically designed for building type-safe dashboards and analytics applications with ClickHouse.

It solves the type safety problems we just identified while maintaining the full power of ClickHouse.

Setting Up hypequery

npm install @hypequery/clickhouse @clickhouse/client
Enter fullscreen mode Exit fullscreen mode

Generate your database schema

npx hypequery-generate-types --host=http://localhost:8123 --username=default --password=password --database=my_db

Enter fullscreen mode Exit fullscreen mode

This creates a generated-schema.ts file that you can import in your application:

Type-Safe Querying

Now you can write fully type-safe queries!

import { createQueryBuilder } from '@hypequery/clickhouse'
import { IntrospectedSchema } from './generated-schema'

const hq = createQueryBuilder<IntrospectedSchema>({
  host: 'http://localhost:8123',
  password: 'your-password',
  username: 'your-username',
  database: 'default'
})

// Fully type-safe query with autocomplete
const eventCounts = await hq
  .table('user_events')
  .select(['event_type'])
  .count('id', 'event_count')
  .where('timestamp', 'gte', 'now() - INTERVAL 1 DAY')
  .groupBy('event_type')
  .orderBy('event_count', 'DESC')
  .execute()

// eventCounts is fully typed - TypeScript knows the exact structure
eventCounts.forEach(row => {
  console.log(`${row.event_type}: ${row.event_count}`)
})
Enter fullscreen mode Exit fullscreen mode

Features

hypequery provides several advanced features that make building analytics applications easier including:

  • Table joins and filtering which can be defined once and applied across your queries or at a query level
  • Streaming support for memory-efficient processing of large result sets
  • Support for ClickHouse specific functions, common table expressions and raw SQL for those hard to reach edge cases
  • First class developer experience with comprehensive query logging, debugging and SQL generation

Why This Matters

The combination of ClickHouse's performance and TypeScript's type safety creates a powerful development experience:

  • Catch Errors Early: Type checking prevents runtime errors from typos and schema mismatches
  • IDE Support: Full autocomplete for table names, column names, and query methods
  • Refactoring Confidence: Rename columns or tables and let TypeScript guide you through the changes
  • Self-Documenting Code: The types serve as documentation for your data structures

Checkout the repo on github: https://github.com/hypequery/hypequery

Top comments (0)