A real-time messaging backend built in Go. Supports multiple server instances communicating across each other using Redis pub/sub — a message sent from a user on instance 1 is delivered to users on instance 2.
Client A Client B
│ │
Server 1 Server 2
│ │
└──── Redis ──────┘
(pub/sub bridge)
│
PostgreSQL
(shared storage)
When a user sends a message:
- The receiving server publishes it to Redis channel
slack:messages - Every server subscribed to that channel receives it
- Each server broadcasts locally to its connected clients in that room
This means users connected to different server instances can talk in the same room seamlessly.
| Layer | Technology |
|---|---|
| Language | Go 1.22+ |
| WebSocket | Socket.IO (go-socket.io v1.7.0) |
| Pub/Sub | Redis (redigo) |
| Database | PostgreSQL (lib/pq) |
| Config | .env (godotenv) |
- Go 1.22+
- PostgreSQL running locally
- Redis running locally
1. Clone and install dependencies:
git clone https://github.com/harsh-patel/slack
cd slack
go mod tidy2. Create .env:
PORT=7001
DATABASE_URL=postgres://user:password@localhost:5432/slack?sslmode=disable
REDIS_URL=localhost:63793. Run:
go run ./cmd/main.goTables (users, rooms, room_members) are created automatically on startup.
# Terminal 1
PORT=7001 go run ./cmd/main.go
# Terminal 2 (PowerShell)
$env:PORT="7002"; go run ./cmd/main.goBoth instances connect to the same PostgreSQL and Redis — rooms and users are shared, and messages flow across instances.
A test UI is available at http://localhost:7001/ after starting the server. It lets you create users, create rooms, add members, and test the full Socket.IO flow without any external tooling.
Note: The test client uses Socket.IO v2.4.0. The server (
go-socket.io v1.7.0) uses the engine.io v3 wire protocol, which is incompatible with Socket.IO v3/v4 clients.
| Method | Endpoint | Body | Description |
|---|---|---|---|
| POST | /users |
{"id","username","email"} |
Create user |
| GET | /users/{id} |
— | Get user by ID |
| GET | /users |
— | Get all users |
| Method | Endpoint | Body | Description |
|---|---|---|---|
| POST | /rooms |
{"id","name"} |
Create room |
| GET | /rooms/{id} |
— | Get room + members |
| POST | /rooms/dm |
{"userID1","userID2"} |
Create DM room |
| POST | /rooms/{id}/members |
{"userID"} |
Add member |
| DELETE | /rooms/{id}/members/{userID} |
— | Remove member |
Connect with a valid userID (must exist in DB):
ws://localhost:7001/socket.io/?userID=user-1
Use a Socket.IO v2.x client — v3/v4 clients use a different wire protocol and will fail to connect.
| Event | Payload | Description |
|---|---|---|
join |
"room-general" |
Join a room (must be a DB member) |
message |
{"roomID":"...","text":"..."} |
Send message to a room |
leave |
"room-general" |
Leave a room |
get_members |
"room-general" |
Request live socket members |
| Event | Payload | Description |
|---|---|---|
message |
{"roomID","text","senderID"} |
Incoming message |
user_joined |
"userID" |
User joined the room |
user_left |
"userID" |
User left the room |
room_members |
{"roomID","members":[...]} |
Response to get_members |
room_error |
"reason" |
Join/message rejected |
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#4a90d9', 'primaryTextColor': '#ffffff', 'primaryBorderColor': '#2c6fad', 'secondaryColor': '#e8453c', 'secondaryTextColor': '#ffffff', 'secondaryBorderColor': '#b03030', 'tertiaryColor': '#2ecc71', 'tertiaryTextColor': '#ffffff', 'tertiaryBorderColor': '#1a9e54', 'noteBkgColor': '#fff5ad', 'noteTextColor': '#333333', 'sequenceNumberColor': '#ffffff', 'actorTextColor': '#ffffff', 'actorBkg': '#4a90d9', 'actorBorder': '#2c6fad'}}}%%
sequenceDiagram
participant u1 as u1
participant Server1 as Server 1
participant Redis as Redis
participant Server2 as Server 2
participant u2 as u2
u1->>Server1: emit "message" {roomID, text}
Server1->>Redis: PUBLISH slack:messages
Redis-->>Server1: deliver
Redis-->>Server2: deliver
Server1->>u1: BroadcastToRoom ✓
Server2->>u2: BroadcastToRoom ✓
- Connection rejected if
userIDis missing or not found in DB joinrejected if room does not exist in DB or user is not a membermessagerejected if sender has not joined the room via socket
slack/
├── cmd/
│ └── main.go # Entry point
├── internal/
│ ├── db/
│ │ └── db.go # PostgreSQL connection + migrations
│ ├── domain/
│ │ ├── user.go
│ │ └── room.go
│ ├── repository/
│ │ ├── user_repo.go # UserRepository interface
│ │ ├── room_repo.go # RoomRepository interface
│ │ ├── postgres_user_repo.go
│ │ └── postgres_room_repo.go
│ ├── services/
│ │ ├── user_service.go
│ │ └── room_service.go
│ └── handler/
│ ├── http.go # REST handlers
│ ├── ws.go # Socket.IO server + room/connection tracking
│ └── redis.go # Redis pub/sub
├── static/
│ └── test.html # Browser-based test client (Socket.IO v2)
└── .env