跪拜 Guibai
← Back to the summary

CaoIM: Self-Hosted Chat on a Single Flutter Codebase

CaoIM is an instant messaging suite I built for people who want to deploy chat themselves. Server side is Java, client side is Flutter. Android, iOS, Windows, macOS and Web share the same client code.

01-conversation-list.png

02-private-chat.png

What the client looks like

On desktop it opens as three columns. Conversations on the left, chat in the middle, and when viewing a group the profile is on the right. On mobile, there are three bottom tabs: Messages, Contacts, Me. The browser shows the same interface, not a separate web version.

Supported message types include text, emoji, images, camera capture, voice, files, and short video. Messages can be quoted, forwarded, recalled, and favorited. Conversations support pinning, do-not-disturb, and unread counts. When you stay in a chat, that conversation stops accumulating unread numbers.

In contacts, you can handle friend requests, edit nicknames, block users, search users, or scan QR codes to add friends or join groups. Group info includes name, announcement, admins, muting, join verification, and group nickname. Member avatars are combined together.

In the top right of a private chat you can start voice or video, currently only one-on-one. There's a ringtone; video supports local preview and switching camera. Group calls are not implemented. The example only sets public STUN; LAN usually works, but on public networks you often need to add TURN yourself.

Dark mode is also supported. When the app is in the background, new message notifications can pop up.

02-chat.png

06-emoji.png

Server components

The repositories are split so each can be viewed separately:

Text goes over the long connection. Images and files upload to app-server first, and the message only stores the URL. Storage can be a local directory or S3-compatible object storage.

After login you get two tokens. token is used to access app-server, imToken is used to connect to im-server. The backend calls the message service's admin interface through an internal token, without modifying business tables directly.

If the other party is online, messages push immediately; if offline, they wait until the user comes online to fetch. Read receipts return to the sender; in groups, you see the number of readers.

02-group-info.png

How to run

Requirements: JDK 17, Maven 3.6, MySQL 5.7 (utf8mb4). Flutter 3, Dart 3.11 or above. The admin page needs Node.js 18.

mysql -u root -p < im-server/sql/schema.sql
mysql -u root -p < app-server/sql/schema.sql

cd im-server && mvn clean install -DskipTests
cd im-server/im-boot && mvn spring-boot:run
cd app-server && mvn spring-boot:run
cd flutter-demo && flutter pub get && flutter run

In Windows PowerShell, don't use &&; run them in separate directories.

Default admin account is admin / admin123, page at http://localhost:5173. Before going live, change this password and also replace the JWT secret. For phone debugging, set the server address to your computer's LAN IP; localhost on a phone points to the phone itself.

Voice-to-text, group calls, payments, and mini programs are not in the current code. One-on-one call media doesn't go through the server; the server only forwards signaling.

Code is in the cao-im organization on GitHub and Gitee:

Other repositories can be reached from the table in the README. It's free for learning and personal use, no need to contact me. Commercial use is also free, but you need to obtain authorization first.