Full-stack architecture overview of the CoDraw collaborative drawing app (Android frontend + Spring Boot backend)
CoDraw is a real-time collaborative drawing app where two players share a canvas via WebSocket. One player creates or joins a room, and both draw simultaneously.
CoDraw/
├── app/ ← Android frontend (Kotlin / Jetpack Compose)
│ └── src/main/java/com/toan/codraw/
└── CoDrawJavaBackend/ ← Java backend (Spring Boot 4.0.3)
└── src/main/java/com/codraw/CoDraw/
CoDrawJavaBackend/)Tech stack: Spring Boot 4.0.3, JPA/Hibernate, MySQL, Redis, WebSocket, JWT (jjwt 0.12.6), Cloudinary, Lombok, Java 21, Maven.
| Package | Purpose |
|---|---|
controller/ | REST endpoints |
service/ | Business logic |
entity/ | JPA entities (MySQL tables) |
dto/ | Request/response DTOs |
handler/ | WebSocket handler |
model/ | Non-entity models (StrokeMessage) |
config/ | Redis, Cloudinary, Security, WebSocket config |
security/ | JWT filter + utils |
repository/ | Spring Data JPA repos |
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/auth/register | No | Register new user |
| POST | /api/auth/login | No | Login, returns JWT |
| GET | /api/profile/me | JWT | Get current user profile |
| PUT | /api/profile | JWT | Update display name |
| POST | /api/profile/avatar | JWT | Upload avatar (Cloudinary) |
| POST | /api/rooms/create | JWT | Create room (PUBLIC/PRIVATE) |
| POST | /api/rooms/join?code=X | JWT | Join room by code |
| GET | /api/rooms/public | No | List public WAITING rooms |
| GET | /api/rooms/{code} | JWT | Get room info |
| GET | /api/drawings/mine | JWT | Get user's completed drawings |
| POST | /api/drawings/complete | JWT | Save completed drawing |
| GET | /api/drawings/{roomCode} | JWT | Get specific completed drawing |
| GET | /api/profile/{username} | JWT | Get user profile by username (Search) |
| POST | /api/chat/{receiverUsername} | JWT | Send text message |
| POST | /api/chat/voice/{receiverUsername} | JWT | Upload voice message (Cloudinary) |
| GET | /api/chat/{friendUsername} | JWT | Get private chat history |
| GET | /api/friends, /api/friends/requests/pending, /api/friends/requests/sent | JWT | Friendship management |
| POST | /api/friends/request, /api/friends/respond | JWT | Send/Respond to friend requests |
ws://host:8080/ws/draw?roomCode=ABC123&token=<jwt>DrawingWebSocketHandler — manages rooms in-memory via ConcurrentHashMapSTROKE → saved to Redis + broadcast to othersSTROKE_PREVIEW → relay only (not persisted)CLEAR → clears Redis canvas + broadcast to allUNDO → removes last stroke of player from Redis + broadcastCOMPLETE_REQUEST/RESPONSE/FINALIZED/CANCELLED → relay only (completion flow)JOIN → server sends back stroke history from RedisCHAT → relay private chat messages (TEXT/VOICE)FRIEND_REQUEST/FRIEND_ACCEPT → relay friend system eventsusers) — id, username, email, password (BCrypt), displayName, avatarUrlrooms) — id, roomCode (6 chars), hostUsername, guestUsername, status (WAITING→PLAYING→FINISHED), roomType (PUBLIC/PRIVATE), createdAtcompleted_drawings) — id, roomCode, hostUsername, guestUsername, roomType, savedByUsername, strokeCount, strokesJson (LONGTEXT), completedAtchat_messages) — id, senderUsername, receiverUsername, content, type (TEXT/VOICE), timestampfriendships) — id, requester_id, receiver_id, status (PENDING/ACCEPTED)room:{roomCode}:strokes → List of StrokeMessage JSONCanvasStateService — addStroke, getStrokes, clearStrokes, removeLastStrokeForPlayerCLOUDINARY_CLOUD_NAME=xxx
CLOUDINARY_API_KEY=xxx
CLOUDINARY_API_SECRET=xxx
JWT config in application.properties: app.jwt.secret, app.jwt.expiration-ms
app/)Tech stack: Kotlin, Jetpack Compose (Material 3), Hilt (DI), Retrofit + OkHttp, Coil (images), WebSocket (OkHttp), Gradle (KSP).
| Package | Purpose |
|---|---|
data/local/ | SessionManager (SharedPreferences for JWT, user info, language, active room) |
data/remote/ | ApiService (Retrofit), WebSocketManager, DTOs |
data/repository/ | Repository implementations |
di/ | Hilt AppModule |
domain/model/ | Domain models |
domain/repository/ | Repository interfaces |
domain/usecase/ | Drawing event use cases |
navigation/ | NavGraph (Compose Navigation) |
presentation/ui/ | Screens (Home, Login, Register, Settings, Room, Drawing, SavedDrawing) |
presentation/viewmodel/ | ViewModels (Home, Auth, Room, Drawing, Settings, SavedDrawing) |
presentation/components/ | Reusable composables (DrawingCanvas, DrawingTool, ColorWheelPicker) |
presentation/model/ | UI model (DrawingToolMode) |
ui/theme/ | Material 3 theme + gradient colors |
| Route | Screen |
|---|---|
login | LoginScreen |
register | RegisterScreen |
home | HomeScreen |
room?code={code} | RoomScreen |
drawing/{roomCode}/{playerId}/{playerCount} | DrawingScreen |
savedDrawing/{roomCode} | SavedDrawingScreen |
settings | SettingsScreen |
GET /api/rooms/publicvalues/strings.xml (EN) and values-vi/strings.xml (VI)MediaRecorder (AudioRecorderHelper), upload to Cloudinary, play via MediaPlayer (AudioPlayerHelper)# Android (from project root)
./gradlew assembleDebug
# Backend (from CoDrawJavaBackend/)
./mvnw spring-boot:run