We value your privacy

This site uses cookies to improve your browsing experience, analyze site traffic, and show personalized content. See our Privacy Policy.

  1. MooseStack
  2. Embed ClickHouse analytics in Fastify

Embed ClickHouse analytics in your Fastify backend

Overview

This quickstart guide shows how to embed MooseStack into an existing Fastify app so you can define ClickHouse schema in TypeScript and serve type-safe analytics endpoints from Fastify—while keeping Fastify as your only deployed backend.

From this you will be able to:

  • Model ClickHouse schema using Moose OLAP objects (TypeScript) alongside your Fastify code
  • Integrate type-safe ClickHouse queries (via the Moose OLAP client) into your Fastify API endpoints
  • Generate/apply migrations against production ClickHouse when you're ready to deploy
What runs in production?

Only your Fastify server runs in production. You use MooseStack as a library + CLI to manage schema/migrations and to generate an importable package (moose/dist) that your Fastify runtime uses to query ClickHouse.

Guide outline

  • Get started: add moose/ as a sibling workspace and configure dependencies + env vars.
  • Model table: define a table model (for example, Events) in moose/app/index.ts.
  • Start local ClickHouse: run moose dev.
  • Use in Fastify: define a client + query helper in moose/, then call it from a Fastify route.
  • Deploy to production: generate/apply migrations and set production env vars.

Project structure

This guide's examples mirror our reference project, which you can find in the /examples/fastify-moose directory of the MooseStack GitHub repository:

  • The Fastify app is at the repo root (./)
  • A sibling moose/ workspace package that contains your ClickHouse models and query helpers

If you're applying this to an existing repo with a different layout, keep the package name moose the same and adjust only the paths/commands.

    • pnpm-workspace.yaml
    • package.json
    • tsconfig.json
    • .env (optional)
      • index.ts
      • app.ts
      • router.ts
        • clickhouseController.ts
      • moose.config.toml
      • package.json
      • tsconfig.json
        • client.ts
        • index.ts
        • (auto-generated by build)
    • .gitignore

Prerequisites

Before you start

Node.js 20+

Minimum version required for @514labs/moose-lib

Download →

Docker Desktop

MooseStack uses Docker to run ClickHouse locally

Download →

Existing Fastify app

TypeScript recommended

pnpm

Package manager (workspaces)

Get started

This section creates a moose/ workspace package next to your Fastify app using moose init.

Initialize Moose in your monorepo

From your monorepo, cd to the directory where you want the Moose project folder to be created (commonly your repo root so this becomes ./moose), then run:

cd <path-in-your-monorepo>
moose init moose typescript-empty

Then install dependencies from your monorepo root:

cd <your-monorepo-root>
pnpm install
Use generated config as-is

moose init creates moose/package.json and moose/tsconfig.json with the required compiler output settings. You do not need extra manual compilation wiring.

Add your moose workspace as a dependency

Add moose as a workspace dependency in your Fastify app workspace package.json:

(your Fastify app workspace)/package.json
{  "dependencies": {    "moose": "workspace:*"  }}

Configure environment variables (Fastify runtime)

Create or update your Fastify app's environment variables with the following ClickHouse connection details for your local development environment:

MOOSE_CLIENT_ONLY=true
 
MOOSE_CLICKHOUSE_CONFIG__DB_NAME=local
MOOSE_CLICKHOUSE_CONFIG__HOST=localhost
MOOSE_CLICKHOUSE_CONFIG__PORT=18123
MOOSE_CLICKHOUSE_CONFIG__USER=panda
MOOSE_CLICKHOUSE_CONFIG__PASSWORD=pandapass
MOOSE_CLICKHOUSE_CONFIG__USE_SSL=false

Model table

In this step you'll define your first ClickHouse model as an OlapTable object.

Open moose/app/index.ts and replace its contents with your first table definition:

moose/app/index.ts
import { OlapTable } from "@514labs/moose-lib"; export interface EventModel {  id: string;  amount: number;  event_time: Date;  status: 'completed' | 'active' | 'inactive';} export const Events = new OlapTable<EventModel>("events", {  orderByFields: ["event_time"],});

This defines a TypeScript interface for your table schema and creates an OlapTable that maps to a ClickHouse table named events. When you run moose dev in the next step, Moose will automatically create this table in your local ClickHouse instance.

Adding more tables

Add more tables by exporting additional OlapTable objects from moose/app/index.ts. See OlapTable Reference.

Start local ClickHouse

Start the Moose Runtime in dev mode. This brings up a local ClickHouse instance (via Docker) and hot-reloads schema changes to it whenever you edit your models.

Navigate to your moose directory and run:

moose dev

moose dev handles TypeScript compilation automatically and reloads after successful incremental compiles.

See Dev Environment Configuration for more details.

1) Set up a shared ClickHouse client in moose/

First, create a shared ClickHouse client initializer inside your moose package. This keeps connection logic in one place and lets Fastify handlers call simple query helpers.

moose/app/client.ts
import { getMooseClients, Sql, QueryClient } from "@514labs/moose-lib"; async function getClickhouseClient(): Promise<QueryClient> {  const { client } = await getMooseClients({    host: process.env.MOOSE_CLICKHOUSE_CONFIG__HOST ?? "localhost",    port: process.env.MOOSE_CLICKHOUSE_CONFIG__PORT ?? "18123",    username: process.env.MOOSE_CLICKHOUSE_CONFIG__USER ?? "panda",    password: process.env.MOOSE_CLICKHOUSE_CONFIG__PASSWORD ?? "pandapass",    database: process.env.MOOSE_CLICKHOUSE_CONFIG__DB_NAME ?? "local",    useSSL:      (process.env.MOOSE_CLICKHOUSE_CONFIG__USE_SSL ?? "false") === "true",  });   return client.query;} export async function executeQuery<T>(query: Sql): Promise<T[]> {  const queryClient = await getClickhouseClient();  const result = await queryClient.execute(query);  return result.json();}

2) Define a query helper in moose/app/index.ts

Next, define a query helper that uses the shared client and export it from "moose". The example defines a very basic query for you to use as a starting point:

moose/app/index.ts
import { OlapTable, sql } from "@514labs/moose-lib";import { executeQuery } from "./client"; export interface EventModel {  id: string;  amount: number;  event_time: Date;  status: 'completed' | 'active' | 'inactive';} export const Events = new OlapTable<EventModel>("events", {  orderByFields: ["event_time"],}); export async function getEvents(limit: number = 10): Promise<EventModel[]> {  return await executeQuery<EventModel>(    sql`SELECT * FROM ${Events} ORDER BY ${Events.columns.event_time} DESC LIMIT ${limit}`,  ); }

For more query patterns and details on using the sql template tag, see Reading Data.

3) Import and use the helper in Fastify

Now your Fastify app can import and use the helper from "moose" in any of your route handlers, like this:

src/controller/clickhouseController.ts
import type { FastifyInstance, FastifyRequest, FastifyReply } from "fastify"; import { getEvents } from "moose"; type RecentEventsQuery = {  limit?: string;}; export default async function clickhouseController(fastify: FastifyInstance) {  // GET /api/v1/clickhouse/recent?limit=10  fastify.get(    "/recent",    async function (      request: FastifyRequest<{ Querystring: RecentEventsQuery }>,      reply: FastifyReply,    ) {      const limit = Math.min(        100,        Math.max(1, Number(request.query.limit ?? 10)),      );       const rows = await getEvents(limit);      reply.send({ rows });    },  );}

Deploy to production

There's no separate production Moose Runtime to deploy. You just need to:

  1. Apply your schema to production ClickHouse
  2. Configure your production environment with production credentials

Enable planned migrations

Make sure ddl_plan = true is set in [features] in moose/moose.config.toml:

moose/moose.config.toml
[features]ddl_plan = true

Generate a migration plan

Important: Use production credentials

This command connects to the ClickHouse instance you specify in --clickhouse-url and generates a migration plan for that database. Use your production ClickHouse URL + credentials if you intend to deploy these schema changes to production.

For detailed information about migration workflows, lifecycle management, and plan formats, see the Migrations documentation.

moose generate migration \
  --clickhouse-url "clickhouse://user:password@your-prod-host:8443/db?secure=true" \
  --save

This creates files in migrations/ including plan.yaml.

Apply the migration

moose migrate \
  --clickhouse-url "clickhouse://user:password@your-prod-host:8443/db?secure=true"

Configure production environment

In production, set these environment variables in your deployment platform. Make sure to use your production ClickHouse URL + credentials.

MOOSE_CLIENT_ONLY=true
MOOSE_CLICKHOUSE_CONFIG__DB_NAME=production_db
MOOSE_CLICKHOUSE_CONFIG__HOST=your-clickhouse-host.example.com
MOOSE_CLICKHOUSE_CONFIG__PORT=8443
MOOSE_CLICKHOUSE_CONFIG__USER=prod_user
MOOSE_CLICKHOUSE_CONFIG__PASSWORD=prod_password
MOOSE_CLICKHOUSE_CONFIG__USE_SSL=true

Troubleshooting

If you see import errors for import ... from "moose", confirm:

  • Your root pnpm-workspace.yaml includes both your Fastify app and the moose workspace
  • You ran pnpm install so workspace:* links are created
  • moose dev is running and TypeScript compilation completed successfully
  • If your moose package uses a custom outDir, verify moose/package.json (main, types, exports) points to that output

Next steps

  • OlapTable Reference — Primary keys, engines, and configuration
  • Read Data — Query patterns and the Moose client
  • Migrations — Schema versioning and migration strategies
  • Schema Optimization — Ordering keys and partitioning

On this page

OverviewGuide outlineProject structurePrerequisitesGet startedInitialize Moose in your monorepoConfigure environment variables (Fastify runtime)Model tableStart local ClickHouseDeploy to productionTroubleshootingNext steps
Edit this page
FiveonefourFiveonefour
Fiveonefour Docs
MooseStackHostingTemplatesGuides
Release Notes
Source531
  • Overview
Build a New App
  • 5 Minute Quickstart
  • Browse Templates
  • Existing ClickHouse
Add to Existing App
  • Next.js
  • Fastify
Fundamentals
  • Moose Runtime
  • MooseDev MCP
  • Language Server
  • Data Modeling
Moose Modules
  • Moose OLAP
  • Moose Streaming
  • Moose Workflows
  • Moose APIs & Web Apps
Deployment & Lifecycle
  • Moose Dev
  • Moose Migrate
  • Moose Deploy
Reference
  • API Reference
  • Query Layer
  • Data Types
  • Table Engines
  • CLI
  • Configuration
  • Observability Metrics
  • Help
  • Release Notes
Contribution
  • Documentation
  • Framework