English | 中文 | 日本語 | Français | Deutsch | Español | 한국어 | Русский
An instant messaging (IM) application — Go backend powering multiple frontends.
Supported clients, by priority:
- 🌐 React Web — full-featured SPA
- 🖥 macOS — native SwiftUI app
- 📱 iOS — native SwiftUI app
- 🤖 Android — (coming soon)
| Directory | Description |
|---|---|
server/ |
Go backend (REST API + WebSocket) |
server/web/ |
Web frontend (React + TypeScript + Vite) |
client/ |
macOS / iOS client (Swift + SwiftUI) |
deps/ |
Local dependencies |
bin/ |
Build artifacts |
- Backend: Go 1.26, PostgreSQL, Redis, JWT, WebSocket
- Web Frontend: React 19, TypeScript, Vite, Tailwind CSS 4, Zustand
- Native Client: Swift 6.3.2, SwiftUI, iOS 18+ / macOS 15+
- Go 1.26+
- Node.js 22+
- Swift 6.3.2+ (macOS client)
- Xcode 16+ (iOS client)
- PostgreSQL 16+
- Redis 7+
- Docker (optional)
# 1. Prepare config file
cp server/config/config.example.yaml server/config/config.yaml
# Edit config.yaml as needed
# 2. Start all services (PostgreSQL + Redis + App)
docker compose up -d
# 3. View logs
docker compose logs -f app
# 4. Stop
docker compose downThis starts three containers:
| Service | Image | Port |
|---|---|---|
| postgres | postgres:16-alpine |
5432 |
| redis | redis:7-alpine |
6379 |
| app | local build | 8080 |
Persistent data is stored in Docker volumes.
Edit server/config/config.yaml:
postgres:
dsn: "postgres://user:pass@your-pg-host:5432/imdb?sslmode=require"
redis:
addr: "your-redis-host:6379"
password: "your-password"Then start only the app container:
docker compose up -d app# Full build (Web frontend + Go backend)
docker build -t ziziphus:latest .
# Go backend only (requires npm run build first)
docker build -t ziziphus:latest -f server/Dockerfile server/docker run -d \
--name ziziphus \
-p 8080:8080 \
-v ./server/config/config.yaml:/app/config/config.yaml:ro \
ziziphus:latestOn every push to main, CI builds and pushes to GitHub Container Registry:
docker pull ghcr.io/dolphinZzv/ziziphus:latestcp server/config/config.example.yaml server/config/config.yaml
# Edit config.yaml — at minimum configure PostgreSQL DSN and JWT secretKey configuration:
| Field | Description | Default |
|---|---|---|
server.port |
HTTP listen port | 8080 |
postgres.dsn |
PostgreSQL connection string | postgres://postgres@localhost:5432/imdb?sslmode=disable |
redis.addr |
Redis address | localhost:6379 |
jwt.secret |
JWT signing key (change in production) | change-me-to-a-random-secret |
jwt.expire_hours |
Access token lifetime | 1 hour |
jwt.refresh_expire_hours |
Refresh token lifetime | 168 hours (7 days) |
ratelimit.msg_per_sec |
Message rate limit | 30 msg/sec |
smtp.* |
SMTP email service (for verification codes) | — |
# Install Go dependencies
cd server && go mod download
# Build and start (auto-compiles web frontend + starts server)
make server
# Start pre-built binary only
bin/ziziphus -c server/config/config.yaml
# Stop
make server-stopAPI server listens on http://localhost:8080.
cd server/web
npm install
npm run devDev server at http://localhost:5173, API proxied to http://localhost:8080.
Production build:
cd server/web
npm run build
# Output is auto-copied to server/internal/webembed/dist/
# Then compile Go binary to embed the frontend# Ensure local dependencies are installed
# deps/textual/ is a local Swift package
# Build & launch macOS client
make macosFirst run auto-generates Info.plist and opens the app. To rebuild:
make macos-stop
make macos# 1) Edit .env, set IOS_DEVICE to your device name
# 2) Generate Xcode project
make xcodegen
# 3) Build and deploy to device
make ios-deploy
# Or open client/IMApp.xcodeproj in Xcode
# Select IMApp-iOS scheme, target your device, Cmd+RThis project uses real devices only (no simulators — see
CLAUDE.md).
Migrations run automatically on startup (db.RunMigrations). Scripts are in:
server/internal/storage/db/migrations/
To run manually:
psql -d imdb -f server/internal/storage/db/migrations/001_initial.sql# Web frontend lint
make lint-web
# Go backend lint
make lint-server
# All lint
make lintEdit .env file:
| Variable | Description |
|---|---|
SSH_HOST |
Server address |
SSH_PORT |
SSH port |
DEPLOY_PORT |
Service port |
DEPLOY_USER |
SSH user |
DEPLOY_PATH |
Deploy path |
DEPLOY_DSN |
Production database DSN |
Then run:
make deploy # Build & deploy (systemd service)
make deploy-status # Check service status
make deploy-logs # View service logs┌─────────────────────────────────────────────────┐
│ Web Frontend (React + Vite) │
│ server/web/ │
└──────────────┬──────────────────────────────────┘
│ HTTP / WebSocket
┌──────────────▼──────────────────────────────────┐
│ Go Backend (ziziphus) │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ API │ │ WebSocket │ │ Message Route │ │
│ └──────────┘ └──────────┘ └──────────────────┘ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ Session │ │ Gateway │ │ File Storage │ │
│ └──────────┘ └──────────┘ └──────────────────┘ │
│ ┌──────────┐ ┌──────────┐ │
│ │PostgreSQL│ │ Redis │ │
│ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ macOS / iOS Client (Swift + SwiftUI) │
│ client/ │
└─────────────────────────────────────────────────┘
Ziziphus supports 8 languages across frontend and backend:
| Code | Language | Backend Constant | Frontend File |
|---|---|---|---|
zh |
Chinese (Simplified) | LangZH |
zh.json |
en |
English | LangEN |
en.json |
ja |
Japanese | LangJA |
ja.json |
fr |
French | LangFR |
fr.json |
de |
German | LangDE |
de.json |
es |
Spanish | LangES |
es.json |
ko |
Korean | LangKO |
ko.json |
ru |
Russian | LangRU |
ru.json |
Uses i18next with react-i18next. Language preference is stored in localStorage under key ziziphus_language. Translation files live in server/web/src/i18n/{lang}.json. Non-Chinese bundles are lazy-loaded on demand to keep the initial bundle small.
The frontend sends the selected language to the backend via the X-Language HTTP header on every request.
The server/pkg/i18n/ package provides:
- Language constants (
LangZH,LangEN, ...) - ParseLang() — Accepts browser locale codes (e.g.
zh-CN,en-US,ja-JP) and normalizes to a supported Lang constant - DetectLanguage() — Reads
X-Languageheader (frontend preference) with fallback toAccept-Languageheader, then toLangZH - T() / TWithLang() — Template-style string translation with positional parameters (
{0},{1}) - HTTP Middleware — Detects language per-request and stores it in the request context
Translation messages are split per language file:
pkg/i18n/messages.go # Message key declarations + registerLang helper
pkg/i18n/{zh,en,ja,fr,de,es,ko,ru}.go # Each language's translations
Email verification and password reset templates support all 8 languages as well. Templates are embedded at compile time via //go:embed and live in:
internal/auth/email_templates/
verify_code_{lang}.html
reset_password_{lang}.html
Backend:
- Add a new
LangXXconstant inpkg/i18n/i18n.go - Add locale mapping in
ParseLang() - Create a new file
pkg/i18n/{lang}.gowithinit() + registerLang()for all message keys - Add
langToFrontendCode()mapping ininternal/api/language.go
Frontend:
- Create
server/web/src/i18n/{lang}.jsonwith translated key-value pairs - Add the language option to the frontend settings/auth language selector
- Update
Languagetype andresolveAutoLang()in the UI store
Email Templates:
- Copy an existing template (e.g.,
verify_code_en.html→verify_code_{lang}.html) - Translate the text content
- Add
//go:embeddirective and register inemailTemplatesmap ininternal/auth/mailer.go - Add subject translations
See .env.example files:
server/config/config.example.yaml— Backend config templateserver/web/.env.example— Web frontend env template- Root
.env— Deploy parameters (gitignored)