<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Luke</title>
    <description>The latest articles on DEV Community by Luke (@lureilly1).</description>
    <link>https://dev.to/lureilly1</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3070260%2F98c640a4-4c3f-45be-9f37-26f23380cc93.jpg</url>
      <title>DEV Community: Luke</title>
      <link>https://dev.to/lureilly1</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lureilly1"/>
    <language>en</language>
    <item>
      <title>The Analytics Language Layer: Why Real-Time Data Needs Typed APIs, Not Just Faster Databases</title>
      <dc:creator>Luke</dc:creator>
      <pubDate>Sat, 07 Mar 2026 20:54:48 +0000</pubDate>
      <link>https://dev.to/lureilly1/the-analytics-language-layer-why-real-time-data-needs-typed-apis-not-just-faster-databases-b51</link>
      <guid>https://dev.to/lureilly1/the-analytics-language-layer-why-real-time-data-needs-typed-apis-not-just-faster-databases-b51</guid>
      <description>&lt;p&gt;We’ve made our databases real-time. We haven’t made our analytics interfaces real-time-safe. The missing abstraction between your analytics DB and your consumers is a typed, programmable analytics language layer.&lt;/p&gt;

&lt;p&gt;ClickHouse can ingest a billion rows per second and return aggregations across terabytes in milliseconds. The storage problem is solved. The execution problem is solved. But the interface problem, how consumers actually talk to the engine, remains stuck in the era of hand-crafted SQL strings, copy-pasted metric definitions, and dashboards that nobody trusts.&lt;/p&gt;

&lt;p&gt;This gap didn’t matter much when the consumer was a human analyst writing a query in a notebook. It matters enormously now that the consumer is increasingly a service, a background job, an embedded dashboard, or an AI agent. The weakest link in the modern analytics stack isn’t the database. It’s the language we use to talk to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Your User Is a Model, SQL Becomes a Liability
&lt;/h2&gt;

&lt;p&gt;The push toward AI-driven analytics has exposed a fundamental fragility in text-to-SQL approaches.&lt;/p&gt;

&lt;p&gt;Spider 2.0, released in late 2024 with enterprise-level complexity (3,000+ columns, multiple SQL dialects), showed even the best models solving only 17% of queries. The BIRD-Interact benchmark, which simulates real interactive analytics sessions, reports a best-case success rate of 16%. Uber built an internal text-to-SQL system and found only 50% overlap with ground truth on their own evaluation set.&lt;/p&gt;

&lt;p&gt;The failure modes are what make this genuinely dangerous. An analysis of 50,000+ production LLM-generated queries found that most broken queries execute successfully and return data, they’re semantically wrong but syntactically valid. The model hallucinates columns that don’t exist, picks wrong join paths, applies incorrect aggregation logic, or silently drops required filters. You get a clean DataFrame back. The numbers just happen to be wrong.&lt;/p&gt;

&lt;p&gt;The evidence for a different approach is already in. Snowflake’s internal tests show query accuracy jumping from 40% to 85% when LLMs are routed through a semantic layer instead of raw SQL. DataBrain reports accuracy going from roughly 55% to over 90% with semantic context. dbt Labs reported at Coalesce 2025 that their semantic layer achieved 83% accuracy on natural language analytics questions, with several categories at 100%. The pattern is clear: constrain what the model can express, and accuracy improves dramatically.&lt;/p&gt;

&lt;p&gt;The fix isn’t better documentation or more careful code review. It’s making the interface itself safe by default. Type-safe query builders. Pre-declared metrics and datasets. Schema-aware tooling that catches errors at compile time, not at query time. The system should enforce correctness structurally, not rely on the discipline of the person or model writing the query.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining the Analytics Language Layer
&lt;/h2&gt;

&lt;p&gt;What’s needed is an abstraction that doesn’t exist cleanly in the current tooling landscape. Call it an analytics language layer: a typed, programmable, stable API for a company’s metrics and queries that everything else plugs into.&lt;/p&gt;

&lt;p&gt;It’s not an ORM — those map objects to rows and optimise for CRUD. It’s not a BI tool — those own the visualisation and assume human consumers. It’s not a raw query builder — those give you flexibility without constraints. The analytics language layer sits between the database and all its consumers, providing a contract that is:&lt;/p&gt;

&lt;p&gt;Type-safe and schema-aware. Column references, filter expressions, and aggregation logic are validated at compile time. If the schema changes, your build breaks before your dashboards do.&lt;/p&gt;

&lt;p&gt;Versioned and evolvable. Metrics and query definitions are first-class code constructs with version history, review workflows, and the ability to deprecate gracefully. You can evolve your analytics API the same way you’d evolve a public REST API.&lt;/p&gt;

&lt;p&gt;Multi-protocol. The same definitions are consumable by backend services, React components, CLI tools, AI agent toolchains, traditional BI. The metric definition is written once; the consumption pattern varies.&lt;/p&gt;

&lt;p&gt;This is the layer that platform teams at scale end up building internally, whether they call it a semantic layer, a metrics catalog, a query translation engine, or something else entirely. The pattern is universal because the problem is universal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seven Predictions for the Next Three Years
&lt;/h2&gt;

&lt;p&gt;The analytics language layer isn’t a speculative concept. The convergence is already visible. Here’s where this heads:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Every serious ClickHouse deployment will have a dedicated analytics language layer sitting between the database and its consumers. The alternative is linear growth in platform engineering headcount as consumer count increases.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AI agents will talk to semantic layers, not databases. The MCP server approach of exposing raw SQL to agents will mature into structured tool interfaces where agents invoke named, typed queries rather than generating SQL strings. The accuracy data demands it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;BI tools will consume typed endpoints. Rather than authoring raw SQL or maintaining their own query logic, BI tools will connect to analytics language layers the same way frontend applications consume REST or GraphQL APIs. The Open Semantic Interchange initiative (dbt, Snowflake, Salesforce, ThoughtSpot) is an early signal of this convergence.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Metric definitions will be code, not configuration. YAML-based metric definitions will give way to programmatic definitions in the same language as the application (TypeScript, Python), enabling IDE support, testing, and the same CI/CD workflows used for application code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Schema drift will become a build failure, not a production incident. Type-safe analytics layers will catch breaking schema changes at compile time. The “we renamed a column and three dashboards broke” class of incident will go the way of runtime type errors in typed languages — still possible, but structurally discouraged.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The semantic layer market will consolidate around API-first architectures. Gartner’s concept of “composable analytics” — modular, API-first business components — will define the winning pattern. Tools that can’t serve their semantics via APIs will lose ground to those that can.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Real-time and batch semantic layers will merge. The artificial divide between dbt (batch) and Cube/hypequery (real-time) will collapse as the analytics language layer becomes the single interface regardless of freshness. The layer’s job is to provide safe access; the underlying engine handles the latency profile.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Challenge
&lt;/h2&gt;

&lt;p&gt;If you’re running ClickHouse in production and your consumers are still writing raw SQL strings to query it, you’ve solved the hard problem (making the database fast) and left the easy problem unsolved (making it safe to talk to).&lt;/p&gt;

&lt;p&gt;The analytics language layer is the missing piece. Not because the database needs help, but because every consumer that touches it does. The organisations that adopt this abstraction early will ship fewer data incidents, onboard new teams faster, and safely expose real-time analytics to more products and agents than their competitors.&lt;/p&gt;

&lt;p&gt;We’ve spent a decade making databases real-time. It’s time to make the interfaces real-time-safe.&lt;/p&gt;

&lt;p&gt;We’re solving this problem at &lt;a href="https://hypequery.com" rel="noopener noreferrer"&gt;hypequery&lt;/a&gt;, you can check out the project on &lt;a href="https://github.com/hypequery/hypequery" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>clickhouse</category>
      <category>typescript</category>
      <category>analytics</category>
    </item>
    <item>
      <title>Seven Companies, One Pattern: Why Every Scaled ClickHouse Deployment Looks the Same</title>
      <dc:creator>Luke</dc:creator>
      <pubDate>Thu, 01 Jan 2026 17:36:40 +0000</pubDate>
      <link>https://dev.to/lureilly1/seven-companies-one-pattern-why-every-scaled-clickhouse-deployment-looks-the-same-gdm</link>
      <guid>https://dev.to/lureilly1/seven-companies-one-pattern-why-every-scaled-clickhouse-deployment-looks-the-same-gdm</guid>
      <description>&lt;p&gt;Uber, Cloudflare, Instacart, GitLab, Lyft, Microsoft, and Contentsquare share more than ClickHouse adoption. Each independently built the same abstraction stack—query translation engines, semantic layers, and self service interfaces that let thousands of people query without reading a line of raw ClickHouse SQL. Different industries, different workloads, identical pattern.&lt;/p&gt;

&lt;p&gt;This convergence is not stylistic. It is the direct consequence of optimising ClickHouse for speed. Every schema level optimisation that squeezes out milliseconds also adds cognitive overhead that analysts should not carry. Platform teams end up absorbing that complexity into infrastructure because it is the only way to scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture Everyone Arrives At
&lt;/h2&gt;

&lt;p&gt;Every scaled deployment eventually looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-------------------------------------------------------------+
|  SELF SERVICE LAYER                                         |
|  Superset, Grafana, Kibana, internal UIs                    |
+------------------------------+------------------------------+
                               |
+------------------------------v------------------------------+
|  SEMANTIC LAYER                                             |
|  GraphQL schemas, YAML configs, DSLs, metrics catalogs      |
+------------------------------+------------------------------+
                               |
+------------------------------v------------------------------+
|  QUERY TRANSLATION LAYER                                    |
|  QueryBridge, ABR router, AST optimisers, HogQL compilers   |
+------------------------------+------------------------------+
                               |
+------------------------------v------------------------------+
|  CLICKHOUSE                                                 |
|  Optimised schemas, materialised views, array based storage |
+-------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tooling changes; Uber leans on Elasticsearch compatible query shapes, Cloudflare wraps everything in GraphQL, Lyft writes TOML while Instacart prefers YAML—but the roles never do.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Root Cause: Performance Forces Complexity
&lt;/h2&gt;

&lt;p&gt;The explanation is not that ClickHouse SQL is "hard." The explanation is that &lt;strong&gt;performant ClickHouse schemas are deliberately complex&lt;/strong&gt;, so teams build veils between the raw schema and the consumers who only need governed, high level access.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uber learned only 5% of indexed fields were queried. Their fix—array based key value storage with runtime type resolution—removed schema conflicts but made direct SQL unusable for most users.&lt;/li&gt;
&lt;li&gt;Contentsquare's AST optimiser propagates partition keys, merges subqueries, and simplifies algebra before emitting SQL. Ten fold speedups materialised, but only after they asked users to describe queries as ASTs rather than SQL strings.&lt;/li&gt;
&lt;li&gt;Instacart orders fraud detection tables by &lt;code&gt;(shopper_id, created_at_ts)&lt;/code&gt; so partitions prune hundreds of millions of rows. Asking every analyst to reason about sort key permutations does not scale.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every win on the query planner side adds new rules about materialised columns, adaptive indexing, or array pairs. The abstraction layer hides those rules so performance gains are preserved without retraining an entire company.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Each Company Built
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Company&lt;/th&gt;
&lt;th&gt;Translation Layer&lt;/th&gt;
&lt;th&gt;Semantic Layer&lt;/th&gt;
&lt;th&gt;Key Outcome&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Uber&lt;/td&gt;
&lt;td&gt;QueryBridge (ES → SQL)&lt;/td&gt;
&lt;td&gt;Schema metadata service&lt;/td&gt;
&lt;td&gt;10,000+ Kibana dashboards migrated unchanged&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare&lt;/td&gt;
&lt;td&gt;ABR router&lt;/td&gt;
&lt;td&gt;GraphQL analytics API&lt;/td&gt;
&lt;td&gt;Consistent response times at any data volume&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Instacart&lt;/td&gt;
&lt;td&gt;Yoda feature system&lt;/td&gt;
&lt;td&gt;YAML feature definitions&lt;/td&gt;
&lt;td&gt;Engineers shifted from 80% → 20% time on heuristics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Microsoft&lt;/td&gt;
&lt;td&gt;Titan query service&lt;/td&gt;
&lt;td&gt;Visual query builder&lt;/td&gt;
&lt;td&gt;2,500 non engineers run 100K queries per day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contentsquare&lt;/td&gt;
&lt;td&gt;AST optimiser&lt;/td&gt;
&lt;td&gt;Query representation layer&lt;/td&gt;
&lt;td&gt;10× speedup on the slowest 5% of queries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitLab&lt;/td&gt;
&lt;td&gt;ClickHouse::Client&lt;/td&gt;
&lt;td&gt;Arel based QueryBuilder&lt;/td&gt;
&lt;td&gt;Sub second queries across 100M row datasets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lyft&lt;/td&gt;
&lt;td&gt;Dynamic transpiler&lt;/td&gt;
&lt;td&gt;TOML configurations&lt;/td&gt;
&lt;td&gt;8× cost reduction versus their previous Druid stack&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Seven different teams built the identical stack because there is no alternative architecture that survives petabyte scale self service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Economics Drive the Convergence
&lt;/h2&gt;

&lt;p&gt;Abstraction layers are an economic decision, not just architectural taste. Without them, each new analyst adds a stream of Jira tickets for query triage and optimisation, so platform headcount grows linearly with the number of people asking questions. With them, platform headcount grows with data volume and infrastructure complexity instead.&lt;/p&gt;

&lt;p&gt;Market data echoes this: self service analytics is compounding at ~15% CAGR from $4.8B in 2024 to a projected $17.5B by 2033. Organisations fund these layers because they change cost curves.&lt;/p&gt;

&lt;p&gt;Microsoft's Titan team proves the point. A single platform group serves 2,500+ monthly active users executing 100,000 queries daily. Without the abstraction layer, supporting that workload would demand an army of embedded data engineers.&lt;/p&gt;

&lt;p&gt;Reported ROI numbers are similarly blunt:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;40–60% fewer inbound IT data requests&lt;/li&gt;
&lt;li&gt;30% faster decision making cycles&lt;/li&gt;
&lt;li&gt;50%+ faster analysis completion&lt;/li&gt;
&lt;li&gt;30%+ template reuse as platform effects accumulate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are step changes in organisational throughput, not marginal “quality of life” upgrades.&lt;/p&gt;

&lt;h2&gt;
  
  
  Organisational Transformation
&lt;/h2&gt;

&lt;p&gt;Abstractions collapse the feedback loop between “I spotted a pattern” and “I validated it.”&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Before abstraction&lt;/em&gt;: Analyst notices a signal → files a ticket → engineer writes and hardens a query → someone reviews → results show up days later.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;After abstraction&lt;/em&gt;: Analyst encodes logic in a config or UI → platform translates → results land the same day.&lt;/p&gt;

&lt;p&gt;Instacart quantified it: before the Yoda platform, engineers spent 80% of their time writing and maintaining fraud heuristics. After YAML based feature definitions on top of ClickHouse, that dropped to 20%. Engineers now work on model quality, not query babysitting.&lt;/p&gt;

&lt;p&gt;Uber's QueryBridge migration preserved 10,000+ Kibana dashboards with zero user retraining. Lyft's interfaces let “any person… even an exec that's able to write SQL” touch real time datasets through Trino without ever seeing ClickHouse.&lt;/p&gt;

&lt;p&gt;The platform team absorbs complexity once; the organisation scales without proportional engineering headcount.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why These Abstractions Become Moats
&lt;/h2&gt;

&lt;p&gt;Once embedded, these layers create natural lock in—not through dark patterns, but through workflow familiarity. Uber's Kibana dashboards represent years of institutional memory. PostHog's HogQL becomes communal language. GitLab's Arel based builders teach analysts GitLab specific conventions. Instacart's YAML definitions capture historical fraud heuristics.&lt;/p&gt;

&lt;p&gt;Retraining humans, migrating saved queries, and rebuilding dashboards is far harder than swapping databases. The abstraction layer becomes an asset that justifies further investment.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI Is Joining the Stack, Not Sitting on Top
&lt;/h2&gt;

&lt;p&gt;The newest twist is treating AI as native infrastructure inside this stack.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ClickHouse 25.7 shipped text to SQL generation directly inside the CLI, while ClickHouse Cloud bakes ClickHouse.ai into the workflow with awareness of live schemas and dashboards.&lt;/li&gt;
&lt;li&gt;Shopify wired 30+ MCP (Model Context Protocol) servers into LibreChat so employees issue natural language questions that resolve into governed queries.&lt;/li&gt;
&lt;li&gt;Lightdash combines dbt defined metrics with AI agents that generate chart ready outputs from plain English while respecting semantic layer rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The canonical four tier stack now looks like: natural language → semantic layer → query translation → ClickHouse. 2024 was the experiment phase; 2025 onward is standardisation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Insight: Architecture Follows Economics
&lt;/h2&gt;

&lt;p&gt;Seven companies. Billions of events per second. Hundreds of petabytes. Hundreds of thousands of daily queries. Every one of them discovered that ClickHouse performance only democratises insight when the complexity is quarantined behind abstractions.&lt;/p&gt;

&lt;p&gt;The pattern holds because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Raw ClickHouse SQL grows unwieldy when schemas are tuned for petabyte performance.&lt;/li&gt;
&lt;li&gt;Platform teams can internalise that complexity once and unlock sublinear scaling.&lt;/li&gt;
&lt;li&gt;Business users expect intuitive interfaces, not database literacy.&lt;/li&gt;
&lt;li&gt;Organisational stickiness forms around tools, saved dashboards, and shared semantics.&lt;/li&gt;
&lt;li&gt;AI assisted workflows need governed semantic layers as context.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The lesson is not purely technical. It is organisational: &lt;strong&gt;platform teams absorb complexity into infrastructure so analytical capability scales with data volume rather than engineering headcount&lt;/strong&gt;. That is why seven companies built the same thing, why the market compounds at 15% annually, and why AI is being wired into the stack itself.&lt;/p&gt;

&lt;p&gt;Want to learn more about building production-ready ClickHouse applications with &lt;a href="https://hypequery.com" rel="noopener noreferrer"&gt;hypequery&lt;/a&gt;? Check out our guide on &lt;a href="https://dev.to/lureilly1/getting-started-with-clickhouse-in-typescript-using-hypequery-15k4"&gt;getting started with ClickHouse and TypeScript&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Sources: Engineering blogs from Uber, Cloudflare, Instacart, GitLab, Lyft, Microsoft, Contentsquare, and PostHog; ClickHouse case studies; market data from Mordor Intelligence and Grand View Research.&lt;/p&gt;

</description>
      <category>clickhouse</category>
      <category>programming</category>
    </item>
    <item>
      <title>Type-Safe Schema Management &amp; Evolution in ClickHouse: Keeping Analytics in Sync</title>
      <dc:creator>Luke</dc:creator>
      <pubDate>Thu, 17 Jul 2025 05:15:43 +0000</pubDate>
      <link>https://dev.to/lureilly1/type-safe-schema-management-evolution-in-clickhouse-keeping-analytics-in-sync-cmd</link>
      <guid>https://dev.to/lureilly1/type-safe-schema-management-evolution-in-clickhouse-keeping-analytics-in-sync-cmd</guid>
      <description>&lt;p&gt;Modern analytics applications rely on ClickHouse for its speed and scalability, but as team structures and business needs evolve, so too must your schema. Uncontrolled schema changes known as schema drift - can silently break applications or degrade performance, especially when teams manage both application and analytics codebases in strongly typed languages like TypeScript.&lt;/p&gt;

&lt;p&gt;In this comprehensive guide, we’ll look at tools such as &lt;a href="https://hypequery.com" rel="noopener noreferrer"&gt;hypequery&lt;/a&gt; and explore best practices to solve the schema evolution challenge by providing automated type generation, compile-time safety, and streamlined workflows that keep your analytics applications resilient as your data grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Schema Evolution Challenge
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is Schema Drift?
&lt;/h3&gt;

&lt;p&gt;Schema drift occurs when the actual database schema deviates from what your application expects. This happens frequently in analytics environments where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New business requirements demand additional data fields&lt;/li&gt;
&lt;li&gt;Data sources evolve and require schema adjustments&lt;/li&gt;
&lt;li&gt;Multiple teams modify the same database independently&lt;/li&gt;
&lt;li&gt;Hot fixes are applied directly to production without proper documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Pain Points of Traditional Approaches
&lt;/h3&gt;

&lt;p&gt;Most TypeScript applications connecting to ClickHouse suffer from these common problems:&lt;/p&gt;

&lt;p&gt;Manual Type Definitions: Developers manually create and maintain TypeScript interfaces that mirror their database schema. This is tedious and error-prone.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Manual types - prone to drift&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;UserAnalytics&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;session_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;last_seen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// Did someone add a new column? You'll find out at runtime...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Runtime Errors: Schema changes often go unnoticed until queries fail in production, causing downtime and data inconsistencies.&lt;/p&gt;

&lt;p&gt;Raw SQL Strings: Without type safety, SQL queries are written as strings, making them vulnerable to typos and SQL injection attacks&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Raw SQL - no type safety&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
  SELECT user_id, sesion_count, last_seen  -- Typo in 'session_count'
  FROM user_analytics 
  WHERE non_existent_column = ?  -- This will fail at runtime
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Slow Development Cycles: Developers must manually check and update types whenever the schema changes, slowing down feature development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Principles of Effective Schema Design
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Model for Your Query Patterns
&lt;/h3&gt;

&lt;p&gt;Start by understanding your most frequent analytics queries. Design tables, columns, and data types to support them.&lt;/p&gt;

&lt;p&gt;Denormalise for read efficiency: wide tables and pre-aggregated metrics are preferred over excessive joins134.&lt;/p&gt;

&lt;p&gt;Use appropriate table engines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MergeTree for most cases.&lt;/li&gt;
&lt;li&gt;ReplicatedMergeTree for HA clusters.&lt;/li&gt;
&lt;li&gt;ReplacingMergeTree, CollapsingMergeTree, AggregatingMergeTree for special use cases&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Partitioning and Ordering
&lt;/h3&gt;

&lt;p&gt;Partition tables for efficient batch deletes e.g. by month or day.&lt;/p&gt;

&lt;p&gt;Choose an order key to support fast range queries e.g. (user_id, timestamp)&lt;/p&gt;

&lt;p&gt;Aim for partitions of 100MB–1GB for best balance between metadata and performance&lt;/p&gt;

&lt;p&gt;Use LowCardinality(String) for enumerations, Decimal for currency, and DateTime64 where needed&lt;/p&gt;

&lt;h2&gt;
  
  
  Pillars of Schema Evolution Best Practices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Schema Change Automation: The Foundations
&lt;/h3&gt;

&lt;p&gt;Change-based migration tools (e.g., Houseplant, Goose) use explicit, ordered scripts (e.g., SQL, YAML) to apply and track every schema modification. Each migration script represents a single change, and migrations are executed in sequence.&lt;/p&gt;

&lt;p&gt;State-based tools (e.g., Atlas, Bytebase) start with a “desired state” schema definition. They automatically detect any difference between the application’s expectation and the current database, then plan and apply the necessary migration scripts, minimising drift and enforcing policy checks&lt;/p&gt;

&lt;p&gt;Version Control Integration: All migrations and schema definitions should be committed to source control for traceability and collaboration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automating Schema Changes &amp;amp; Type Generation
&lt;/h3&gt;

&lt;p&gt;For strongly-typed application layers (TypeScript, Go, Java, etc.), automated type generation tools ensure that application code and database schema remain in lockstep.&lt;/p&gt;

&lt;p&gt;hypequery is a notable tool for TypeScript and ClickHouse users: it introspects your live database schema and emits up-to-date, type-safe interfaces that application code can trust at compile time.&lt;/p&gt;

&lt;p&gt;When schema changes are applied (via any migration tool), hypequery regenerates TypeScript types based on your current ClickHouse schema.&lt;br&gt;
If drift is detected (such as a missing column or type change), the next type-check or build will fail, catching mismatches before they reach production.&lt;/p&gt;
&lt;h3&gt;
  
  
  Additive-First (Expand–Migrate–Contract) Pattern
&lt;/h3&gt;

&lt;p&gt;Favour ADD COLUMN with defaults over destructive DROP or renames.&lt;br&gt;
When renaming, add the new column, copy/backfill data, switch reads, then safely drop the old column after a grace period (“expand–migrate–contract”).&lt;/p&gt;
&lt;h3&gt;
  
  
  Testing and Observability
&lt;/h3&gt;

&lt;p&gt;Assert that queries work with current schema via integration tests before deployment.&lt;/p&gt;

&lt;p&gt;Monitor outstanding mutations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sql
SELECT database, table, parts_to_do FROM system.mutations WHERE is_done = 0;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Observe system tables (system.mutations, system.replication_queue, system.query_log) for DDL lag and slow queries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Governance and Change Review
&lt;/h3&gt;

&lt;p&gt;Require schema migration scripts (or generated diffs) to be code-reviewed alongside application changes.&lt;/p&gt;

&lt;p&gt;Assign change owners and maintain a schema changelog documenting the rationale and business impact.&lt;/p&gt;

&lt;h3&gt;
  
  
  Zero-Downtime Deployment Techniques
&lt;/h3&gt;

&lt;p&gt;Use ADD COLUMN for online DDL.&lt;/p&gt;

&lt;p&gt;Schedule mutative operations (DROP, MODIFY) during low-traffic windows; coordinate on clusters to avoid replication lag.&lt;/p&gt;

&lt;p&gt;For critical fields like partition or order keys, create a new table, backfill, then swap aliases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example Modern CI/CD Pipeline
&lt;/h3&gt;

&lt;p&gt;Apply Migrations: Run schema migration tool (Houseplant, Goose, Atlas, Bytebase) to update database schema, typically in a controlled staging environment.&lt;/p&gt;

&lt;p&gt;Regenerate Types: Use hypequery (or a similar tool in your language) to generate or refresh application type definitions from live database schema.&lt;/p&gt;

&lt;p&gt;Schema Drift Check: Automate a git diff or similar check — if the generated type models have changed, require a review or halt the pipeline until resolved.&lt;/p&gt;

&lt;p&gt;Compile / Type-Check: Run type checking on application code; build fails on any schema mismatch.&lt;/p&gt;

&lt;p&gt;Test &amp;amp; Deploy: Proceed only when all checks pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Discipline in schema management is foundational for rapidly scaling, reliable analytics on ClickHouse. While tooling (migration frameworks, type generators, managed platforms) eases much of the operational load, the enduring best practices remain: automate every aspect of schema change, design for additive evolution, continuously test, and rigorously monitor. This approach enables teams to move swiftly, minimise drift, and deliver resilient analytical applications, regardless of team size or infrastructure maturity.&lt;/p&gt;

&lt;p&gt;Whether you favour change-based scripts, state-driven declarative management, or full-featured platforms, the key is to match your workflow to team needs — and standardise it. Treating schema as code, embedding drift detection in your pipelines, and leveraging ClickHouse’s system observability will future-proof your analytics infrastructure as business and data evolve.&lt;/p&gt;

&lt;p&gt;Want to learn more about building production-ready ClickHouse applications with &lt;a href="https://hypequery.com" rel="noopener noreferrer"&gt;hypequery&lt;/a&gt;? &lt;a href="https://dev.to/lureilly1/getting-started-with-clickhouse-in-typescript-using-hypequery-15k4"&gt;Check out our guide on getting started with ClickHouse and TypeScript&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>clickhouse</category>
      <category>webdev</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Building Dashboards on ClickHouse with hypequery and Next.js</title>
      <dc:creator>Luke</dc:creator>
      <pubDate>Sat, 12 Jul 2025 19:52:24 +0000</pubDate>
      <link>https://dev.to/lureilly1/building-dashboards-on-clickhouse-with-hypequery-and-nextjs-4lbm</link>
      <guid>https://dev.to/lureilly1/building-dashboards-on-clickhouse-with-hypequery-and-nextjs-4lbm</guid>
      <description>&lt;p&gt;Modern analytics applications demand both real-time insights and developer productivity. If you’re building dashboards on ClickHouse with TypeScript, you’ve likely run into the pain of raw SQL strings, manual type definitions, and the challenges of interactive, scalable data apps. In this guide, we’ll show you how to solve these problems using hypequery — a TypeScript-first, type-safe query builder for ClickHouse — alongside Next.js, the leading React framework.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You can check hypequery out on &lt;a href="https://github.com/hypequery/hypequery" rel="noopener noreferrer"&gt;github&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why ClickHouse + TypeScript for Dashboards?
&lt;/h2&gt;

&lt;p&gt;ClickHouse is an industry-leading analytical database, purpose-built for high-performance, real-time analytics. TypeScript, meanwhile, brings type safety and modern tooling to JavaScript development. But connecting the two has historically meant sacrificing type safety, maintainability, or both.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;hypequery bridges this gap by providing:&lt;/strong&gt;&lt;br&gt;
A fully type-safe query builder tailored for ClickHouse&lt;br&gt;
Autocomplete and type validation for every part of your query&lt;br&gt;
Features designed for dashboard development: cross-filtering, streaming, pagination, and more&lt;/p&gt;
&lt;h3&gt;
  
  
  The Challenge
&lt;/h3&gt;

&lt;p&gt;Real-Time Dashboards Without the Pain&lt;br&gt;
Traditional approaches to dashboard development with ClickHouse and TypeScript are fraught with issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Raw SQL strings: Prone to runtime errors and SQL injection&lt;/li&gt;
&lt;li&gt;Manual type definitions: Tedious to maintain and easily out of sync&lt;/li&gt;
&lt;li&gt;Complex query management: Difficult to build interactive, cross-filtered dashboards&lt;/li&gt;
&lt;li&gt;Handling large result sets: Risk of memory issues and slow performance&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  The Solution: hypequery
&lt;/h3&gt;

&lt;p&gt;hypequery is a TypeScript SDK and query builder for ClickHouse, designed specifically for real-time, type-safe analytics dashboards. Its core features include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complete Type Safety: Every query is type-checked, from table and column names to filter values and result types. Get autocomplete and instant feedback in your IDE&lt;/li&gt;
&lt;li&gt;Flexible Join System: Fluent API for all join types, with support for custom relationships and smart aliasing&lt;/li&gt;
&lt;li&gt;SQL Expressions &amp;amp; Functions: Use ClickHouse-specific functions and raw SQL expressions while maintaining type safety&lt;/li&gt;
&lt;li&gt;Streaming Support: Efficiently process large result sets with streaming, ideal for real-time dashboards and monitoring&lt;/li&gt;
&lt;li&gt;Advanced Filtering: Build complex, reusable filters and apply cross-filtering logic across multiple queries&lt;/li&gt;
&lt;li&gt;Comprehensive Logging: Track queries, parameters, execution times, and row counts for observability
= Schema Generation: CLI tool to generate TypeScript types directly from your ClickHouse schema, keeping your types in sync&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Getting Started: Setting Up hypequery with Next.js
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Install Dependencies
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @hypequery/clickhouse @clickhouse/client
npm &lt;span class="nb"&gt;install &lt;/span&gt;next react react-dom
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  2. Generate TypeScript Types from Your ClickHouse Schema
&lt;/h3&gt;

&lt;p&gt;Use the CLI to introspect your schema and generate types:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx hypequery-generate &lt;span class="nt"&gt;--host&lt;/span&gt; your-clickhouse-host &lt;span class="nt"&gt;--database&lt;/span&gt; your-database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a generated-schema.ts file you’ll import in your app.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Initialise the Query Builder and build a quer
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createQueryBuilder&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@hypequery/clickhouse&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;IntrospectedSchema&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./generated-schema&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createQueryBuilder&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;IntrospectedSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-clickhouse-host&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Example: Fetch top 10 recent trips with fares over $50&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;trips&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pickup_datetime&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dropoff_datetime&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;total_amount&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;total_amount&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;orderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pickup_datetime&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DESC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Column names are type-checked, typos or missing columns are caught at compile time.&lt;br&gt;
Query results are fully typed, no more any or manual casting&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Cross-Filtering for Interactive Dashboards
&lt;/h3&gt;

&lt;p&gt;Interactive dashboards often need to synchronise filters across multiple queries. hypequery’s CrossFilter makes this easy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CrossFilter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@hypequery/clickhouse&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CrossFilter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pickup_datetime&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gte&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2024-01-01&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;total_amount&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;trips&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;trips&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;applyCrossFilters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;drivers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;drivers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;applyCrossFilters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Integrate with Next.js API Routes
&lt;/h3&gt;

&lt;p&gt;In your Next.js app, create an API route that fetches data using hypequery:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pages/api/trips.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextApiRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextApiResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../lib/db&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// your hypequery instance&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextApiRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextApiResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;trips&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pickup_datetime&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;total_amount&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;orderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pickup_datetime&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DESC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Type safety is preserved from the database to your frontend.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Display Data in Your Next.js Dashboard
&lt;/h3&gt;

&lt;p&gt;Use React components to fetch and display the data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// components/TripsTable.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;useSWR&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;swr&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetcher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;TripsTable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSWR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/trips&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fetcher&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Loading&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;thead&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;tr&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;th&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Pickup&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/th&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;th&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Dropoff&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/th&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;th&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Total&lt;/span&gt; &lt;span class="nx"&gt;Amount&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/th&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/tr&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/thead&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;tbody&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="na"&gt;trip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;tr&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;trip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pickup_datetime&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;td&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;trip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pickup_datetime&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/td&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;td&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;trip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dropoff_datetime&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/td&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;td&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;trip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;total_amount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/td&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/tr&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="p"&gt;))}&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/tbody&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/table&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why Use hypequery for Real-Time Dashboards?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Type safety at every step: No more runtime surprises or mismatched columns&lt;/li&gt;
&lt;li&gt;Developer productivity: Autocomplete, instant error checking, and less boilerplate&lt;/li&gt;
&lt;li&gt;Built for analytics: Features like cross-filtering, streaming, and pagination are designed for dashboard use cases&lt;/li&gt;
&lt;li&gt;Works everywhere: Node.js and browser support for flexible deployments&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;If you’re building real-time dashboards on ClickHouse with TypeScript and Next.js, hypequery provides the type safety, developer experience, and analytics features you need to ship faster and with confidence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ready to build?&lt;/strong&gt;&lt;br&gt;
Install hypequery and start building your type-safe analytics dashboard today:&lt;/p&gt;

&lt;p&gt;Explore the &lt;strong&gt;&lt;a href="//www.hypequery.com/docs"&gt;hypequery documentation&lt;/a&gt;&lt;/strong&gt; for more examples and advanced features.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>clickhouse</category>
      <category>webdev</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Getting Started with ClickHouse in TypeScript using hypequery.</title>
      <dc:creator>Luke</dc:creator>
      <pubDate>Fri, 11 Jul 2025 18:56:12 +0000</pubDate>
      <link>https://dev.to/lureilly1/getting-started-with-clickhouse-in-typescript-using-hypequery-15k4</link>
      <guid>https://dev.to/lureilly1/getting-started-with-clickhouse-in-typescript-using-hypequery-15k4</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;For type-safe ClickHouse queries, check out &lt;a href="//www.hypequery.com"&gt;hypequery&lt;/a&gt;, the TypeScript SDK for Clickhouse&lt;/p&gt;

&lt;h2&gt;
  
  
  Why ClickHouse + TypeScript?
&lt;/h2&gt;

&lt;p&gt;Before diving into the implementation, let's understand why this combination is so compelling:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ClickHouse's Strengths:&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;TypeScript's Benefits:&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;Together, they provide a robust foundation for building analytics applications that are both performant and maintainable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Setup with @clickhouse/client
&lt;/h2&gt;

&lt;p&gt;Let's start with the basics. First, install the official ClickHouse client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install @clickhouse/client 

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's a minimal setup to connect to ClickHouse:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@clickhouse/client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:8123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Test the connection&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;testConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT version()&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;JSON&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Connected to ClickHouse:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Connection failed:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;testConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Creating Your First Table &amp;amp; Query&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createTable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
    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)
  `&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;insertSampleData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user_events&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;event_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;page_view&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/home&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;event_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;button&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;signup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;event_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;page_view&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/pricing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;JSONEachRow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;queryEvents&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
      SELECT 
        event_type,
        count() as event_count
      FROM user_events 
      WHERE timestamp &amp;gt;= now() - INTERVAL 1 DAY
      GROUP BY event_type
      ORDER BY event_count DESC
    `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;JSON&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Event counts:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Type Safety Gap
&lt;/h2&gt;

&lt;p&gt;While the above code works, you'll quickly notice some significant limitations:&lt;/p&gt;

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

&lt;p&gt;Here's a typical issue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This compiles but fails at runtime&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT non_existent_column FROM user_events&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Typo!&lt;/span&gt;
  &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;JSON&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// TypeScript doesn't know what this contains&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// data is 'any'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Introducing Type Safety with hypequery
&lt;/h2&gt;

&lt;p&gt;This is where hypequery comes in. hypequery is a TypeScript SDK specifically designed for building type-safe dashboards and analytics applications with ClickHouse. &lt;/p&gt;

&lt;p&gt;It solves the type safety problems we just identified while maintaining the full power of ClickHouse.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up hypequery
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install @hypequery/clickhouse @clickhouse/client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Generate your database schema
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx hypequery-generate-types --host=http://localhost:8123 --username=default --password=password --database=my_db

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a &lt;code&gt;generated-schema.ts&lt;/code&gt; file that you can import in your application:&lt;/p&gt;

&lt;h3&gt;
  
  
  Type-Safe Querying
&lt;/h3&gt;

&lt;p&gt;Now you can write fully type-safe queries!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createQueryBuilder&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@hypequery/clickhouse&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;IntrospectedSchema&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./generated-schema&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createQueryBuilder&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;IntrospectedSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:8123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-username&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Fully type-safe query with autocomplete&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;eventCounts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;hq&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user_events&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;event_type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;event_count&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;timestamp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gte&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;now() - INTERVAL 1 DAY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;event_type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;orderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;event_count&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DESC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// eventCounts is fully typed - TypeScript knows the exact structure&lt;/span&gt;
&lt;span class="nx"&gt;eventCounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event_type&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event_count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;p&gt;hypequery provides several advanced features that make building analytics applications easier including:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;The combination of ClickHouse's performance and TypeScript's type safety creates a powerful development experience:&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Checkout the repo on github: &lt;a href="https://github.com/hypequery/hypequery" rel="noopener noreferrer"&gt;https://github.com/hypequery/hypequery&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>clickhouse</category>
      <category>typescript</category>
      <category>analytics</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
