Skip to main content

Deploy on Node

A CovaraApp runs standalone on Node via startServer from covara/node (which wraps @hono/node-server).

import { createClient } from "@libsql/client";
import { drizzle } from "drizzle-orm/libsql";
import { createCovara } from "covara";
import { startServer } from "covara/node";
import { todos } from "./schema.js";
import { env } from "./env.js"; // your createEnv schema — see Environment variables

const db = drizzle(createClient({ url: env.DB_FILE_NAME }));

const app = createCovara({ cors: true }).resource(todos, {
db, id: todos.id, auth: { public: true },
});

const server = await startServer(app, {
port: env.PORT,
hostname: "0.0.0.0",
onListen: ({ port }) => console.log(`Listening on ${port}`),
});

startServer(app, options) resolves once the server is listening and returns { port, address, close }. If port is omitted, PORT from the environment is used (default 3000).

Graceful shutdown

By default startServer installs SIGTERM/SIGINT handlers that drain the instance before exiting. On a shutdown signal it:

  1. Flips readiness so /readyz returns 503 — load balancers and Kubernetes readiness probes stop routing new traffic here.
  2. Closes long-lived SSE subscriptions cleanly so clients reconnect to a healthy instance instead of seeing a dropped socket.
  3. Waits a bounded drain window (drainTimeoutMs, default 10s) before closing the listener and exiting.
await startServer(app, {
port: 3000,
drainTimeoutMs: 15000, // drain window before forcing the socket closed
// gracefulShutdown: false, // opt out and manage shutdown yourself via close()
});

Set gracefulShutdown: false to manage shutdown yourself — the returned close() still performs the same drain sequence. /healthz (liveness) keeps returning 200 during drain; only /readyz flips to 503.

Docker

npx covara create writes a Dockerfile and docker-compose.yml (app + Redis, plus Postgres when --db postgres) and a .dockerignore. See the CLI.

Scaling

Run multiple instances behind a load balancer; give every instance a shared Redis KV (via initializeKV) so subscriptions, sessions, rate limits, and the task queue are shared. See Scaling across instances.

Runtime notes