Skip to content

Commit 8608c15

Browse files
authored
Add blog post on building a chat app with Go Micro
Added a blog post detailing the development of a full chat app using Go Micro, outlining features, architecture, and lessons learned.
1 parent 9823f6d commit 8608c15

1 file changed

Lines changed: 191 additions & 0 deletions

File tree

  • internal/website/blog

internal/website/blog/8.md

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
---
2+
layout: blog
3+
title: "We Built a Full Chat App in a Day — Here's How"
4+
permalink: /blog/8
5+
description: "How we defined 13 services, built a production-grade chat app, and shipped it as a single binary using Go Micro's modular monolith pattern."
6+
---
7+
8+
# We Built a Full Chat App in a Day — Here's How
9+
10+
*March 7, 2026 — By the Go Micro Team*
11+
12+
We set out to answer a question: how fast can you go from a feature list to a working, production-grade application using Go Micro? The answer surprised us.
13+
14+
We built **Micro Chat** — a full-featured chat platform with real-time messaging, AI integration, SSO, webhooks, full-text search, file uploads, and more. Thirteen domain services. One binary. One afternoon.
15+
16+
Here's how we did it, and what it says about Go Micro's role in modern application architecture.
17+
18+
## The Feature List
19+
20+
We started with a list. Not a design doc, not a spec — a list of things a real chat app needs:
21+
22+
- User registration, authentication, profiles, and roles
23+
- Channels and direct messages
24+
- Real-time messaging with WebSockets — typing indicators, read receipts, reactions, edit/delete
25+
- User groups with membership and permissions
26+
- Threaded replies on messages
27+
- Full-text search across all messages
28+
- Invite links with expiration and usage limits
29+
- File uploads and message attachments
30+
- Data export (JSON and CSV)
31+
- Outbound webhooks with event subscriptions and HMAC signing
32+
- AI assistant powered by Claude with tool use and vision
33+
- MCP server exposing tools over JSON-RPC 2.0
34+
- SSO/OIDC with external identity providers
35+
- Audit logging for admin and security events
36+
37+
That's a lot. In a traditional microservices setup, you'd spend a week just on the infrastructure — service mesh, message broker, API gateway, deploy pipelines, Kubernetes manifests. We spent zero time on that.
38+
39+
## One Service Per Domain
40+
41+
Each feature maps to a service. Each service is a Go package under `service/`:
42+
43+
```
44+
service/
45+
├── agent/ # Claude AI integration
46+
├── audit/ # Audit logging
47+
├── chats/ # Channels, DMs, messages, WebSocket hub
48+
├── export/ # Data export
49+
├── files/ # File uploads
50+
├── groups/ # User groups
51+
├── invites/ # Invite links
52+
├── mcp/ # Model Context Protocol server
53+
├── search/ # Full-text search (FTS5)
54+
├── sso/ # SSO/OIDC
55+
├── threads/ # Threaded replies
56+
├── users/ # Auth, profiles, roles
57+
└── webhooks/ # Outbound webhooks
58+
```
59+
60+
Every service follows the same pattern: a struct, a constructor, and methods. No framework magic, no code generation, no annotations. Just Go.
61+
62+
```go
63+
// service/search/search.go
64+
type Service struct{}
65+
66+
func NewService() *Service { return &Service{} }
67+
68+
func (s *Service) Search(filter SearchFilter) ([]SearchResult, int, error) {
69+
// FTS5 query against SQLite
70+
}
71+
```
72+
73+
The simplicity is the point. A new team member can read any service top to bottom in five minutes.
74+
75+
## Go Micro Ties It Together
76+
77+
Here's where Go Micro earns its keep. Each domain is declared as a `micro.Service`, and they're all composed into a single runnable group:
78+
79+
```go
80+
gateway := micro.New("gateway",
81+
micro.BeforeStart(func() error {
82+
database.Init()
83+
auth.Init()
84+
searchSvc.InitFTS()
85+
go wsHub.Run()
86+
go httpServer.ListenAndServe()
87+
return nil
88+
}),
89+
micro.AfterStop(func() error {
90+
httpServer.Close()
91+
database.Close()
92+
return nil
93+
}),
94+
)
95+
96+
usersSvc := micro.New("users")
97+
chatsSvc := micro.New("chats")
98+
groupsSvc := micro.New("groups")
99+
agentSvc := micro.New("agent")
100+
mcpSvc := micro.New("mcp")
101+
searchSvc := micro.New("search")
102+
threadsSvc := micro.New("threads")
103+
webhooksSvc := micro.New("webhooks")
104+
ssoSvc := micro.New("sso")
105+
auditSvc := micro.New("audit")
106+
107+
g := micro.NewGroup(gateway, usersSvc, chatsSvc, groupsSvc, agentSvc,
108+
mcpSvc, searchSvc, threadsSvc, webhooksSvc, ssoSvc, auditSvc)
109+
110+
g.Run()
111+
```
112+
113+
`micro.NewGroup` handles lifecycle management — ordered startup, signal handling, graceful shutdown. You declare your services, compose them, and run. That's the entire `main.go`.
114+
115+
The startup banner tells the story:
116+
117+
```
118+
Micro Chat - Modular Monolith (go-micro.dev/v5)
119+
─────────────────────────────────────────
120+
Server: http://localhost:8080
121+
Claude AI: Configured (with tools)
122+
MCP: Enabled
123+
SSO/OIDC: Enabled
124+
─────────────────────────────────────────
125+
```
126+
127+
## Why a Modular Monolith?
128+
129+
We could have built this as 13 separate microservices from the start. We deliberately didn't. Here's why:
130+
131+
**Velocity.** A single binary means `go build && ./server`. No Docker Compose, no service discovery config, no inter-service networking. We went from zero to a working app in hours, not days.
132+
133+
**Simplicity.** One database (SQLite), one process, one deploy. You can run this on a $5 VPS or your laptop. The operational overhead is effectively zero.
134+
135+
**Clean boundaries anyway.** The service packages don't know about each other. `service/webhooks` has no idea `service/search` exists. The API layer composes them, but the domains are fully isolated. We get the architectural benefits of microservices without the infrastructure tax.
136+
137+
**Cheap iteration.** Want to add audit logging? Create `service/audit`, add a few methods, wire it into the API handler. The cost of a new service is one package and two lines in `main.go`. We added SSO/OIDC support the same way — the pattern is always identical.
138+
139+
## How It Breaks Out
140+
141+
This is the real power of the modular monolith: it's not a dead end, it's a starting point. When scale or team structure demands it, the extraction path is clear.
142+
143+
**Step 1: The interface already exists.** Every service has a clean method-based API. `search.Service.Search(filter)` doesn't change whether it's an in-process call or an RPC endpoint.
144+
145+
**Step 2: Go Micro makes it native.** Replace the in-process call with a `micro.Client` call. The service moves to its own binary, registers with service discovery, and the caller barely changes.
146+
147+
**Step 3: Extract incrementally.** Maybe `agent` (the AI service) needs its own deployment because it's making expensive API calls. Pull it out. Everything else stays in the monolith. You don't have to go all-or-nothing.
148+
149+
**Step 4: The database splits last.** Each service already accesses only its own tables — users has `users`, search has `messages_fts`, SSO has `oidc_providers` and `oidc_users`. When you extract a service, you move its tables to a dedicated database. The code barely changes.
150+
151+
The progression looks like this:
152+
153+
```
154+
Day 1: Modular monolith (single binary, SQLite)
155+
Month 3: Extract agent service (expensive AI calls)
156+
Month 6: Add message broker for webhooks and audit (async events)
157+
Year 1: Split database per service, full microservices where needed
158+
```
159+
160+
You grow into microservices. You don't start there.
161+
162+
## The Stack
163+
164+
For the curious:
165+
166+
- **Go Micro v5** — service lifecycle, composition, and the future extraction path
167+
- **SQLite + FTS5** — embedded database with full-text search (swap for Postgres when ready)
168+
- **Gorilla WebSocket** — real-time messaging with typing indicators and read receipts
169+
- **Claude API** — AI agent with tool use, vision, and streaming
170+
- **MCP (JSON-RPC 2.0)** — Model Context Protocol for AI tool integration
171+
- **Go standard library**`net/http`, `crypto`, `encoding/json` — minimal dependencies
172+
173+
Total external dependencies: a handful. Total services: 13. Total binaries: 1.
174+
175+
## What We Learned
176+
177+
**Define services early, split them late.** Drawing domain boundaries at the start costs nothing. Deploying 13 separate services on day one costs everything.
178+
179+
**Go Micro's group primitive is underrated.** `micro.NewGroup` is a small API with a big impact. It turns "a bunch of services" into "a managed application" with lifecycle hooks, signal handling, and graceful shutdown.
180+
181+
**The modular monolith is not a compromise.** It's often the right architecture for most of a product's lifetime. You get the modularity of microservices, the simplicity of a monolith, and a clear path forward when you need to break things apart.
182+
183+
**AI integration is just another service.** The `agent` service wraps the Claude API. The `mcp` service exposes tools over JSON-RPC. They're not special — they're domain services with the same constructor-and-methods pattern as everything else. That's how it should be.
184+
185+
## Try It Yourself
186+
187+
The full source is at [github.com/micro/chat](https://github.com/micro/chat). Clone it, run `go build ./cmd/server && ./server`, and you have a working chat app with 13 services in a single binary.
188+
189+
Then start thinking about which service you'd extract first — and notice how easy the answer is, because the boundaries are already there.
190+
191+
That's the modular monolith. That's Go Micro.

0 commit comments

Comments
 (0)