Skip to content

Project structure ​

Refract.Chat is one repository with several packages. Each has its own package.json and yarn.lock, and its commands run inside it (yarn --cwd <package> ...).

PackageWhat it is
sdk/@refractchat/sdk: the REST client, socket manager, shared types and logic both sides need
backend/The server: Node.js, Express, Socket.IO and Sequelize, in TypeScript run with tsx
frontend/The web app: React, Vite and Tailwind
electron/The desktop app: a shell around the web app, with a preload bridge for native features
mobile/The mobile app: Expo and React Native, using the SDK directly
relay/The push relay that forwards mobile notifications to FCM and APNs
docker/Compose files and example configs for deployment
website/This site

The SDK is the contract ​

Everything the server and the apps agree on lives in sdk/src/types/: request and response shapes, socket events, permissions and preferences. Change a contract there first, then the backend that produces it, then the apps that use it.

  • No second copy. Import shared types from the SDK, never redefine them. Types only the backend needs go in backend/types/.
  • No framework code. The SDK runs in browsers and Node.js, so it stays free of React, Express and Sequelize. Logic both sides need, such as permission maths, mention parsing and notification rules, belongs in it rather than duplicated.
  • The apps call the API through it. The web app uses import { api } from "@refractchat/sdk", never raw fetch calls.
  • Permission bits are stored in the database. Only ever append new ones; never reorder or reuse a bit.

The SDK is ESM, so its relative imports end in .js. Yarn copies it into each package, so after changing it run ./sync-sdk.sh (see Getting started).

Backend ​

A request goes route → middleware → controller → service → model:

  • Routes (src/routes/api/, mounted under /api/v1) compose middleware for sign-in, rate limits, membership and permissions.
  • Controllers (src/controllers/) validate the request, call services, reply, and emit socket events.
  • Services (src/services/) hold the database access and domain logic, with no Express in them. They return null for not found, and use transactions for multi-step writes.
  • Models, migrations and seeders are plain JavaScript in models/, migrations/ and seeders/. Development and tests use SQLite; production uses MariaDB.

Permissions are deny-first bitmasks, resolved and cached in Redis. Every new endpoint needs the right middleware and checks of its own: a database lookup succeeding doesn't mean the caller may see the result. Redis also holds live state: presence, read positions, voice grants and the job queues.

Web app ​

  • @/ points at src/. UI primitives are in src/components/ui/, icons come from lucide-react.
  • Rendering lives in components/, reusable React behaviour in hooks/, plain helpers in lib/.
  • Style with Tailwind and the theme's tokens (bg-card, text-foreground, border-border), not hard-coded colours. The palette is CSS variables in src/styles/theme.css, which people's own themes override.
  • The providers in App.tsx, outermost first: AuthProvider, SocketProvider, PreferencesProvider, NotificationSettingsProvider, DataProvider, ThemeProvider, LivekitProvider. App-wide side effects are small *Bridge components inside them.
  • Socket subscriptions go through the SDK's socket manager, and each effect returns its cleanup function.

Desktop app ​

The web app in the desktop shell never touches Node.js. Native features are exposed only through electron/preload.js, typed in frontend/src/electron.d.ts. The packaged app is served from its own origin, refract://app.

Further reading ​

backend/docs/ and docs/ in the repository hold the detailed design notes: the permission system, caching, the service layer, sockets, LiveKit, federation and more.

Released under the GNU Affero General Public License v3.0.