ClickHouse is an open-source columnar database built for high-performance analytical queries. This guide shows how I installed ClickHouse on macOS, ran it in the background using a lightweight nohup setup that stores logs and PID in hidden user folders, and tested it with a minimal Node.js + TypeScript example using @clickhouse/client.
Prerequisites
- macOS (Intel or Apple Silicon)
- Homebrew (optional but recommended for installing ClickHouse)
-
pnpmfor Node package management (ornpm/yarnif you prefer) - Basic knowledge of the terminal
1. Install ClickHouse (macOS)
The ClickHouse official macOS install docs: https://clickhouse.com/docs/install/macOS
The simplest approach via Homebrew:
brew install clickhouse This installs the clickhouse binary. If you used another install method (downloaded the binary/cask), adjust paths below if necessary.
2. Run ClickHouse in the background (nohup)
This guide uses hidden directories in your home for logs and PID to keep things neat:
- Logs:
~/.logs/clickhouse/ - PID:
~/.nohup/clickhouse.pid
Create the folders (one-time)
mkdir -p ~/.logs/clickhouse mkdir -p ~/.nohup touch ~/.logs/clickhouse/server.log ~/.logs/clickhouse/server.err chmod 644 ~/.logs/clickhouse/server.* Start ClickHouse in background
Use the clickhouse binary from your PATH to run it in background with nohup
# start backgrounded; this uses nohup and writes to the hidden logs and pid file BINARY="$(which clickhouse)" nohup "$BINARY" server > ~/.logs/clickhouse/server.log 2> ~/.logs/clickhouse/server.err & echo $! > ~/.nohup/clickhouse.pid Alternative (handles daemonizing binaries better): use setsid which is more robust if the binary forks:
BINARY="$(which clickhouse)" setsid "$BINARY" server > ~/.logs/clickhouse/server.log 2> ~/.logs/clickhouse/server.err < /dev/null & echo $! > ~/.nohup/clickhouse.pid Verify it started & is listening
# check pid/process cat ~/.nohup/clickhouse.pid ps -p "$(cat ~/.nohup/clickhouse.pid)" -o pid,ppid,user,args # see listening ports (8123 HTTP, 9000 native TCP) sudo lsof -iTCP -sTCP:LISTEN | egrep 'clickhouse|8123|9000|9004' || true # quick HTTP test (ClickHouse HTTP endpoint) curl -sS 'http://localhost:8123/' -d 'SELECT version()' || echo "no http response" If curl returns the ClickHouse version string, the server is reachable on :8123.
Tail logs
tail -n 200 ~/.logs/clickhouse/server.log tail -n 200 ~/.logs/clickhouse/server.err # follow live tail -f ~/.logs/clickhouse/server.err Stop the background server
if [ -f ~/.nohup/clickhouse.pid ]; then kill "$(cat ~/.nohup/clickhouse.pid)" && rm -f ~/.nohup/clickhouse.pid else echo "No PID file at ~/.nohup/clickhouse.pid" fi If the process does not stop, find the true daemon PID (sometimes the parent exits and the real daemon has a different PID):
ps aux | grep clickhouse | egrep -v 'grep|egrep' 3. Node.js + TypeScript test app (pnpm)
We’ll create a minimal TypeScript script that:
- connects to ClickHouse HTTP (
http://localhost:8123), - creates a temporary
Memorytable, - inserts rows using
client.insert(...), - queries and prints the rows,
- drops the table.
Project setup
mkdir clickhouse-test && cd clickhouse-test pnpm init pnpm add @clickhouse/client pnpm add -D ts-node typescript @types/node Add the start script in package.json:
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "ts-node src/main.ts" } Create tsconfig.json:
{ "compilerOptions": { "target": "ES2020", "module": "commonjs", "strict": true, "esModuleInterop": true, "skipLibCheck": true } } Example: src/main.ts
import { createClient } from "@clickhouse/client"; async function main() { const client = createClient({ // use url instead of host (host is deprecated) url: "http://localhost:8123", username: "default", database: "default", application: "ts-pnpm-test", }); console.log("pinging ClickHouse..."); if (!(await client.ping())) { console.error( "❌ failed to ping ClickHouse. Is it running on http://localhost:8123 ?", ); process.exit(1); } console.log("✅ ping ok"); // create table (ephemeral Memory engine for testing) await client.exec({ query: ` CREATE TABLE IF NOT EXISTS test_ts_pnpm ( id UInt32, name String ) ENGINE = Memory `, }); // insert rows using the client.insert helper (preferred) await client.insert({ table: "test_ts_pnpm", values: [ { id: 1, name: "alice" }, { id: 2, name: "bob" }, ], // format optional; JSONEachRow is convenient for array-of-objects format: "JSONEachRow", }); // select rows back (explicit JSON output) const result = await client.query({ query: `SELECT id, name FROM test_ts_pnpm ORDER BY id`, format: "JSON", }); const json = (await result.json()) as { meta: unknown[]; data: Array<{ id: number; name: string }>; rows: number; }; console.log("rows:", json.rows); console.table(json.data); // cleanup await client.exec({ query: `DROP TABLE IF EXISTS test_ts_pnpm` }); await client.close(); console.log("done"); } main().catch((err) => { console.error(err); process.exit(1); }); Run the test
pnpm start Expected output:
pinging ClickHouse... ✅ ping ok rows: 2 ┌─────────┬────┬─────────┐ │ (index) │ id │ name │ ├─────────┼────┼─────────┤ │ 0 │ 1 │ 'alice' │ │ 1 │ 2 │ 'bob' │ └─────────┴────┴─────────┘ done Troubleshooting
-
No response on port 8123
- Confirm the server is running and the PID from
~/.nohup/clickhouse.pidis alive. - Try running ClickHouse in the foreground to observe startup errors:
"$(which clickhouse)" server 2>&1 | tee ~/clickhouse-startup-debug.log tail -n 200 ~/clickhouse-startup-debug.log- Search ClickHouse config for custom ports (
http_port,tcp_port) under/opt/homebrew/etc/clickhouse-server/or/etc/clickhouse-server/.
- Confirm the server is running and the PID from
-
Log files empty after
nohup- Some binaries fork/daemonize after start. If
nohuplogs remain empty, the real daemon may be writing to its own log directory (e.g.,/opt/homebrew/var/log/clickhouse/). - Use
setsidor run intmux/screento capture logs more reliably. - Also check macOS unified logs:
log show --predicate 'process == "clickhouse" OR process == "clickhouse-server"' --last 1h --info | tail -n 200 - Some binaries fork/daemonize after start. If
-
Insert parsing errors
- Use
client.insert(...)or sendINSERTas a single SQL string viaclient.exec(...). The@clickhouse/clienthelper methods handle the correct transport format.
- Use
-
Permission or config errors
- The foreground run will show file permission or config parsing errors. Fix config paths or file ownership accordingly.
Wrap-up
You now have:
- ClickHouse installed on macOS,
- a tidy
nohupbackground setup that stores logs in~/.logs/clickhouse/and the pid in~/.nohup/clickhouse.pid, - a minimal Node.js + TypeScript test that confirms read/write connectivity.
Note: The nohup setup will not automatically start ClickHouse after a macOS restart. After reboot, you would need to run the nohup command again to start the server.
If you’d like, I can:
- provide a small
launchdplist that uses your~/.logs/~/.nohuppaths for auto-start on boot, or - create a one-file shell helper that manages start/stop/status for you.
Happy benchmarking! 🚀
Top comments (0)