반응형
Socket.io 란?
웹 어플리케이션에서 양방향 통신을 가능하게 해주는 라이브러리 입니다.
Socket.io 라이브러리와 Next.js 프레임 워크를 이용해서 실시간 채팅을 구현 하겠습니다.
실시간 채팅 만들기
1. Next.js 프로젝트 생성
npx create-next-app
Next.js 설치하기 npx create-next-app
Next.js 설치하는 방법에 대해서 알아보도록 하겠습니다. 설치하는 방법에는 두가지가 있습니다. npx create-next-app을 이용해서 한 번에 자동으로 설치하는 방법과 next.js에 필요한 라이브러리를 하나
powerku.tistory.com
2. axios, socket.io, socket.io-client 라이브러리 설치
npm install axios socket.io socket.io-client
3. SocketProvider 추가
app/layout.tsx
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'
import { SocketProvider } from '@/components/socket-provider'
const inter = Inter({ subsets: ['latin'] })
export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<SocketProvider>
<body className={inter.className}>{children}</body>
</SocketProvider>
</html>
)
}
components/socket-provider.tsx
"use client";
import {
createContext,
useContext,
useEffect, useState
} from "react";
import {io as ClientIO} from "socket.io-client";
type SocketContextType = {
socket: any | null;
isConnected: boolean;
}
const SocketContext = createContext<SocketContextType>({
socket: null,
isConnected: false,
})
export const useSocket = () => { return useContext(SocketContext) };
export const SocketProvider = ({ children }: {children: React.ReactNode}) => {
const [socket, setSocket] = useState<any | null>(null);
const [isConnected, setIsConnected] = useState(false);
useEffect(() => {
if (!socket) {
return;
}
socket.on("disconnect", async () => {
setIsConnected(false);
});
}, []);
useEffect(() => {
const socketInstance = new (ClientIO as any)(process.env.NEXT_PUBLIC_SITE_URL, {
path: "/api/socket/io",
addTrailingSlash: false
})
socketInstance.on("connect", async () => {
setIsConnected(true);
})
setSocket(socketInstance);
return () => {
socketInstance.disconnect();
}
}, [])
return (
<SocketContext.Provider value={{socket, isConnected}}>
{children}
</SocketContext.Provider>
)
}
4. Socket api 추가
pages/api/socket/io.ts
import { Server as NetServer} from "http";
import { NextApiRequest, NextApiResponse } from "next";
import { Server as ServerIO } from "socket.io";
import {Socket} from "net";
export type NextApiResponseServerIo = NextApiResponse & {
socket: Socket & {
server: NetServer & {
io: ServerIO;
}
}
}
const ioHandler = (req: NextApiRequest, res: NextApiResponseServerIo) => {
if (!res.socket.server.io) {
const path = "/api/socket/io";
const httpServer: NetServer = res.socket.server as any;
const io = new ServerIO(httpServer, {
path: path,
addTrailingSlash: false
})
res.socket.server.io = io;
}
}
export default ioHandler;
pages/api/chat/index.ts
import { NextApiRequest } from "next";
export default async function handler(req: NextApiRequest, res: any) {
if (req.method === 'POST') {
const message = req.body;
res?.socket?.server?.io?.emit('message', message);
res.status(201).json(message);
}
};
5. 채팅 페이지 만들기
app/chat/page.tsx
"use client";
import React, { useState, useEffect } from 'react';
import axios from "axios";
import {useSocket} from "@/components/socket-provider";
interface message {
userId: number;
content: string;
}
const ChatPage = () => {
const [messages, setMessages] = useState<message[]>([]);
const [currentMessage, setCurrentMessage] = useState('');
const { socket, isConnected } = useSocket();
const [userId, setUserId] = useState(+new Date())
useEffect(() => {
if (!socket) {
return;
}
socket.on('message', (data: any) => {
setMessages((messages) => [...messages, ...[data]]);
});
return () => {
socket.off('message');
};
}, [socket, messages]);
const sendMessage = async (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault()
await axios.post('/api/chat', {
userId: userId,
content: currentMessage
});
setCurrentMessage('');
};
return (
<div className="rounded-xl border bg-card text-card-foreground shadow w-[300px] mx-auto">
<div className="p-6">
<p>{isConnected ? "연결 완료" : "연결중"}</p>
</div>
<div className="p-6 pt-0">
<div className="space-y-4">
{messages.map((message, index) => (
<div key={index} className={"flex w-max max-w-[75%] flex-col gap-2 rounded-lg px-3 py-2 text-sm " +
(message.userId !== userId ? "ml-auto bg-blue-400 text-white": "bg-zinc-100") }>
{message.content}
</div>
))}
</div>
</div>
<div className="flex items-center p-6 pt-0">
<form className="flex w-full items-center space-x-2">
<input
type="text"
value={currentMessage}
onChange={(e) => setCurrentMessage(e.target.value)}
className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 flex-1">
</input>
<button type="submit"
onClick={(e) => sendMessage(e)}
className="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 bg-blue-600 text-white shadow hover:bg-primary/90 h-9 w-9">
전송
</button>
</form>
</div>
</div>
);
};
export default ChatPage;
결과물
Socket을 이용해서 실시간 채팅을 구현할 수 있습니다.
1. 소켓을 연결하고,
2. 해당 소켓의 이벤트로 데이터를 전송하면
3. 연결 되어 있는 이벤트로 전달하여 메시지를 전달할 수 있습니다.
728x90
반응형