Skip to content

Every backend eventually grows something that is not a request handler - a queue consumer, a mail sender, a cron runner, a listener speaking a protocol that is not HTTP.

A sidecar is a source folder for exactly that: it declares an entry point to build, and nothing else. No base, no routes, no pages/ or api/ tree.

txt
src/
├── front/                React + Hono, base "/"
├── mailer/               sidecar - no HTTP surface
│   ├── kosmo.config.ts
│   └── entry.ts
└── smtp/                 another one - one process per folder
    ├── kosmo.config.ts
    └── entry.ts

It is still a source folder, so everything that follows from that still holds: ~/ points at it, @/ reaches shared code, its tsconfig.json covers it, and pnpm build mailer builds it on its own.

Declaring one

src/mailer/kosmo.config.ts
ts
import { defineConfig } from "@kosmojs/dev";

export default defineConfig({
  sidecar: {
    entry: "./entry.ts",
    serve: false,
  },
  typecheck: true,
});

sidecar sits alongside frontend, backend and validation as a top-level key, and a folder carries one. Two processes are two folders - that is what keeps pnpm build, pnpm dev and the output tree addressable per process.

Option
entryrequired - the source file, relative to the source folder
serverun it under the dev server - see below
viteConfigVite's UserConfig for this build

The build writes it into dist/<folder>/sidecar/, so the folder above runs with node dist/mailer/sidecar/entry.js.

The extra directory is there for the folders that serve HTTP as well. A web build already owns api/, client/ and ssr/ in its output tree, so the sidecar takes a subdirectory of its own rather than dropping a loose file among them. The path is the same either way, so the command you run never depends on what else the folder does.

Keep it in its own folder

Nothing stops you defining sidecar next to frontend/backend in the same folder. It works - but a source folder is the unit you build, typecheck and deploy, so mixing the two ties things together that have no reason to move at the same time:

  • pnpm build front rebuilds the web app to ship a one-line worker change, and a broken worker fails the build that your site was waiting on
  • typecheck is a folder-level switch, so you cannot exempt a third-party worker entry without exempting your routes with it
  • the two scale and restart on different schedules in production, but ship as one artifact

A separate folder costs a kosmo.config.ts and gives you back pnpm build mailer, pnpm typecheck mailer, and a dist/mailer/ you can deploy on its own.

A sidecar builds the way the API side does - a Node bundle, no client assets - so viteConfig takes the same shape and the same exclusions as the web blocks: root, base, cacheDir, mode, builder, future and legacy are derived and not accepted. viteConfig ›

Sidecars in development

Without serve, a sidecar is built and left alone - it is a build artifact like any other, and starting it is yours.

With serve: true, kosmo serve treats it the way it treats your API: the entry joins the watch set, and a change rebuilds and restarts the process.

That restart is a real process restart, not a hot reload. A sidecar holding a port has to release it before the new process starts, or the respawn fails to bind - close listeners on SIGTERM.

Development only

kosmo preview and dist/run.js build sidecars but never start them. Details ›

Preview and production

preview and run.js are HTTP. run.js is a dispatcher over the folders that serve requests, and a sidecar serves none, so neither starts one.

A sidecar running under pnpm dev will therefore not be running under pnpm preview, and the line is deliberate: the dev server supervises processes, the production runner dispatches requests. Putting process management into run.js would make it a process manager you did not ask for, on every deploy.

In preview, and in production, start them yourself:

sh
node dist/mailer/sidecar/entry.js
node dist/smtp/sidecar/entry.js

Whatever you already use for long-running processes - systemd, a container command, a platform worker - applies unchanged, because a sidecar is a plain Node entry point with no KosmoJS runtime around it.

Typechecking

Nothing to configure. A sidecar lives inside a source folder, and a folder's tsconfig.json already covers everything under it, so pnpm typecheck mailer checks it like any other file in the folder.

A sidecar that wraps third-party JavaScript, or one you simply do not want checked, opts out with the folder-level typecheck key:

ts
sidecar: { entry: "./entry.mjs" },
typecheck: false,

typecheck › · kosmo typecheck ›

Adding one

sh
pnpm sidecar mailer

Rather than a folder with every web option switched off. kosmo sidecar ›

Released under the MIT License.