Docs menu

Quickstart

Early release (v0.2). APIs and deployment guidance may still change, and dotwire isn't production-hardened yet, so evaluate it carefully before relying on it. Feedback and issues are welcome at github.com/pasztor-mark/dotwire.

In this guide you run dotwire, Postgres (TimescaleDB), and NATS on your machine with Docker Compose. Then you use the Node.js host SDK to grant a user access to a room, mint a token, and send a message.

Prerequisites

  • Docker with Docker Compose
  • OpenSSL, to generate keys
  • Git
  • Node.js 20 or later, for the host SDK step

1. Clone the repository

git clone https://github.com/pasztor-mark/dotwire.git
cd dotwire
cp .env.example .env

.env is gitignored. .env.example is the template.

2. Generate keys

You need two unrelated keys. One is dotwire's own at-rest encryption key. The other is an RSA keypair that stands in for your host application's JWT signing key.

# AES-256-GCM at-rest encryption key (32 bytes, base64)
openssl rand -base64 32

# RS256 keypair. The private key stays outside dotwire; it plays the host.
openssl genpkey -algorithm RSA -out dev-jwt.key -pkeyopt rsa_keygen_bits:2048
openssl rsa -pubout -in dev-jwt.key -out dev-jwt.pub

See Encryption and retention for why these are separate key systems.

3. Fill in .env

Set these values in .env:

POSTGRES_PASSWORD=<migrator-password>
POSTGRES_APP_PASSWORD=<app-password>

Dotwire__Encryption__ActiveKeyId=k1
Dotwire__Encryption__Keys__k1=<base64-32-byte-key>

Auth__Issuer=dev-host
Auth__Audience=dotwire
Auth__Keys__dev="-----BEGIN PUBLIC KEY-----\n<public-key-body>\n-----END PUBLIC KEY-----"
  • POSTGRES_PASSWORD is for the dotwire owner role, which runs migrations. POSTGRES_APP_PASSWORD is for the dotwire_app runtime role.
  • Dotwire__Encryption__Keys__k1 is the output of openssl rand -base64 32.
  • The suffix after Auth__Keys__ is the key id (kid). compose.yaml passes Auth__Keys__dev to the container, so keep the key id dev.
  • The public key must be one double-quoted line, with \n in place of each line break. This prints the finished line from dev-jwt.pub:
echo "Auth__Keys__dev=\"$(awk '{printf "%s\\n",$0}' dev-jwt.pub)\""

4. Start the stack

docker compose up -d --build

Omit --build to run the published image, ghcr.io/pasztor-mark/dotwire:latest (linux/amd64 and linux/arm64), instead of building from source.

On startup, dotwire runs its database migrations and provisions its JetStream streams.

5. Check that dotwire is up

curl http://localhost:8080/healthz

A 200 OK response means the service is up.

6. Grant access, mint a token, and send a message

In a separate folder, install the host SDK:

npm install @dotwire/host

Create first-message.mjs. Point privateKeyPem at the dev-jwt.key you generated in step 2.

import { readFileSync } from 'node:fs';
import { DotwireHostClient } from '@dotwire/host';

const host = new DotwireHostClient({
  baseUrl: 'http://localhost:8080',
  privateKeyPem: readFileSync('../dotwire/dev-jwt.key', 'utf8'),
  keyId: 'dev',
  issuer: 'dev-host',
  audience: 'dotwire'
});

const roomId = 'a0000000-0000-0000-0000-000000000001';

await host.setUserRole('user-1', 'member');
await host.addRoomMembers(roomId, ['user-1']);

const token = await host.mintToken('user-1', { role: 'member', ttl: '15m' });

const ack = await host.sendAsUser(roomId, 'user-1', 'Hello, dotwire');
console.log(`Accepted at seq ${ack.seq}`);

const history = await host.getMessages(roomId, { asUserId: 'user-1', limit: 50 });
console.log(`History has ${history.messages.length} message(s)`);

Run it:

node first-message.mjs

What happens:

  1. setUserRole and addRoomMembers call the admin API. The SDK signs those calls as host-admin, a user that the migrations seed with the admin role.
  2. mintToken returns the RS256 JWT a browser client would use for user-1. In a real app, your token endpoint returns it to @dotwire/client.
  3. sendAsUser mints its own token for user-1 and posts the message. ack.seq is the message's position in the room's ordering.
  4. getMessages reads the room's history as user-1, who must be a room member.

Room ids are UUIDs. A room exists as soon as it has members; there is no separate create step.

Stop the stack

docker compose down

This keeps the pgdata and natsdata volumes. Add -v to delete them and start fresh.

Next steps