Next.js 에서 Socket.io 이용해서 실시간 채팅 만들기

반응형

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
반응형