<!-- Markdown mirror of https://justscale.sh/docs/cluster/overview -->

# Cluster Overview

The Unix socket that ties your processes together

ℹ️Info

This page documents the underlying `app.serve()` / `JustScale().build()` API. **Most apps don't call it directly.** The canonical entrypoint is `defineApp(import.meta, env => JustScale().add(env)...)` exported from `src/app.ts`; the `just` CLI handles env selection, build, compile, and serve. See [Quick Start](https://justscale.sh/docs/overview/quick-start) for the typical project shape (no `main.ts`, no manual `app.serve()`). The lower-level API below is for embedding, tests, and tooling that needs to drive the runtime by hand.

## What is the Cluster?

When the app starts, JustScale opens a Unix domain socket alongside any HTTP / WebSocket / gRPC transports you wired up. That socket **is** the cluster — a private, same-user-only RPC channel that lets external processes drive the running app: the `just` CLI, sibling worker processes, signal delivery between durable processes, scheduled tasks, and HMR reload coordination.

The same controllers serve all of it. A route defined with `Cli('migrate')` is callable from the terminal via `just migrate`; a route defined with `Get('/users')` is callable over HTTP. Define once, expose across whichever transports your app loaded.

## Creating a Cluster (low-level)

Under the hood, `defineApp` calls `JustScale().add(...).build()` and then `app.serve(...)`. Tests and embeddings can do this by hand:

embed.tsTypeScript

```typescript
import JustScale from '@justscale/core';
import { UserController } from './controllers/user';
import { DbController } from './controllers/db';

const app = JustScale()
  .add(UserController)
  .add(DbController)
  .build();

// Start serving — HTTP + cluster socket for CLI
await app.serve({ http: 3000 });
```

This single call:

- Starts an HTTP server on port 3000 (when @justscale/http is loaded)
- Creates a Unix socket for CLI communication
- Registers all transport handlers automatically

## Multi-Transport Controllers

Controllers can define routes for multiple transports. Each transport has its own route factory:

controllers/user.tsTypeScript

```typescript
import { createController } from '@justscale/core';
import { Get, Post } from '@justscale/http';
import { Cli } from '@justscale/core/cli';
import { body } from '@justscale/http/builder';
import { z } from 'zod';
import { UserService } from '../services/user';

const CreateUserArgs = z.object({
  email: z.string().email(),
  password: z.string().min(8),
});

export const UserController = createController('user', {
  inject: { users: UserService },
  routes: (services) => ({
    // HTTP route - accessible via REST API
    create: Post('/')
      .body(CreateUserArgs)
      .handle(async ({ body, res }) => {
        const user = await services.users.create(body);
        res.json({ user });
      }),

    // CLI route - accessible via CLI commands
    createCli: Cli('create')
      .input(CreateUserArgs)
      .handle(async ({ args, io }) => {
        const user = await services.users.create(args);
        io.log(`Created user: ${user.email}`);
        io.result({ email: user.email });
      }),

    // HTTP route for listing
    list: Get('/')
      .handle(async ({ res }) => {
        const allUsers = await services.users.findAll();
        res.json({ users: allUsers });
      }),

    // CLI route for listing
    listCli: Cli('list')
      .handle(async ({ io }) => {
        const allUsers = await services.users.findAll();
        io.table(allUsers, ['email', 'name']);
      }),
  }),
});
```

This controller exposes both HTTP and CLI interfaces:

Bash

```bash
# HTTP usage:
curl -X POST http://localhost:3000/user \
  -H "Content-Type: application/json" \
  -d '{"email":"test@example.com","password":"secret123"}'

curl http://localhost:3000/user

# CLI usage:
just user create --email test@example.com --password secret123
just user list
```

## App Methods

### app.serve()

Start serving all transports. This is the primary way to start your application:

serve-options.tsTypeScript

```typescript
import JustScale from '@justscale/core';

const app = JustScale().build();

// HTTP + cluster socket
await app.serve({ http: 3000 });

// Custom socket path
await app.serve({
  http: 3000,
  socketPath: '/tmp/my-app.sock',
});

// Socket only (no HTTP)
await app.serve({ noSocket: false });

// HTTP only (no socket — CLI won't work)
await app.serve({
  http: 3000,
  noSocket: true,
});
```

### app.stop()

Stop the app and clean up all resources:

stop-app.tsTypeScript

```typescript
await app.stop();
```

### Direct invocation (tests)

A built app exposes its container and routes for in-process testing or direct invocation:

testing-direct.tsTypeScript

```typescript
import { invoke } from '@justscale/core/cli';

// Direct CLI command invocation — no socket, no HTTP
const result = await invoke(app, 'user create', {
  email: 'test@example.com',
  password: 'secret123',
});

// In-process HTTP testing (no listen() required)
import { createTestClient } from '@justscale/testing';
import { httpTransport } from '@justscale/http/testing';

const client = await createTestClient(app, {
  transports: { http: httpTransport },
});
const typed = client.http.useControllers({ users: UserController });
```

## Features with JustScale

Features plug into the same `.add(...)` chain and can contribute routes to multiple transports:

app-with-features.tsTypeScript

```typescript
import JustScale from '@justscale/core';
import { AuthFeature } from '@justscale/auth';
import { UserController } from './controllers/user';

const app = JustScale()
  .add(AuthFeature())
  .add(UserController)
  .build();

await app.serve({ http: 3000 });
```

The auth feature now provides both HTTP and CLI routes:

Bash

```bash
# HTTP routes:
curl -X POST http://localhost:3000/auth/register
curl http://localhost:3000/auth/me?token=xxx

# CLI routes:
just auth create-user --email admin@example.com
just auth list-users
just auth delete-session --session abc123
```

## Transport Plugins

Transports are registered as plugins. When you import a transport package, it automatically registers itself with the cluster system:

transport-registration.tsTypeScript

```typescript
// These imports auto-register their transports:
import '@justscale/http';      // registers HTTP transport
import '@justscale/core/cli';  // registers CLI transport
import JustScale from '@justscale/core';
import { UserController } from './controllers/user';

// The app knows about all registered transports
const app = JustScale()
  .add(UserController)
  .build();

// app.serve() starts all registered transports
await app.serve({ http: 3000 });
```

ℹ️Info

Future transports (WebSockets, gRPC, etc.) will follow the same pattern - just import the package and define routes using the transport's route factory.

## Cluster Socket

When `app.serve()` is called, a Unix domain socket is created for inter-process communication. This socket:

- Enables the justscale CLI to communicate with your running app
- Supports bidirectional streaming for progress indicators and prompts
- Uses local credentials for security (only accessible by the same user)
- Auto-cleans up on process exit

socket-info.tsTypeScript

```typescript
await app.serve({ http: 3000 });

console.log(`Socket path: ${app.socketPath}`);
// Socket path: /tmp/justscale-1000.sock

console.log(`Is serving: ${app.isServing}`);
// Is serving: true
```

## Connecting to a Running Cluster

You can connect to a running cluster from another process:

connect-to-cluster.tsTypeScript

```typescript
import { connectToCluster } from '@justscale/core/cluster';

// Connect to the running cluster via Unix socket
const client = await connectToCluster();

// Invoke a CLI command
const result = await client.invoke('user create', {
  email: 'test@example.com',
  password: 'secret123',
});

// Stream output
client.on('stdout', (data) => console.log(data));
client.on('stderr', (data) => console.error(data));

await client.close();
```

## Testing a built app

For testing, drive the built app directly — no listen() needed:

user.test.tsTypeScript

```typescript
import { describe, test } from 'node:test';
import assert from 'node:assert';
import JustScale from '@justscale/core';
import { invoke } from '@justscale/core/cli';
import { createTestClient } from '@justscale/testing';
import { httpTransport } from '@justscale/http/testing';
import { UserController } from './controllers/user';

describe('UserController', () => {
  const app = JustScale()
    .add(UserController)
    .build();

  test('creates user via CLI', async () => {
    const result = await invoke(app, 'user create', {
      email: 'test@example.com',
      password: 'secret123',
    });
    assert.ok(typeof (result as any).id === 'string');
  });

  test('creates user via HTTP', async () => {
    const client = await createTestClient(app, {
      transports: { http: httpTransport },
    });
    const { api } = client.http.useControllers({ users: UserController });
    const response = await api.users.create({
      email: 'test@example.com',
      password: 'secret123',
    });
    assert.strictEqual(response.status, 200);
  });
});
```

## Next Steps

- CLI Usage
- Request Handling
- Controllers
