채팅 서버와 웹 어플리케이션 데모

차후 본 커리큘럼에서 진행 할 프로젝트를 먼저 데모로 작성해봅니다.
웹, JavaScript, Node.js


완성된 데모: https://codeflow-chat.herokuapp.com/

스펙과 의존성 결정

안녕하세요. 김동욱입니다. 새로운 커리큘럼을 연재하기 이전에 데모로 저 혼자 일단 프로젝트를 작성해보려합니다. 추후 커리큘럼에서는 풀스택 웹 개발자에게 필요한 컴퓨터 기초 및 프로그래밍, 네트워크 및 HTTP, 웹 등의 이론 강의를 포함해, 다양한 과제와 못난 코드들을 거치며 멀고 먼길을 돌아 프로젝트를 완수하게 될 것입니다.

지금 이 강의에서는 외주 마감을 하루 앞둔 개발자의 마음으로 제 총력을 기울여서 코딩하는 과정을 보여드리려 합니다. 아무런 대본이나 준비 없이 실제 상황에서 제가 개발을 해나가는 과정을 보여드릴까 합니다. 다만 커밋을 조금 더 끊어서 작성하고, 또 과정이 진행됨에 따라 교재를 함께 작성해 나가면서 프로젝트를 완수 할 것입니다. 이론이나 배경에 대한 설명은 모두 생략합니다.

스펙

지금은 야심한 새벽, 저는 내일 아침까지 간단한 채팅 어플리케이션을 만들어주기로 계약했는데 아직까지 코드 한자를 작성하지 않았습니다. 클라이언트가 일어나기 전까지 코드를 완성해야합니다. 클라이언트의 요구사항은 다음과 같습니다.

클라이언트 요구사항

  • 익명 채팅방이며, 유저는 몇가지 이미지 중에서 아바타를 선택 할 수 있고 닉네임을 직접 입력 할 수있다.
  • 유저는 채팅방을 개설하거나, 기존의 채팅방에 입장 할 수 있다.
    • 유저가 하나도 없는 채팅방은 한 시간 이후에 자동으로 사라진다.
  • 스크립트를 통해 IFRAME으로 어떠한 HTML 페이지에도 임베딩 할 수 있어야한다.
    • 임베딩 할 때 패러미터를 통해 입장 할 채팅방을 미리 고정 할 수 있다.
  • 모든 채팅 메시지 뿐만 아니라 모든 데이터를 실시간으로 주고 받는다.

클라이언트가 이 정도를 요구했습니다. 마음이 따듯한 분이라 디자인에는 크게 신경쓰지 않아도 좋다고 합니다.

뼈대 코드

이제 프로젝트를 본격적으로 시작합니다. 먼저 스펙에서 플랫폼에 대한 단서가 없기에 제가 좋아하는 자바스크립트만으로 프론트 백엔드를 구성하려합니다. 프론트에는 React.js와 MobX라는 라이브러리를 쓸테구요. 백엔드에서는 Express와 Socket.io라는 라이브러리를 씁니다. 왜냐구요? 지금은 공부가 아니라 외주를 하는 중이라 최대한 빨리 만들어보려합니다. 추후 커리큘럼에서는 최초에 바닐라(쌩) 자바스크립트로 프로젝트를 시작해서 점차 갖다 쓰는 법을 배워나갈 것입니다.

먼저 github에 저장소를 하나 만들 텐데요. 이건 단순히 여러분들께 코딩 과정을 보여드리기 위해서 필요한 용도입니다. https://github.com/dehypnosis/chat 저장소를 만들었습니다.

다음으로 뼈대를 만듭니다. 백엔드와 프론트의 플랫폼을 node와 웹브라우저로 결정하고 갖다 쓸 라이브러리도 결정했기 떄문에, 풍부한 생태계니 만큼 더욱 더 갖다 쓰기 위해서 뼈대 코드를 찾아봅니다. 보일러플레이트(bolierplate)라고 합니다. 구글에 express react mobx 라고 검색하면 첫번째 링크(https://github.com/zckly/MobX-React-Webpack-Express)에 보일러플레이트 코드가 나옵니다. 코드를 복제하기 전에 살펴보니 서버 쪽도 충분히 뼈대를 잡아뒀고, 클라이언트 쪽엔 Webpack으로 개발 및 배포 환경도 꾸려져있고 react와 mobx에 왠지 필요 없을듯한 react-router까지 준비해뒀습니다. 클론하고 제 저장소로 옮깁니다.

패키지 의존성을 뜯어보니 필요 없을 jquery가 있어서 제거했습니다. 그리고 babel의 deprecated 패키지가 몇가지 있지만 일단 무시하고 진행합니다. 자잘한 문제가 많아서 추후에 커리큘럼에서는 쓰지 않겠습니다.

chat-demo 1chat-demo 1

프론트 설계

먼저 JS로 웹 어플리케이션(SPA)을 만들고 백엔드에서는 웹 서버가 JS로 만든 어플리케이션을 포함한 엔트리 HTML페이지를 서빙해줄 예정입니다. 이후엔 웹소켓 서버를 만들어 (기존 웹서버에 갖다 붙혀서) API를 구현하면서 점차 채팅이 살아움직이도록 만들 예정입니다. 우선 백엔드쪽은 모두 무시하고 죽어있는(오프라인) 어플리케이션을 만들도록 하겠습니다.

에디터는 좀있다가 켜겠습니다. 코드를 작성하기 전에 먼저! 주어진 스펙에 기초해서 어플리케이션이 갖는 상태와 데이터를 모델링합니다.

상태 설계

type State {
  user: User
  rooms: [Room!]!
  activeRoom: Room
  roomFixed: Boolean = false
}

type User {
  id: Int!
  nickname: String!
  avatarUrl: String!
}

type Room {
  id: Int!
  title: String!
  users: [User!]!
  messages: [Message!]!
}

type Message {
  id: Int!
  user: User!
  content: String!
  at: Date!
}

의사코드입니다. 적당히 이해하시면 되겠습니다. 사실은 제가 요즘 빠진 GraphQL 스키마 문법입니다. 위에서 State나 User, Room 같은 타입은 아직 실제 코드(JS)로 옮기기 전에 현실의 객체를 단순히 추상화한 단어입니다.

제가 지금 설계한 앱의 모든 상태를 표현 할 수 있는 구조입니다. 아마 후반에 상태가 변경될 수도 있지만, 최초에 상태를 설계하고 모든 장면을 하나 하나 상상해보면서 다듬어나가면 됩니다.

앱의 전역 상태

type State {
  user: User
  rooms: [Room!]!
  activeRoom: Room
  roomFixed: Boolean = false
}
  • user
    앱에 현재 유저가 로그인했을 수도 아닐 수도 있습니다. 이 상태를 참조해서 닉네임과 아바타를 선택하는 UI를 보여주면 되겠습니다.
  • rooms
    채팅방 목록을 앱이 보여줄 수 있어야하므로 모든 방을 포함하는 컬렉션이 필요합니다.
  • activeRoom
    현재 입장한 방이 있을 수도 없을 수도 있고 동일하게 Room 타입이 됩니다.
  • roomFixed
    스펙에서 방을 고정 할 수 있어야한 했으니 단순한 boolean 플래그를 하나 두었습니다. 이 상태를 참조해서 방 나가기 기능을 on/off 할 수 있겠죠?

유저라는 타입

type User {
  id: Int!
  nickname: String!
  avatarUrl: String!
}
  • id: 단순한 고유한 숫자로 어떤 의미가 부여된 값은 아닙니다.
    • 유저가 방을 나가지 않고서 비정상적으로 소켓 연결을 종료 할 수 있습니다. 이럴 때 유령 유저가 방에 남아있는 것을 방지하기 위해서, 서버에서 소켓과 유저에 동일한 id 값을 부여해두고 소켓 종료시에 유령 유저를 방에서 퇴장 시킬 수도 있겠습니다. 아마도 제 생각대로 되겠죠?

방이라는 타입

type Room {
  id: Int!
  title: String!
  users: [User!]!
  messages: [Message!]!
}
  • id
    마찬가지로 서버에서 메세지를 송수신하거나 방에 입장하는 등의 처리를 할 때 방을 구별하기 위한 고유한 값이 될 것 입니다.
  • title
    방 제목입니다.
  • users
    현재 입장한 유저 목록이 되겠습니다.
  • messages
    방에 누적된 메세지의 컬렉션입니다.

메세지라는 타입

type Message {
  id: Int!
  user: User!
  content: String!
  at: Date!
}
  • id
    메세지 역시 id를 갖습니다. 당장 생각하기엔 어디에 쓸지는 모르겠습니다. 왠지 필요해질 것 같습니다. 일단 넣어둡니다.
  • user
    발신한 유저가 되겠습니다.
  • content
    내용입니다.
  • at
    아마 Javascript의 native Date객체가 연상되실텐데 서버와 송수신 할 때 Date객체를 주고 받을 순 없으니 (지금 생각해보기엔 서버와 앱이 JSON으로 통신 할 것입니다.) 아마 단순히 String으로 혹은 Int(Unix Timestamp)로 주고 받을 수도 있겠습니다. 나중에 결정하겠습니다.

아직 코드를 한줄도 안치고 교재만 열심히 작성했습니다만, 기분은 꼭 프론트를 다 만든 것 같습니다. 다음으로 UI를 설계 해보고 상태가 적절히 구성되었는지 검토해보겠습니다.

UI 및 순서도 설계

앱이 흘러가는 순서를 검토를 해봅니다. 앱이 그렇게 크지 않기 떄문에 코드를 치기전에 모든 시나리오를 미리 다 검토해볼 수 있겠습니다. 글과 상상력으로 순서도를 검토해본다고 보시면 되겠습니다. 정말 순서도를 그릴 필요는 없습니다.

  1. State.user가 없음.
    • Prompt라는 UI를 보여줍니다. 닉네임 입력창과 아바타를 선택하는 버튼이 있습니다.
    • 유저의 닉네임과 아바타를 입력 받고 로그인하기 요청을 보내고 State.user 상태를 갱신해 줍니다.
    • 네트워킹하는 동안 Prompt(loading)이라는 UI를 보여줍니다.
  2. State.activeRoom이 없음.
    1. 입장 할 방의 id가 고정된 패러미터로 제공되었음.
      • 서버에 방에 입장하기 요청을 보내고 그 응답 결과(방 데이터가 되어야 겠습니다)로 State.rooms를 갱신하고 State.activeRoom의 참조를 갱신합니다.
      • 이 때는 Room.messages를 일정 부분만 받아오면 새로 입장한 유저가 대화 흐름을 파악하는데 도움이 될 수도 있겠습니다.
      • 그 동안 Room(loading)이라는 UI를 보여줍니다.
    2. 패러미터가 없음.
      • 서버로부터 모든 방의 목록을 받아옵니다.
      • 이 때는 Room.messages를 받아올 필요는 없겠죠?
      • 그 동안 Lobby(loading)이라는 UI를 보여줍니다.
      • 데이터를 모두 받아왔다면 Lobby라는 UI를 통해 유저가 방을 선택하거나 개설 할 수 있도록 합니다.
        • 유저가 방에 입장하기 요청을 보냈다면 그 응답 결과로 State.rooms를 갱신하고 State.activeRoom의 참조를 갱신합니다.
        • 유저가 방을 개설하기 요청을 보냈다면 그 응답 결과(동일한 Room 데이터)로 State.rooms를 갱신하고 State.activeRoom의 참조를 갱신합니다.
      • 그 동안 Room(loading)이라는 UI를 보여줍니다.
  3. 입장 완료.
    • Room이라는 UI를 보여줍니다. 이 UI에는 유저 목록과 메세지 창, 입력 창, 나가기 버튼이 있겠습니다.
    • 서버로부터 실시간으로 방의 메세지를 갱신합니다.
    • 입력 폼에 메세지를 입력하고 전송하는 경우 서버로 방에 메세지를 발송하기 요청을 보냅니다.
    • State.roomFixed가 참인 경우 나가기 버튼을 비활성화 합니다.
    • 나가기 버튼을 클릭하면 서버에 방을 나가기 요청을 보내고 State.activeRoom의 값을 비웁( = null)니다. 이 순서도가 메인 로직에서 정상적으로 작동한다면 다시 바로 2.2로 가서 방 목록을 받아오면서 Lobby(loading) UI가 보여야 겠습니다.

자 위 순서도를 점검하면서 프론트 앱에 필요한 모든 UI와 로직, 또한 서버와의 통신에 필요한 창구도 결정 할 수 있었습니다.

프로젝트를 완료하고 써내려간 교재를 검수하는 중입니다. 위의 2.2에서 모든 방의 목록을 받아 온다는 대목이 프로젝트 막바지에 큰 설계적인 결함이 되었습니다. 결국 최종적으로는 방향을 바꾸어 Lobby에서는 실시간으로 방 목록을 받아오고 계속해서 업데이트를 기다린다가 되었습니다. 역시 경험이 필요했습니다.

필요한 UI

  • Prompt
    유저가 입장하고 닉네임과 아바타를 설정하는 UI
  • Lobby
    방 목록을 보여주고 방에 입장하거나 방을 개설 할 수 있는 UI
  • Room
    채팅방

이 메인 UI들 하위에 UI 조각들이 생겨나겠지만, 그런 부분은 마크업을 할 때 실시간으로 해도 충분 할 것 같습니다.

서버와의 통신에 필요한 창구 (나중에 다듬어지면 API 명세라고 하겠습니다.)

  • 로그인하기
  • 방에 입장하기
  • 모든 방의 목록을 받아오기
  • 방을 개설하기
  • 방의 메세지를 갱신
  • 방에 메세지를 발송
  • 방을 나가기

이 정도가 되겠습니다. 아마 위 기능들은 웹 소켓 프로토콜로 구성될 것 같습니다. 부분 부분 HTTP로 할까요? 좀 있다가 결정하겠습니다. 글로 적힌 내용은 너무 쉽게 구상한 것 같지만, 시나리오를 계속 점검하면서 여러번을 수정한 내용입니다. 짝짝짝! 프론트 앱을 다만들었습니다. 자! 코드로 옮기기만 하면됩니다.

프론트 구현

상태 구현

type State {
  user: User
  rooms: [Room!]!
  activeRoom: Room
  roomFixed: Boolean = false
}

type User {
  id: Int!
  nickname: String!
  avatarUrl: String!
}

type Room {
  id: Int!
  title: String!
  users: [User!]!
  messages: [Message!]!
}

type Message {
  id: Int!
  user: User!
  content: String!
  at: Date!
}

위에서 설계한 전역 상태와 타입들을 mobx로 구현합니다. 전역 상태 State는 물론이고, Room.usersRoom.messages 등 UI와 동적으로 바인딩 될 필요가 있는 값들이 있습니다. 이러한 값들은 mobx의 observable로 설계해서 추후에 리액트 컴포넌트를 만들 때 값의 변화에 따라 다른 조작 없이 UI가 변경 될 수 있도록 하겠습니다.

chat-demo 2chat-demo 2

우선 전역 상태와 위의 타입들과 상응하는 모델을 정의하고, 단순히 생성자만을 정의했습니다. 또한 앱 실행시 상태를 초기화하면서 window.location.pathname으로 부터 고정된 방 id 패러미터를 읽어오기로 했습니다. 이에 react-router를 제거하고, 백엔드에서는 404 라우팅을 제거하고, 정적 파일을 제외한 모든 path에 대한 요청에 대해서 동일한 리액트 앱을 서빙하도록 바꾸었습니다. 또한 뼈대에서 꾸려놓았던 SSR에 대한 지원을 모두 제거했습니다.

UI 골격 및 분기

chat-demo 3 (Prompt)chat-demo 3 (Prompt)
chat-demo 3 (Lobby)chat-demo 3 (Lobby)
chat-demo 3 (Room)chat-demo 3 (Room)

react-router 없이 전역 상태만으로 메인 UI를 분기합니다. 백엔드 없이 모든 시나리오를 검토하기 위해서 더미 데이터를 만들고, 전역 상태와 UI를 연결, 조작해봅니다. 이제 백엔드 없는 앱이 거의 다 완성되었습니다.

UI 구현

백엔드를 시작하기 이전에, 먼저 제가 좋아하는 semantic-ui의 컴포넌트를 이용해서 UI를 정교하게 다듬어 보겠습니다. 먼저 기존 보일러플레이트에 웹팩 설정이 미비하기 때문에 CSS를 함꼐 번들 할 수 있도록 웹팩 설정을 구성해주고, 무의미하게 섞여있는 잡다한 패키지를 정리해줍니다. (아무래도 보일러플레이트를 잘못 골랐습니다.)

이후 시맨틱 UI의 컴포넌트를 하나 하나 적용해주고, UX를 향상시킬 폼 검증이나, 채팅 창에 자동 포커싱, 도배 방지, 채팅창 자동 스크롤 등의 간단한 스크립트들을 적용해줍니다. 이제 프론트 앱은 90%가 완료되었습니다.

chat-demo 4 (Prompt)chat-demo 4 (Prompt)

chat-demo 4 (Lobby)chat-demo 4 (Lobby)

chat-demo 4 (Room)chat-demo 4 (Room)

백엔드 구현

백엔드는 설계적으로 크게 고민 할 것이 없습니다. 왜냐하면 프론트를 구현하면서 비지니스 로직에 필요한 모든 API 명세를 가늠했었기 떄문에 이를 정교하게 다듬어서 API를 구현하면 되겠습니다. 아마도 물론 빈틈이 아주 많이 발견될 것입니다.

  • 로그인하기
  • 방에 입장하기
  • 모든 방의 목록을 받아오기
  • 방을 개설하기
  • 방의 메세지를 갱신
  • 방에 메세지를 발송
  • 방을 나가기

먼저 백엔드에 프로토콜의 골격을 만들고나서, API를 하나씩 구현해 나가면서, 프론트와 엮어가며 TODO로 주석처리해 놓은 부분들을 하나씩 지워나가겠습니다.

소켓 서버와 로그인

본 채팅 앱에서는 최초 프론트 앱을 서빙한 이후로 HTTP 통신 없이 모든 네트워킹을 WS 통신으로 처리합니다. 이에 별도 AJAX 통신 없이 웹 소켓간에 JSON을 주고 받음으로써 API를 구분하려 합니다.

최초 로그인하기의 경우는 보통의 웹 서비스의 로그인이라는 의미와는 다르게 웹 소켓 서버와의 최초 연결, 즉 connection 이벤트를 발생시키고 logined 이벤트를 기다립니다. 비동기적인 일련의 프로세스이기에 모든 과정을 Socket이라는 클래스 안에 Promise로 감싸서 캡슐화합니다.

src/client/core/Socket.js

class Socket {
  constructor({ state }) {
    this.io = null;
    this.state = state;
  }

  login = ({ nickname, avatarUrl }) => {
    return new Promise((resolve, reject) => {

      // connect to ws server
      console.log('login')
      this.io = io(window.location.origin, {
        query: { nickname, avatarUrl },
      });

      // wait logined event
      this.io.once('logined', (userData) => {
        console.log('logined', userData);
        this.state.setUser(userData);
        resolve();
      })
    })
  }
}

이에 대응되는 백엔드의 Socket의 connection 핸들러는 다음과 같습니다. 단순히 최초 연결 생성시 socket의 id와 nickname, avatar를 다시 logined 이벤트로 되돌려 줍니다. 클로저 내에서 { id, nickname, avatarUrl }은 이후에 다른 API를 구현해나가면서 이용될 것입니다. socket-io에서 socket의 id를 String 타입으로 전달하기에 최초 설계한 타입과는 다르게 id 값은 정수가 아닌 문자열이 되겠습니다.

export default function socketHandler(socket) {

 // 1: login (connected) -> emit logined with userData
 const { nickname, avatarUrl } = socket.handshake.query;
 const id = socket.id;
 
 socket.emit('logined', { id, nickname, avatarUrl });
}

위처럼 모습으로 첫번째 API Socket.login을 구현했습니다. Socket 클래스가 전역 state에 의존성을 가지면 앱의 흐름을 더욱 자연스럽게 만들 수 있습니다. 기존의 Prompt UI는 state.setUser 함수를 주입 받았지만, 이후로는 socket.login 함수를 주입 받아서 컴포넌트 내부에서 state.user 전역 상태를 조작하지 않도록 합니다.

src/clients/components/App.js

@observer
export default class App extends React.Component {
  render() {
    const { state, socket } = this.props;

    if (!state.user) {
      return (
        <Prompt
          login={socket.login}
        />
      )
    }
    // ...    
  }
}

모든 방의 목록을 받아오기

다음으로 방의 목록을 받아봅니다. 클라이언트가 fetchRooms 이벤트를 보내면 서버는 rooms 이벤트에 데이터를 실어 보내줍니다. 더미데이터를 지워주고, 당장 Room 데이터가 하나도 없기에 메모리에 const rooms = []를 두고 이 데이터를 전달해주기만 하면 현시점에서는 충분합니다.

프로젝트를 끝내고 교재를 검수하고 있습니다. 위의 단발성 fetchRooms API는 결과적으로 부적절한 디자인이 되었습니다. 최종에 Socket.joinLobby/leaveLobby API를 구현하여 로비에서 실시간으로 방 목록을 업데이트하는 방식으로 대체됩니다.

방을 개설하기

다음으로 데이터를 넣어보고 테스트를 계속하기 위해서, 방을 개설하는 부분을 먼저 구현합니다. createRoom 이벤트를 보내면 서버는 room 이벤트에 데이터를 실어 보내줍니다. 차후에 서버에서 보내는 room 이벤트는 방의 접속자 정보가 변경될 떄, 혹은 방에 처음 접속 할 때도 공통으로 사용하겠습니다. 이쯤되면 서버에서 Room 데이터를 어떻게 다룰지 구조화 할 필요가 있습니다. 클라이언트 쪽과 비슷하게 class로 구현하여 계속해서 인메모리에 보관합니다.

만약 이 채팅 앱이 아~주 거대해져서 여러 노드를 묶어 클러스터로 띄워야한다면, 혹은 메세지를 모두 파일시스템에 백업하고 싶다면? 등? 데이터를 중앙으로 분리해야 하는 경우엔 DBMS에 문자열로 직렬화해서 담아보고 ORM으로 랩핑하는 시도를 해 볼 수도 있겠습니다.

src/server/Room.js

class Room {
  static instances = [];

  constructor({ title, user }) {
    this.id = Room.instances.length + 1; // count from 1
    this.title = title;
    this.users = [user];
    this.messages = [];
    Room.instances.unshift(this);
  }
}

export default Room;

부분 핫리로딩

개발하다보니 프론트처럼 핫리로딩이 지원되지 않으니 빈번하게 서버를 켰다 끌 수도 없는 노릇이고 여간 불편한게 아닙니다. 이를 해결하기 위해서 프로덕션 모드가 아닌 경우에는 새로운 웹소켓 커넥션이 생길 때마다 노드 프로세스에 캐시된 특정 모듈들을 메모리에서 해제하고 다시 로드하는 방편으로 핫로딩을 구현합니다.

if (process.env.NODE_ENV != 'production') {
  const fs = require('fs');
  io.on('connection', function(...args) {
    // Dynamically reload socket handler for fast deveolpment environment
    // Invalidate all cached module in current directory
    fs.readdirSync(__dirname).forEach(file => {
      delete require.cache[require.resolve(`./${file}`)];
    });
    return require('./socket').default.apply(null, args);
  });
} else {
  io.on('connection', require('./socket').default);
}

프로젝트 종료 후 교재를 검수중입니다. 이 핫리로딩이 바로 아래에서 문제를 일으킵니다. 분리된 DB 없이 Room 모듈내에 지역 변수에 모든 데이터를 보관하기에, 새로운 연결이 생성 될 떄마다 모듈이 계속 리셋되면서 데이터가 보존되지 않습니다.

방에 들어가고 나가기

Socket.enterRoom/leaveRoom을 만들어 API를 콜한 이후에 업데이트된 방의 유저 명단을 전달 받고 State.setActiveRoomId를 통해 전역 상태를 변경했습니다. 채팅앱에서 지금 최초로 브로드캐스팅이 쓰였습니다. 또한 방을 나가지 않고서 소켓 연결이 종료되는 경우에도 leaveRoom과 동일한 처리를 해줍니다.

로비에 들어가고 나가기

2번째로 구현한 fetchRooms가 방 목록이나 방의 멤버를 실시간으로 갱신해주지 못하고 있습니다. 위에서는 인터벌을 주고 주기적으로 확인하는 식으로 작성해두었는데, 완성도가 떨어집니다. 이제는 enterLobby, leaveLobby라는 API를 통해 브로드캐스팅 채널에 들어가고, 나가는 방식으로 변경하도록 하겠습니다. lobby 브로드캐스팅 채널에 입장한 유저는 실시간으로 모든 rooms의 이벤트를 받습니다.

메세지 보내고 받기

방에 메세지를 발송하고 방의 메세지를 실시간으로 수신하는 API를 완성하면 드디어 프로젝트가 완료됩니다. 그리고 fetchRooms API를 joinLobby/leaveLobby로 대체하고, 마지막으로 clearEmptyRooms API를 구현했습니다.

완성 및 배포

이제 충분한 테스를 마치고 서비스를 배포하려합니다. 데모용으로 만들었으니 간편하게 heroku를 통해서 배포하려고 합니다. 코드를 한줄만 수정하면 그대로 heroku에 배포 할 수가 있습니다. 3000으로 고정했던 포트 번호를 환경변수에서 읽어 올 수 있도록 process.env.PORT로 변경해줍니다.

한 줄이 아니라 세 줄이네요. 이렇게 프로젝트를 완료하고 배포까지 해봤습니다. 초기 기획이 조금만 더 탄탄했더라면 하는 생각이들지만, 한번 로비가 있는 채팅방을 만들어 보았으니 추후 실제 커리큘럼을 연재 할 때는 더욱 괜찮은 골조를 만들 수 있지 않을까합니다. 감사합니다.

chat-demo finalchat-demo final

완성된 데모: https://codeflow-chat.herokuapp.com

저자

김동욱

개발 경력 약 10년, 전문 분야는 웹 및 각종 응용 소프트웨어 플랫폼입니다. Codeflow를 운영하고 있습니다.

02월 07일 업데이트

지원되지 않는 웹 브라우저거나 예기치 않은 오류가 발생했습니다.