Skip to content
rifleDB
Docs: Quickstart

Quickstart

Run the server, connect a React app, read the same value with curl, and lock a folder to its owner. Six steps, all on your machine.

Install the SDK

shell
npm install rifledb

npm release coming. Until rifledb is published, apps inside the rifleDB workspace depend on it with "rifledb": "workspace:*" instead. Setup has the details.

Run the server

The server is one Node.js process with Redis behind it. Start both from the workspace root:

shell
# in the rifleDB workspace
pnpm install
pnpm redis:dev                      # Redis on port 6380 (another terminal)
REDIS_URL=redis://localhost:6380 pnpm -F @rifledb/server dev   # rifleDB on http://localhost:4000
# without REDIS_URL the server keeps everything in memory (gone on restart)

Folders are declared in server/rifle.config.tsx. It is JSX, but nothing renders: each element is plain data the server loads at boot. Add a public folder that anyone may write to, next to the folders it already has:

server/rifle.config.tsx
export default (
  <Rifle>
    <Users registration='open' />
    {/* … the folders it ships with: system (admin), playground, demo … */}
    <Folder path='polls' mode='public' publicWrite />
  </Rifle>
)

Create a client

createRifle opens one WebSocket to the server. With identity: 'auto' each browser gets an Ed25519 device key, kept in IndexedDB and non-extractable wherever the browser supports Ed25519 in WebCrypto. There are no passwords to store.

lib/rifle.ts
import { createRifle } from 'rifledb'

// connect: false keeps server rendering offline; <RifleProvider> connects it in the browser.
export const rifle = createRifle({ url: 'http://localhost:4000', identity: 'auto', connect: false })

Wrap your app once with the provider. It opens the socket in the browser, never during server rendering:

app/providers.tsx
'use client'
import type { ReactNode } from 'react'
import { RifleProvider } from 'rifledb/react'
import { rifle } from '@/lib/rifle'

export function Providers({ children }: { children: ReactNode }) {
  return <RifleProvider client={rifle}>{children}</RifleProvider>
}

Read and write with useRifle

useRifle(path) subscribes to a path and returns [value, api, { rev, pending, status }]. Writes show up in your tab at once and are rolled back if the server refuses them.

app/votes.tsx
'use client'
import { useRifle } from 'rifledb/react'

export function Votes() {
  const [votes, api, { pending }] = useRifle('polls/launch/votes')
  return (
    <button onClick={() => api.inc()}>
      {votes ?? 0} votes{pending ? ' (saving)' : ''}
    </button>
  )
}

Open the page in two windows and click. Both counters move together.

Same data over REST

Every path is also a URL under /v1/. Reads return an ETag; send it back as If-Match to write only if nothing changed in between. Errors come back as application/problem+json.

shell
curl http://localhost:4000/v1/polls/launch/votes
# {"v":2,"r":7}, with ETag: "<epoch>:<rev>"

curl -X PATCH http://localhost:4000/v1/polls/launch/votes \
  -H 'content-type: application/rifle-op+json' \
  -d '{"op":"inc","args":{"by":5}}'

curl -N 'http://localhost:4000/v1/polls/launch/votes?stream=1'
# every change, as server-sent events

Your first private folder

Every user gets a private folder at users/<uid>. By default only that user’s devices can read or write notes in it.

app/notes.ts
await rifle.identity.ensure()
const uid = rifle.identity.uid

await rifle.set(`users/${uid}/notes/first`, 'Only my devices can read this.')

Anyone else is turned away, including a curl request without a signature:

shell
curl -i http://localhost:4000/v1/users/u_8fQ2xV/notes/first
# HTTP/1.1 401 Unauthorized
# content-type: application/problem+json

To work on a folder with other people, declare it private, for example <Folder path='boards' mode='private' />, and give access with grants, folder keys or claim invites: Access control explains how.