Socket.io + Next JS로 채팅 구현

김은호·2023년 5월 9일
1

NextJS도 백엔드 프레임워크 역할을 하므로 Express를 사용하지 않고 NextJS 하나로 소켓을 이용한 채팅 기능을 구현해보았다.

서버와 Socket.io 연결하기

// pages/api/socket.ts

import { Server } from 'Socket.IO';

const SocketHandler = (req, res) => {
  if (res.socket.server.io) {
    console.log('이미 바인딩 되었습니다.');
  } else {
    console.log('서버-소켓 연결완료');
    const io = new Server(res.socket.server);
    res.socket.server.io = io;
  }
  res.end();
};

export default SocketHandler;

채팅 API 작성

emit한 데이터를 on으로 받는다!

// pages/api/chat.ts

import { NextApiRequest } from "next";


export default (req: NextApiRequest, res: any) => {
  if (req.method === 'POST') {
    // 메시지 얻기
    const message = req.body;

    // on('message')가 메시지를 받음
    res?.socket?.server?.io?.emit('message', message);

    res.status(201).json(message);
  }
};

클라이언트 부분

import { chatAPI } from '../utils/api/chat';
import { Box, Button, HStack, Input, Text } from '@chakra-ui/react';
import React, { useState, useEffect } from 'react';
import io from 'socket.io-client';

export interface IMsg {
  user: string;
  msg: string;
}

let socket: SocketIOClient.Socket;

// 랜덤 유저
const user = 'User_' + String(new Date().getTime()).substr(-3);

// 내 메시지
const Me = ({ message }: { message: string }) => {
  return (
    <Box p="6px 10px">
      <Text as="p">Me</Text>
      <Box
        bgColor="blue.400"
        color="white"
        maxW="320px"
        borderRadius={8}
        p="6px 8px"
        display="inline-block"
      >
        <Text>{message}</Text>
      </Box>
    </Box>
  );
};

// 상대 메시지
const Other = ({ user, message }: { user: string; message: string }) => {
  return (
    <Box p="6px 10px" textAlign="right">
      <Text as="p" textAlign="right">
        {user}
      </Text>
      <Box
        bgColor="green.400"
        color="white"
        maxW="320px"
        borderRadius={8}
        p="6px 8px"
        display="inline-block"
      >
        <Text>{message}</Text>
      </Box>
    </Box>
  );
};

function index() {
  // connected flag
  const [connected, setConnected] = useState<boolean>(false);

  // init chat and message
  const [chat, setChat] = useState<IMsg[]>([]);
  const [msg, setMsg] = useState<string>('');

  // 소켓 연결
  useEffect(() => {
    const socketInitializer = async () => {
      await fetch('/api/socketio');
      socket = io();

      socket.on('connect', () => {
        console.log('connected', socket);
        setConnected(true);
      });

      socket.on('error', (error: any) => {
        console.log(error);
      });

      socket.on('message', (message: IMsg) => {
        chat.push(message);
        console.log(chat);
        setChat([...chat]);
      });
    };
    socketInitializer();

    // 브라우저가 꺼지면 소켓 연결 종료
    return () => {
      if (socket) {
        socket.disconnect();
      }
    };
  }, []);

  const sendMessage = async () => {
    if (msg) {
      const message: IMsg = {
        user,
        msg,
      };

      // send Message to Other user
      const res = await chatAPI(message);

      // reset field if OK
      if (res.status === 201) {
        setMsg('');
      }
    }
  };
  return (
    <Box>
      <Box
        borderRadius={4}
        w="480px"
        h="640px"
        m="0 auto"
        mt="32px"
        bgColor="gray.200"
        overflowY="scroll"
      >
        {chat.map((chat, i) => (
          <Box key={i}>
            {chat.user === user ? (
              <Me message={chat.msg} />
            ) : (
              <Other user={chat.user} message={chat.msg} />
            )}
          </Box>
        ))}
      </Box>
      <HStack w="480px" h="75px" m="0 auto" mt="12px">
        <Input
          type="text"
          w="75%"
          h="100%"
          value={msg}
          placeholder={connected ? '메시지를 입력하세요' : '연결중입니다...'}
          isDisabled={!connected}
          onChange={(e) => {
            setMsg(e.target.value);
          }}
          onKeyDown={(e) => {
            if (e.key === 'Enter') {
              sendMessage();
            }
          }}
        />
        <Button w="25%" h="100%" onClick={sendMessage} isLoading={!connected}>
          보내기
        </Button>
      </HStack>
    </Box>
  );
}

export default index;

참고 & 마치며

https://codesandbox.io/s/nextjs-socketio-chat-piffv?file=/src/pages/index.tsx
위 코드를 그대로 갖다 쓰면 클라이언트에서 소켓 연결이 안되었다. 왜 그러는지는 아직 잘 모르겠다..

소켓을 사용할 때 TS에서 타입을 어떻게 지정하는지도 공부가 필요할듯

0개의 댓글