Socket을 사용한 Read-Time 채팅 구현하기 - (2) Socket으로 채팅 구현하기
Socket을 사용한 Read-Time 채팅 구현하기 - (2) Socket으로 채팅 구현하기
2️⃣ Socket으로 채팅 구현하기
이번 글에서는 Socket.IO를 활용한 실시간 채팅에서 메시지를 입력하고 서버로 전송하는 과정을 다룬다. 이전 글에서 Socket.IO를 초기 설정했으므로, 이제 실제 채팅 기능을 구현해보자.
1️⃣ Chat Input 컴포넌트 구현
Socket을 활용한 채팅 애플리케이션의 메시지를 입력받는 컴포넌트를 구현한다.
1.1 zod 유효성 검사
content
는 최소 한 글자 이상이어야 한다.react-hook-form
의zodResolver
를 통해 처리된다.
1.2 폼 상태 관리
react-hook-form
으로 폼의 상태를 관리한다.defaultValues
를 설정하여 초기 상태를 지정한다.isSubmitting
속성을 사용하여 요청 중인지 확인한다.
1.3 메시지 전송 로직 (onSubmit
)
query-string
라이브러리를 사용해 API URL에 쿼리 파라미터를 추가한다.axios
를 활용하여 POST 요청을 보낸다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// components/chat/chat-input.tsx
"use client";
import * as z from "zod";
import axios from "axios";
import qs from "query-string";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Input } from "@/components/ui/input";
import { Form, FormControl, FormField, FormItem } from "@/components/ui/form";
interface ChatInputProps {
apiUrl: string;
query: Record<string, any>;
name: string;
type: "conversation" | "channel";
}
const formSchema = z.object({
content: z.string().min(1)
});
export const ChatInput = ({ apiUrl, query, name, type }: ChatInputProps) => {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
content: ""
}
});
const isLoading = form.formState.isSubmitting;
const onSubmit = async (values: z.infer<typeof formSchema>) => {
try {
const url = qs.stringifyUrl({ url: apiUrl, query });
await axios.post(url, values);
} catch (error) {
console.log(error);
}
};
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<FormField
control={form.control}
name="content"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
disabled={isLoading}
placeholder={`Message ${
type === "conversation" ? name : "#" + name
}`}
{...field}
/>
</FormControl>
</FormItem>
)}
/>
</form>
</Form>
);
};
1.4 페이지에 컴포넌트 추가
1
2
3
4
5
6
7
8
9
10
// app/(main)/(routes)/servers/[serverId]/channels/[channelId]/page.tsx
import { ChatInput } from "@/components/chat/chat-input";
...
<ChatInput
name={channel.name}
type="channel"
apiUrl="/api/socket/messages"
query=
/>
...
2️⃣ Messages API 구현
메시지 전송을 처리하는 API를 구현하고 주요 로직을 설명한다.
2.1 API 기능 개요
- HTTP 메서드 검증:
POST
요청만 허용한다. - 요청 데이터 검증:
content
값이 존재하는지 확인한다. - 서버 및 채널, 멤버 유효성 확인: 사용자의 접근 권한을 검증한다.
- 메시지 저장:
db.message.create
를 통해 DB에 저장한다. - 소켓 이벤트 전송: 저장된 메시지를
io.emit
을 통해 실시간으로 클라이언트에 전달한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// pages/api/socket/messages.ts
import { NextApiRequest } from "next";
import { NextApiResponseServerIo } from "@/types";
import { db } from "@/lib/db";
import { currentProfilePages } from "@/lib/current-profile-pages";
export default async function handler(
req: NextApiRequest,
res: NextApiResponseServerIo
) {
if (req.method !== "POST") {
return res.status(405).json({ error: "Method not allowed" });
}
try {
const profile = await currentProfilePages(req);
const { content } = req.body;
const { serverId, channelId } = req.query;
if (!profile || !serverId || !channelId || !content) {
return res.status(400).json({ error: "Invalid request" });
}
const message = await db.message.create({
data: {
content,
channelId: channelId as string,
memberId: profile.id
}
});
const channelKey = `chat:${channelId}:messages`;
res?.socket?.server?.io?.emit(channelKey, message);
return res.status(200).json(message);
} catch (error) {
console.log("[MESSAGE_POST]", error);
return res.status(500).json({ message: "Internal Error" });
}
}
3️⃣ 정리
이제 사용자가 채팅 입력창에 메시지를 입력하고 전송하면, 서버로 저장되고 다른 클라이언트에게 실시간으로 전파된다.
This post is licensed under CC BY 4.0 by the author.