Moose

Getting Started

Start from Existing ClickHouse

Start With Your ClickHouse Database

Viewing

Early Access Feature

We’re actively developing this feature and would love your feedback. While it’s ready for most use cases, check out Known Limitations for details on what’s still in progress.

Why Use Moose with ClickHouse?

Moose provides a layer on top of your existing ClickHouse database to make it simpler to manage your tables, views, pipelines, and APIs in plain TypeScript code:

Manually Managing ClickHouse?

Copy-pasting DDL between environments

Running the same CREATE TABLE and CREATE MATERIALIZED VIEW statements in dev, staging, and prod, hoping you didn't miss a field

Forgetting dependency order

Creating a materialized view before its source table exists, then scrambling to figure out what broke

Lost schema changes

That ALTER TABLE you ran last week? Good luck remembering what it was when you need to apply it to production

How Moose Can Speed Things Up:

Reduce boilerplate

Generate typed models from your existing tables instead of hand-coding everything

Build APIs in code, not Jinja templates

Write TypeScript/Python functions with proper IDE support instead of templated SQL

Add streaming with one line of code

Turn any table into a real-time streaming destination without configuring Kafka topics, serialization, or message schemas

Version control your database

Manage schema changes, materialized views, and transformations like actual code

Leverage CI/CD built for ClickHouse

See a plan for your database changes before promoting to production, and apply them automatically when you do

Quick Setup

Prerequisites

  • OS: macOS or Linux (WSL supported for Windows)
  • Docker Desktop/Engine: 24.0.0+
  • Access to ClickHouse Database: Connection URL with credentials

Get Your ClickHouse Connection String

You need a ClickHouse connection URL. Format looks like this:

http://username:password@host:port/?database=database_name

Try with ClickHouse Playground

Want to test without your own ClickHouse? Use the ClickHouse Playground with the connection string above. It has sample datasets (read-only) you can experiment with.

https://explorer:@play.clickhouse.com:443/?database=default

Install Moose & Bootstrap Your Project

Install Moose if you haven’t already:

# Install Moose
bash -i <(curl -fsSL https://fiveonefour.com/install.sh) moose

Create a new project:

# Bootstrap from your ClickHouse database
moose init my-project --from-remote <YOUR_CLICKHOUSE_CONNECTION_STRING> --language typescript
 
# Set up dependencies
cd my-project
npm install

Start Your Local Environment

moose dev

This spins up your entire data stack locally and generates typed models from your ClickHouse tables.

Check What Got Generated

Moose created data models for each of your ClickHouse tables. Check app/index.ts to see what you got.

    • index.ts
  • moose.config.toml
  • package.json

Type Mapping

Moose automatically maps ClickHouse data types to appropriate language-specific types:

ClickHouse TypeTypeScript TypeNotes
Stringstring
Int8, Int16, Int32, Int64number
UInt8, UInt16, UInt32, UInt64number
Float32, Float64number
Decimal(P,S)number
Date, Date32Date
DateTime, DateTime64Date
Array(T)T[]Nested type mapping
UUIDstring
Booleanboolean
Nullable(T)T | null
LowCardinality(T)TMaps to base type
JSONRecord<string, any>Partial support

What’s Next?

Now that you’ve got Moose talking to your ClickHouse database:

1. Get Some Data Locally

# CLI command to seed from remote ClickHouse
moose seed from-remote-clickhouse <YOUR_CLICKHOUSE_CONNECTION_STRING> --rows 100

2. Build Materialized Views That Don’t Break

Moose lets you define materialized views in code with automatic schema management and dependency orchestration:

What you’re probably doing now:

-- Step 1: Create target table (remember the exact schema!)
CREATE TABLE daily_user_stats (
    user_id UInt64,
    date Date,
    event_count UInt64
) ENGINE = MergeTree()
ORDER BY (user_id, date);
 
-- Step 2: Create materialized view (hope you got the dependency order right!)
CREATE MATERIALIZED VIEW daily_user_stats_mv TO daily_user_stats AS
SELECT 
    user_id,
    toDate(timestamp) as date,
    count(*) as event_count
FROM user_events 
GROUP BY user_id, date;
 
-- Step 3: Copy-paste this to staging... then production... 
-- Step 4: Pray you didn't miss anything when the schema changes
-- Step 5: Remember to DROP VIEW before changing SELECT logic

With Moose:

import { userEvents } from "./tables"; // Import existing tables
import { MaterializedView, sql } from "@514labs/moose-lib";
 
// Define your target schema
interface DailyUserStats {
  user_id: number;
  date: Date;
  event_count: number;
}
 
// Define what you want - Moose generates all the DDL
const dailyStats = new MaterializedView<DailyUserStats>({
  // ✅ SQL in actual code, directly reference table names and columns for autocompletion
  selectStatement: sql`
    SELECT 
      ${userEvents.user_id}, 
      toDate(${userEvents.timestamp}) as date, 
      count(*) as event_count
    FROM ${userEvents} 
    GROUP BY ${userEvents.user_id}, date
  `,
  selectTables: [userEvents],
  tableName: "daily_user_stats",
  materializedViewName: "daily_user_stats_mv",
  orderByFields: ["user_id", "date"]
});
 
// Moose automatically:
// ✅ Creates the target table with correct schema
// ✅ Sets up the materialized view 
// ✅ Handles dependency order
// ✅ Manages schema migrations
// ✅ Provides hot-reload in development

Ready to dive deeper?Materialized Views docs

3. Build APIs That Handle the Annoying Stuff

Consumption APIs are a way to write native TypeScript functions that wrap your ClickHouse queries and make them available for your frontend clients via a type-safe REST API.

They are designed to take care of the boilerplate that slows you down like:

  • Request validation
  • Response type mapping
  • SQL injection protection
  • Documentation
  • JWT validation (if you’re using it)

Ready to dive deeper?Consumption APIs docs

4. Use AI to Generate Analytics & APIs

Want AI to generate insights and APIs from your data? Try Aurora - our AI analytics agent for Moose projects.

Current Limitations

What we’re working on adding:

  • Selective table import: Imports all tables (filtering coming soon)
  • Default values: Adding annotations to your models to set default values for your tables
  • Advanced ClickHouse types:
    • Map(K, V) types
    • Tuple(T1, T2, ...) types
    • Geo types (Point, Ring, Polygon, etc.)

Tables with these types will be skipped

Right now, tables featuring these types will be skipped during the initialization process.

Known Limitations

Supported Types with Manual Type Adjustments:

LowCardinality(T)

Currently, LowCardinality(T) maps to base type T.

You can override the autogenerated data models by making the following adjustments to fields that are LowCardinality in your generated code:

You can add LowCardinality optimization by using intersection types:

interface MyModel {
  status: string & LowCardinality; // Optimized for low cardinality
  category: string;                // Regular string
}

JSON types

Maps to Record<string, any>

Enum types

Enum column types that have an empty string as a key will be skipped:

CREATE TABLE my_table (
    my_enum Enum8(""=0, "A"=1, "B"=2)
);
## This does not work:
class my_enum(StringToEnumMixin, IntEnum):
    "" = 0 ## Illegal to have an empty string as a key
    A = 1
    B = 2
 
## Instead what will be generated is:
class my_enum(StringToEnumMixin, IntEnum):
    = 0 ## Will lead to a type error, consider using a different key
    A = 1
    B = 2

Learning & Community Resources

Get help, learn, contribute, and stay connected with the Moose community

Join Our Community

Connect with developers and get help with your projects

Learn & Watch

Video tutorials, demos, and deep-dives into Moose features

Talk to Us

Contact the Moose maintainers for support and feedback

Contribute

Check out the code, contribute to Moose, and report issues

Follow Us on X

Follow us on X for the latest news and updates