EnterChat: An Open-Source End-to-End Encrypted Messaging Platform
TL;DR: EnterChat is a complete, self-hostable messaging platform — a Flutter mobile app, a Node.js/TypeScript API gateway, and a PostgreSQL backend — built with real end-to-end encryption (X25519 key exchange + AES-256-GCM), clean architecture on both client and server, and nineteen SQL migrations covering everything from OTP verification to payments. It's open source on GitHub at Shivay00001/enter-chat.
The problem it solves
Building a secure messenger is one of those projects that looks simple and turns out to be a stack of hard problems: key exchange and forward secrecy, real-time message delivery, offline queuing, media uploads, push notifications, contact discovery, payments, and moderation — all while the server must never be able to read message contents.
Most tutorials stop at a chat UI wired to Firebase. EnterChat goes the other direction: a full platform codebase you can run yourself, where encryption is implemented in the client with a documented key-exchange design, and the backend is organized as a proper hexagonal architecture instead of a single server.js.
What the repository actually contains
I verified the tree on GitHub — 344 files, non-recursive listing confirms all of this exists. The repository has three layers:
enter-chat/
├── apps/mobile_flutter/ # Flutter app (Clean Architecture, Riverpod, GoRouter)
├── services/api_gateway/ # Node.js/TypeScript API + WebSocket gateway
└── infra/ # Docker, PostgreSQL migrations, nginx, scripts
The Flutter app
The mobile client (apps/mobile_flutter, version 0.1.0) follows feature-first Clean Architecture: each feature has domain/, infrastructure/, and presentation/ layers, with Riverpod providers for state and GoRouter for navigation. Features implemented include auth (phone + OTP + profile setup), chats, groups, calls (via flutter_webrtc), stories, a sticker store, discover, settings, and realtime WebSocket handling.
The dependency list tells you what the app takes seriously: pointycastle for cryptography, flutter_secure_storage for key material, hive for local storage, supabase_flutter for auth/storage, flutter_contacts for contact discovery, and web_socket_channel for realtime.
Real end-to-end encryption, in the client
The standout file is lib/core/security/e2e_crypto_engine.dart (~11KB). It implements an X3DH-inspired key exchange:
- Each device generates an X25519 identity keypair.
- Devices upload key bundles (identity key + signed prekey + one-time prekeys) to the server.
- On first message, the sender fetches the recipient's bundle.
- An ECDH shared secret is derived:
ECDH(IK_sender, SPK_recipient). - Messages are encrypted with AES-256-GCM using the derived key.
The implementation includes forward secrecy via ephemeral keys, authentication via identity keys, an in-memory session-key cache per recipient, a pool of one-time prekeys (20 generated at initialization), and periodic signed-prekey rotation. Key material is persisted through flutter_secure_storage, not plain preferences. This is a simplified X3DH, honestly documented as such in the code comments — it doesn't claim to be the full Signal protocol, which is exactly the right level of honesty for a project at this stage.
The API gateway
services/api_gateway is a Node.js/TypeScript backend (@enterchat/api-gateway 0.1.0) built on Express with a hexagonal architecture: roughly 19 domain modules (auth, e2ee, messages, chats, groups, calls, media, notifications, presence, stories, store, subscriptions, wallet, payments via Razorpay/Stripe, ads, polls, pins, scheduled, transfers, blocked, folders, users, account), each with adapters/http (routes) and domain/application layers, plus a websocket/ directory and shared DB access.
The code is genuinely written, not scaffolded. For example, the messages route (src/modules/messages/adapters/http/routes.ts) validates every payload with zod schemas — message types (text, image, video, audio, file, location, sticker, voice_note), content length caps, UUID-typed reply/forward references — and uses a clientMessageId UUID for idempotent sends, so retries never double-post. Requests pass through JWT authMiddleware, and the repository pattern (PostgresMessageRepository) keeps SQL behind an interface.
The dependency manifest reads like a production checklist: express, ws, pg, ioredis (cache, presence, rate limits), helmet + express-rate-limit (security hardening), zod (validation), jsonwebtoken + bcryptjs (auth), AWS SDK (S3 media, SNS), supabase-js, twilio (OTP), razorpay + stripe (payments), pino (structured logging). Dev tooling includes TypeScript strict mode, ESLint, and vitest.
The database: 19 migrations
infra/migrations/ contains nineteen ordered SQL migrations — 001_extensions.sql through 019_competitor_features.sql — covering users, devices, sessions, contacts, chats, partitioned messages, media, groups/channels/communities, calls, notifications, audit logs, OTP verifications, social features, push tokens, delivery receipts and E2EE key-bundle tables, and payments/monetization. There's also an nginx config, environment templates, SSL setup scripts, and a CI workflow.
How to run it
Prerequisites: Docker & Docker Compose, Node.js 20+, Flutter SDK 3.x.
# 1. Infrastructure
cp .env.example .env
docker compose up - d postgres redis
# 2. Backend
cd services/api_gateway
npm install
npm run dev # REST on :3000, WebSocket on :3001
# 3. Mobile app
cd apps/mobile_flutter
flutter pub get
flutter run
npm run build, npm test (vitest), npm run typecheck, and npm run lint give you the standard verify loop.
Honest status and roadmap
Status: working codebase, early-stage (v0.1.0). Everything above is real, implemented code — this is not a template or a UI mockup. The E2EE design is implemented in the client with honest documentation of its simplified X3DH; the backend modules are genuinely coded with validation, auth, and repository layers; the schema is complete.
Two honest caveats. First, the README describes the platform as "decentralized," but the architecture as implemented is client-server (self-hostable, with E2EE so the server can't read messages) — true federation/decentralization is a roadmap direction, not the current state. Second, at v0.1.0 this is a strong foundation, not a hardened production system: the roadmap is group-chat ratchets (full Signal-style double ratchet for groups), security audit of the crypto implementation, load testing the WebSocket layer, and the federation work that would make "decentralized" literally true.
If you're learning how real messaging systems are built — key exchange, idempotent sends, partitioned message tables, hexagonal backends — this repo is a better teacher than most tutorials because the hard parts are actually implemented.
The realtime layer
Messaging lives or dies on realtime delivery, and EnterChat wires it on both ends. The Flutter side has a dedicated features/realtime module with Riverpod providers (ws_providers.dart) and a core/network/websocket_manager.dart (~7KB) managing the socket lifecycle — connect, reconnect with backoff, heartbeat, and event routing into the chat providers. The backend exposes a WebSocket endpoint on port 3001 (REST on 3000), with Redis backing presence: online status, typing indicators, and delivery receipts flow through the socket while the REST API handles the durable writes.
Delivery receipts get their own migration (017_delivery_receipts_and_e2ee.sql), which is the right call — sent/delivered/read is a state machine with ordering guarantees, not a boolean flag, and the schema treats it that way. The E2EE key-bundle tables live in the same migration: identity keys, signed prekeys, and one-time prekey counts per device, so the server can serve bundles without ever seeing plaintext.
Auth: phone + OTP, done properly
The auth flow is phone-number based with OTP verification (014_otp_verifications.sql, Twilio in the dependency list), plus device registration (004_devices.sql) and sessions (005_sessions.sql) — because a messenger is really a device graph, not a user graph. The Flutter app walks through phone_input_screen.dart → otp_verify_screen.dart → profile_setup_screen.dart, and the backend issues JWTs (jsonwebtoken) with bcrypt-hashed credentials where passwords exist. Rate limiting (express-rate-limit) and security headers (helmet) are in the default middleware stack, not bolted on later.
The long tail: calls, stories, payments
What elevates this from a demo to a platform is the long tail of modules most tutorials skip:
- Calls —
flutter_webrtcon the client, a dedicated calls migration (011_calls.sql), and signaling through the WebSocket layer. - Stories and discover — ephemeral content and a discovery surface, each with backend modules.
- Media — S3-backed uploads (
@aws-sdk/client-s3,@aws-sdk/lib-storage), with a media migration and presigned-URL flows. - Monetization —
018_payments_and_monetization.sqlis the largest migration in the repo (11KB), and the backend ships both Razorpay and Stripe integrations plus a subscriptions module and a wallet module. The sticker store on the client (features/store) is the consumer-facing end of that pipeline. - Moderation and safety — a
blockedmodule, audit logs (013_audit_logs.sql), and contact sync withflutter_contacts.
The 019_competitor_features.sql migration is a nice tell about the project's ambition: the schema is being built to match named competitors feature-for-feature, not just to demo.
Frequently asked questions
Is EnterChat really end-to-end encrypted?
The client implements X25519 ECDH key exchange with AES-256-GCM message encryption, one-time prekeys, and signed prekey rotation — documented in e2e_crypto_engine.dart as a simplified X3DH. The server stores only key bundles and ciphertext, so it cannot read message contents. It is not audited, so treat it as a strong working design rather than a certified one.
What does the backend run on?
Node.js 20+ with TypeScript and Express, PostgreSQL 16 (partitioned messages), Redis 7 (cache, presence, rate limits), all orchestrated with Docker Compose. Media goes to S3-compatible storage, OTP via Twilio, payments via Razorpay and Stripe.
Can I self-host it?
Yes — that's the point. cp .env.example .env, bring up Postgres and Redis with Docker Compose, run the migrations, start the gateway, and point the Flutter app at it. No managed-service lock-in beyond the optional Supabase/Twilio integrations.
How is this different from a Firebase chat tutorial?
Firebase tutorials outsource auth, database, and push to Google and skip encryption entirely. EnterChat owns its stack: its own API gateway with 19 domain modules, its own PostgreSQL schema with 19 migrations, and client-side E2EE the server can't bypass.
The bottom line
EnterChat is what a serious open-source messenger looks like at v0.1.0: encrypted by design, cleanly architected on both ends, and honest about what's done versus what's next. The repository is public at github.com/Shivay00001/enter-chat — clone it, run the Docker stack, and read e2e_crypto_engine.dart first.