사용 사례

/

신속한 개발

기능성 프로토타입을 빠르게 생성

모델

클로드 3.7 소네트

React, Node.js, Socket.io를 사용하여 기본 룸, 메시징 및 사용자 프레즌스를 사용하여 실시간 채팅 앱의 프로토타입을 만들어 보세요.

쿼리 사용해보기
클로드 3.7 소네트
/
코더

리액트, Node.js, Socket.io를 사용한 실시간 채팅 앱 프로토타입

채팅방 지원, 메시지, 사용자 현재 상태 표시기가 포함된 기본적인 실시간 채팅 애플리케이션을 만드는 과정을 안내해 드리겠습니다.이를 프런트엔드와 백엔드 구성 요소로 세분화해 보겠습니다.

프로젝트 구조

1chat-app/
2├── client/             # React frontend
3│   ├── public/
4│   └── src/
5├── server/             # Node.js backend
6└── package.json

백엔드 (Node.js + 소켓.io)

1.서버 설정

먼저 서버 디렉터리를 만들고 다음을 초기화합니다.

1mkdir -p chat-app/server
2cd chat-app/server
3npm init -y
4npm install express socket.io cors

2.만들기 server.js

1const express = require('express');
2const http = require('http');
3const socketIo = require('socket.io');
4const cors = require('cors');
5
6const app = express();
7app.use(cors());
8
9const server = http.createServer(app);
10const io = socketIo(server, {
11  cors: {
12    origin: "http://localhost:3000",
13    methods: ["GET", "POST"]
14  }
15});
16
17// Track users in each room
18const rooms = {};
19
20io.on('connection', (socket) => {
21  console.log('New client connected:', socket.id);
22  
23  // User joins a room
24  socket.on('join_room', ({ username, room }) => {
25    socket.join(room);
26    
27    // Initialize room if it doesn't exist
28    if (!rooms[room]) {
29      rooms[room] = [];
30    }
31    
32    // Add user to room
33    rooms[room].push({ id: socket.id, username });
34    
35    // Broadcast to room that user has joined
36    io.to(room).emit('user_joined', { 
37      username, 
38      users: rooms[room],
39      message: `${username} has joined the room.` 
40    });
41    
42    // Send room history to the new user
43    socket.emit('room_users', rooms[room]);
44    
45    console.log(`${username} joined room: ${room}`);
46  });
47  
48  // Handle messages
49  socket.on('send_message', ({ room, username, message }) => {
50    const timestamp = new Date().toISOString();
51    io.to(room).emit('receive_message', { 
52      username, 
53      message, 
54      timestamp 
55    });
56  });
57  
58  // Handle user typing
59  socket.on('typing', ({ room, username }) => {
60    socket.to(room).emit('user_typing', { username });
61  });
62  
63  // Handle user disconnect
64  socket.on('disconnect', () => {
65    console.log('Client disconnected:', socket.id);
66    
67    // Find which room the user was in
68    for (const room in rooms) {
69      const userIndex = rooms[room].findIndex(user => user.id === socket.id);
70      
71      if (userIndex !== -1) {
72        const username = rooms[room][userIndex].username;
73        
74        // Remove user from room
75        rooms[room].splice(userIndex, 1);
76        
77        // Notify room that user has left
78        io.to(room).emit('user_left', { 
79          username, 
80          users: rooms[room],
81          message: `${username} has left the room.` 
82        });
83        
84        break;
85      }
86    }
87  });
88});
89
90const PORT = process.env.PORT || 4000;
91server.listen(PORT, () => {
92  console.log(`Server running on port ${PORT}`);
93});

프론트엔드 (리액트+ 소켓.IO 클라이언트)

1.리액트 앱 설정

1npx create-react-app chat-app/client
2cd chat-app/client
3npm install socket.io-client

2.컴포넌트 생성

src/App.js

1import React, { useState } from 'react';
2import './App.css';
3import JoinRoom from './components/JoinRoom';
4import ChatRoom from './components/ChatRoom';
5
6function App() {
7  const [joined, setJoined] = useState(false);
8  const [userData, setUserData] = useState({ username: '', room: '' });
9
10  const handleJoinRoom = (username, room) => {
11    setUserData({ username, room });
12    setJoined(true);
13  };
14
15  return (
16    <div className="App">
17      <header className="App-header">
18        <h1>Real-Time Chat App</h1>
19      </header>
20      <main>
21        {!joined ? (
22          <JoinRoom onJoin={handleJoinRoom} />
23        ) : (
24          <ChatRoom username={userData.username} room={userData.room} />
25        )}
26      </main>
27    </div>
28  );
29}
30
31export default App;

src/components/JoinRoom.js

1import React, { useState } from 'react';
2
3function JoinRoom({ onJoin }) {
4  const [username, setUsername] = useState('');
5  const [room, setRoom] = useState('');
6
7  const handleSubmit = (e) => {
8    e.preventDefault();
9    if (username && room) {
10      onJoin(username, room);
11    }
12  };
13
14  return (
15    <div className="join-container">
16      <h2>Join a Chat Room</h2>
17      <form onSubmit={handleSubmit}>
18        <div className="form-group">
19          <label htmlFor="username">Username:</label>
20          <input
21            type="text"
22            id="username"
23            value={username}
24            onChange={(e) => setUsername(e.target.value)}
25            required
26          />
27        </div>
28        <div className="form-group">
29          <label htmlFor="room">Room:</label>
30          <input
31            type="text"
32            id="room"
33            value={room}
34            onChange={(e) => setRoom(e.target.value)}
35            required
36          />
37        </div>
38        <button type="submit">Join Room</button>
39      </form>
40    </div>
41  );
42}
43
44export default JoinRoom;

src/components/ChatRoom.js

1import React, { useState, useEffect, useRef } from 'react';
2import io from 'socket.io-client';
3
4const ENDPOINT = 'http://localhost:4000';
5
6function ChatRoom({ username, room }) {
7  const [messages, setMessages] = useState([]);
8  const [message, setMessage] = useState('');
9  const [users, setUsers] = useState([]);
10  const [typingUsers, setTypingUsers] = useState([]);
11  const socketRef = useRef();
12  const messagesEndRef = useRef();
13
14  // Initialize socket connection
15  useEffect(() => {
16    socketRef.current = io(ENDPOINT);
17    
18    // Join room
19    socketRef.current.emit('join_room', { username, room });
20    
21    // Listen for messages
22    socketRef.current.on('receive_message', (data) => {
23      setMessages((prevMessages) => [...prevMessages, data]);
24    });
25    
26    // Listen for user joined
27    socketRef.current.on('user_joined', (data) => {
28      setUsers(data.users);
29      setMessages((prevMessages) => [
30        ...prevMessages, 
31        { system: true, message: data.message, timestamp: new Date().toISOString() }
32      ]);
33    });
34    
35    // Listen for user left
36    socketRef.current.on('user_left', (data) => {
37      setUsers(data.users);
38      setMessages((prevMessages) => [
39        ...prevMessages, 
40        { system: true, message: data.message, timestamp: new Date().toISOString() }
41      ]);
42    });
43    
44    // Get current users in room
45    socketRef.current.on('room_users', (data) => {
46      setUsers(data);
47    });
48    
49    // Listen for typing events
50    socketRef.current.on('user_typing', (data) => {
51      setTypingUsers((prevTypers) => {
52        if (!prevTypers.includes(data.username)) {
53          return [...prevTypers, data.username];
54        }
55        return prevTypers;
56      });
57      
58      // Remove typing indicator after 2 seconds
59      setTimeout(() => {
60        setTypingUsers((prevTypers) => 
61          prevTypers.filter(user => user !== data.username)
62        );
63      }, 2000);
64    });
65    
66    // Cleanup on unmount
67    return () => {
68      socketRef.current.disconnect();
69    };
70  }, [username, room]);
71  
72  // Auto-scroll to bottom when new messages arrive
73  useEffect(() => {
74    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
75  }, [messages]);
76  
77  // Handle sending messages
78  const handleSendMessage = (e) => {
79    e.preventDefault();
80    if (message.trim()) {
81      socketRef.current.emit('send_message', {
82        room,
83        username,
84        message
85      });
86      setMessage('');
87    }
88  };
89  
90  // Handle typing indicator
91  const handleTyping = () => {
92    socketRef.current.emit('typing', { room, username });
93  };
94  
95  // Format timestamp
96  const formatTime = (timestamp) => {
97    return new Date(timestamp).toLocaleTimeString([], { 
98      hour: '2-digit', 
99      minute: '2-digit' 
100    });
101  };
102
103  return (
104    <div className="chat-container">
105      <div className="chat-header">
106        <h2>Room: {room}</h2>
107      </div>
108      
109      <div className="chat-main">
110        <div className="chat-sidebar">
111          <h3>Users in Room</h3>
112          <ul className="users-list">
113            {users.map((user, index) => (
114              <li key={index} className={user.username === username ? 'current-user' : ''}>
115                {user.username} {user.username === username ? '(You)' : ''}
116              </li>
117            ))}
118          </ul>
119        </div>
120        
121        <div className="chat-messages">
122          {messages.map((msg, index) => (
123            <div 
124              key={index} 
125              className={`message ${msg.system ? 'system-message' : 
126                (msg.username === username ? 'my-message' : 'other-message')}`}
127            >
128              {!msg.system && (
129                <div className="message-header">
130                  <span className="username">{msg.username}</span>
131                  <span className="timestamp">{formatTime(msg.timestamp)}</span>
132                </div>
133              )}
134              <p className="message-text">{msg.message}</p>
135            </div>
136          ))}
137          
138          {typingUsers.length > 0 && (
139            <div className="typing-indicator">
140              {typingUsers.join(', ')} {typingUsers.length === 1 ? 'is' : 'are'} typing...
141            </div>
142          )}
143          
144          <div ref={messagesEndRef} />
145        </div>
146      </div>
147      
148      <form className="message-form" onSubmit={handleSendMessage}>
149        <input
150          type="text"
151          value={message}
152          onChange={(e) => setMessage(e.target.value)}
153          onKeyPress={handleTyping}
154          placeholder="Type a message..."
155        />
156        <button type="submit">Send</button>
157      </form>
158    </div>
159  );
160}
161
162export default ChatRoom;

3.몇 가지 기본 CSS를 추가하세요

src/App.css

1* {
2  box-sizing: border-box;
3  margin: 0;
4  padding: 0;
5}
6
7body {
8  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
9  line-height: 1.6;
10  background-color: #f5f5f5;
11}
12
13.App {
14  max-width: 1200px;
15  margin: 0 auto;
16  padding: 1rem;
17}
18
19.App-header {
20  text-align: center;
21  margin-bottom: 2rem;
22  color: #333;
23}
24
25/* Join Room Styles */
26.join-container {
27  max-width: 500px;
28  margin: 0 auto;
29  background: white;
30  border-radius: 8px;
31  padding: 2rem;
32  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
33}
34
35.join-container h2 {
36  margin-bottom: 1.5rem;
37  text-align: center;
38}
39
40.form-group {
41  margin-bottom: 1rem;
42}
43
44.form-group label {
45  display: block;
46  margin-bottom: 0.5rem;
47  font-weight: 500;
48}
49
50.form-group input {
51  width: 100%;
52  padding: 0.75rem;
53  border: 1px solid #ddd;
54  border-radius: 4px;
55  font-size: 1rem;
56}
57
58button {
59  background-color: #4caf50;
60  color: white;
61  border: none;
62  padding: 0.75rem 1.5rem;
63  font-size: 1rem;
64  border-radius: 4px;
65  cursor: pointer;
66  transition: background-color 0.2s;
67}
68
69button:hover {
70  background-color: #45a049;
71}
72
73/* Chat Room Styles */
74.chat-container {
75  display: flex;
76  flex-direction: column;
77  height: 80vh;
78  background: white;
79  border-radius: 8px;
80  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
81}
82
83.chat-header {
84  padding: 1rem;
85  background: #4caf50;
86  color: white;
87  border-top-left-radius: 8px;
88  border-top-right-radius: 8px;
89}
90
91.chat-main {
92  display: flex;
93  flex: 1;
94  overflow: hidden;
95}
96
97.chat-sidebar {
98  width: 200px;
99  background: #f8f8f8;
100  padding: 1rem;
101  border-right: 1px solid #ddd;
102  overflow-y: auto;
103}
104
105.chat-sidebar h3 {
106  margin-bottom: 1rem;
107  font-size: 1.1rem;
108}
109
110.users-list {
111  list-style: none;
112}
113
114.users-list li {
115  padding: 0.5rem 0;
116  border-bottom: 1px solid #eee;
117}
118
119.current-user {
120  font-weight: bold;
121  color: #4caf50;
122}
123
124.chat-messages {
125  flex: 1;
126  padding: 1rem;
127  overflow-y: auto;
128}
129
130.message {
131  margin-bottom: 1rem;
132  padding: 0.75rem;
133  border-radius: 8px;
134  max-width: 80%;
135}
136
137.my-message {
138  background-color: #e3f2fd;
139  margin-left: auto;
140}
141
142.other-message {
143  background-color: #f1f1f1;
144}
145
146.system-message {
147  background-color: #fffde7;
148  text-align: center;
149  font-style: italic;
150  padding: 0.5rem;
151  margin: 0.5rem auto;
152  max-width: 90%;
153  color: #666;
154}
155
156.message-header {
157  display: flex;
158  justify-content: space-between;
159  margin-bottom: 0.5rem;
160  font-size: 0.85rem;
161}
162
163.username {
164  font-weight: bold;
165}
166
167.timestamp {
168  color: #777;
169}
170
171.typing-indicator {
172  font-style: italic;
173  color: #777;
174  padding: 0.5rem;
175}
176
177.message-form {
178  display: flex;
179  padding: 1rem;
180  border-top: 1px solid #ddd;
181}
182
183.message-form input {
184  flex: 1;
185  padding: 0.75rem;
186  border: 1px solid #ddd;
187  border-radius: 4px;
188  margin-right: 0.5rem;
189  font-size: 1rem;
190}

애플리케이션 실행

1.서버 시작:

1cd chat-app/server
2node server.js

2.리액트 클라이언트 시작:

1cd chat-app/client
2npm start

3.브라우저를 열어 다음 작업을 수행하세요. http://localhost:3000

구현된 기능

  1. 룸 기반 채팅:
    • 사용자는 특정 대화방에 참여할 수 있습니다.
    • 여러 개의 방이 동시에 존재할 수 있습니다
  2. 실시간 메시징:
    • 메시지는 회의실의 모든 사용자에게 즉시 전달됩니다.
  3. 사용자 프레즌스:
    • 현재 방에 있는 사람이 누구인지 보여줍니다.
    • 사용자 가입 또는 탈퇴 시 알림
  4. 타이핑 인디케이터:
    • 누군가가 메시지를 입력할 때 표시됩니다.
  5. 메시지 형식:
    • 내 메시지와 다른 사람의 메시지 스타일이 다릅니다.
    • 참여/탈퇴 이벤트에 대한 시스템 메시지
    • 메시지의 타임스탬프

이 프로토타입은 실시간 채팅 애플리케이션을 위한 견고한 기반을 제공합니다.다음과 같은 기능을 사용하여 확장할 수 있습니다.

  • 사용자 인증
  • 영구 메시지 기록
  • 비공개 메시지
  • 파일 공유
  • 영수증 읽기
  • 맞춤 이모티콘/반응

닌자의 AI 어시스턴트를 경험하세요

지금 무료로 사용해 보십시오.요금제는 월 19달러부터 시작합니다.