Pusher 를 이용한 실시간 채팅 웹앱 만들기 #5 (w/ Laravel, Nuxt)
Pusher 를 이용한 실시간 채팅 웹앱 만들기 #5 (w/ Laravel, Nuxt)
이번 글에서는 채팅 앱 프론트엔드 코드를 작성하고 Pusher
, 만들어놓은 Laravel
백엔드와 각각 연결해서 가장 간단한 채팅 앱을 만들어보겠습니다.
프론트엔드 구현
Pusher
연결을 위한 패키지 설치
우리의 Nuxt
프로젝트에서 Pusher
를 연결하여 채널을 구독하기 위해서 pusher-js
패키지와 laravel-echo
패키지를 설치합니다.
npm install --save laravel-echo pusher-js
정보
Pusher
와 연결하려면pusher-js
만 설치하여 연결해도 되지만laravel-echo
패키지는Laravel
에서 보내는 이벤트를 더 편하게 받을 수 있도록pusher-js
를 감싼 패키지입니다.laravel-echo
의 자세한 내용은Laravel
문서를 참고하세요.pusher-js
만 이용하여 연결하는 방법은 Pusher 문서를 참고하세요.
ChatApp 컴포넌트 작성
chat
채널에 구독하고 ChatSent
Laravel
이벤트를 수신하여 화면에 목록으로 보여주며, 다른 사용자들에게 메세지를 보낼 수 있는 ChatApp
컴포넌트를 작성하겠습니다.
Nuxt
에서 컴포넌트는 components/
디렉토리에 작성합니다. 이 디렉토리에 있는 .vue
파일들은 nuxt.config.js
파일의 components
옵션이 true
라면 자동으로 임포트됩니다. (기본값)
components/ChatApp.vue
파일을 만들고 컴포넌트를 작성합니다.
<template>
<div class="rounded shadow-lg border p-4">
<!-- 채팅 제목 라인 -->
<h1 class="text-lg font-bold">채팅</h1>
<!-- 구분선 -->
<div class="bg-gray-200 my-2" style="height: 1px"></div>
<!-- 대화 로그 -->
<ol class="rounded border p-2 mb-2" style="min-height: 200px">
<!-- 채팅 아이템 -->
<li v-for="chat in chats" :key="chat.chatAt" class="flex p-1">
<!-- 송신자 -->
<h2 class="font-bold tracking-wide mr-2">{{ chat.sender }}</h2>
<!-- 내용 -->
<div class="tracking-tight">
{{ chat.message }}
</div>
</li>
<li v-if="sending" class="text-right text-sm p-1">보내는 중..</li>
</ol>
<div class="flex flex-wrap text-sm">
<div class="w-full mb-1 sm:w-1/4 sm:mb-0 sm:pr-1">
<input class="w-full rounded border p-1 focus:outline-none focus:ring-1 focus:ring-blue-500" type="text" ref="sender" placeholder="이름" />
</div>
<div class="w-3/4 sm:w-2/4 sm:px-1">
<input class="w-full rounded border p-1 focus:outline-none focus:ring-1 focus:ring-blue-500" type="text" ref="message" placeholder="메세지" @keydown.enter="send" />
</div>
<div class="w-1/4 pl-1">
<button class="w-full h-full rounded bg-blue-500 text-white font-bold" @click="send">전송</button>
</div>
</div>
</div>
</template>
<script>
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
export default {
data() {
return {
token: '',
chats: [],
echo: null,
sending: false,
};
},
methods: {
async send() {
if (!this.sending) {
const data = {
sender: this.$refs.sender.value,
message: this.$refs.message.value,
};
if (!this.validate(data)) {
alert('입력을 확인해주세요!');
return;
}
this.sending = true;
this.$refs.message.value = '';
this.$refs.message.focus();
await this.$axios.post('http://localhost:8000/chat/send', data);
this.sending = false;
}
},
validate(data) {
if (data.sender === '') return false;
if (data.message === '') return false;
return true;
},
onChatSent(event) {
this.chats.push(event);
},
connect() {
if (!window.Pusher) window.Pusher = Pusher;
if (!this.echo) {
this.echo = new Echo({
broadcaster: 'pusher',
key: 'e6e27d54525dcd0b0de1',
cluster: 'ap3',
});
}
this.echo.channel('chat').listen('ChatSent', (e) => {
this.onChatSent(e);
});
},
disconnect() {
this.echo.leaveChannel('chat');
},
},
mounted() {
this.connect();
window.addEventListener('beforeunload', this.disconnect);
this.$refs.sender.focus();
},
};
</script>
ChatApp
컴포넌트가 마운트되면 laravel-echo
라이브러리를 사용해 chat
채널의 ChatSent
이벤트를 구독하도록 했습니다. ChatSent
이벤트를 받으면 chats
배열에 추가하고 이 배열을 채팅창의 느낌으로 화면에 보여줬습니다.
하단의 텍스트박스에 이름과 메세지를 입력하고 엔터를 치거나 전송 버튼을 누르면 send
메서드를 통해 입력을 검증한 뒤 이전 포스트에서 만들어놓은 /chat/send
URL로 보내 브로드캐스팅하여 다른 구독자들에게 알리도록 했습니다.
index 페이지 작성
ChatApp
컴포넌트를 만들었으니, 이 컴포넌트를 보여줄 페이지가 필요합니다.
Nuxt
에서 페이지 컴포넌트는 pages/
디렉토리에 작성합니다. 해당 디렉토리에 존재하는 .vue
파일들은 자동으로 자신의 파일명으로 라우트(vue-router
)를 생성합니다. 참고
우리가 만든 컴포넌트를 /
Path 로 들어오면 바로 보이도록, pages/
디렉토리에 index.vue
파일을 만들어서 ChatApp
컴포넌트를 적당히 렌더링하겠습니다.
<template>
<div class="container mx-auto p-3">
<ChatApp class="mx-auto" style="max-width: 500px" />
</div>
</template>
<script>
export default {}
</script>
Nuxt 개발 서버 실행 및 테스트
간단하게 프론트엔드 코드를 모두 작성했습니다.
이제 Nuxt
개발 서버를 실행해 우리가 만든 프론트엔드 코드를 제공하는 Node
서버를 실행시켜 접속해봅시다.
npm run dev
위 명령을 실행하면 잠시 뒤 서버가 실행됩니다. 기본적으로 http://localhost:3000 에서 실행됩니다. 크롬으로 접속해봅시다.
우리의 첫 채팅 앱이 완성되었습니다!
여러개의 탭이나 창을 띄워 여러개의 페이지를 띄우고, 그 중 하나의 페이지에서 이름과 메세지를 입력하고 전송을 눌러보세요.
하나의 페이지에서 메세지를 전송하면, 다른 페이지들에 그 채팅이 전달됩니다! 성공적입니다.
그런데 지금 서버의 코드는 DB를 사용하지 않아서 채팅 기록들이 남지 않고 새로고침하면 사라집니다.
Laravel
서버 DB 연동
간단하게 채팅 기록을 저장하는 테이블을 만들어 채팅들을 저장하고, 프론트에서는 채팅 앱이 처음 로딩될 때 서버에 요청하여 DB에 있는 기존 채팅들을 가져와 목록에 추가시켜보겠습니다.
Chat
모델 및 마이그레이션 생성
Laravel
은 데이터베이스를 다룰 때, 일반적으로 SQL을 직접 사용하지 않고, Eloquent
ORM을 사용하여 테이블을 모델로 추상화하여 다룰 수 있는 인터페이스를 제공합니다.
Laravel
의 ORM 기능을 사용하기 위해 우리의 채팅 기록을 표현할 Chat
모델을 만들겠습니다. 모델이 만들어지는 폴더는 Laravel
8.x 기준 /app/Models
디렉토리입니다.
php artisan make:model Chat -m
모델을 만들면서 -m
옵션을 붙였습니다. -m
옵션을 붙이면 모델에 대한 마이그레이션 파일도 같이 생성합니다.
마이그레이션은 데이터베이스 스키마 조작을 프로그래밍적으로 정의해두고 실행할 수 있도록 해주는 Laravel
의 기능입니다. 다른 환경 간에 쉽게 스키마를 동기화할 수 있도록 사용합니다. 참고
만들어진 마이그레이션 파일에 우리의 채팅 기록 데이터에 필요한 Column
들을 추가하여 chats
테이블에 대한 마이그레이션을 작성합니다.
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateChatsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('chats', function (Blueprint $table) {
$table->id();
$table->string('sender');
$table->text('message');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('chats');
}
}
보낸 사람의 이름을 저장하는 sender
, 보낸 메세지를 저장하는 message
Column
을 추가했습니다.
마이그레이션을 모두 작성했으면, 아래 명령을 실행해 추가한 마이그레이션을 실행합니다. 해당 마이그레이션이 실행되면 chats
테이블이 생성됩니다.
php artisan migrate
이제 Chat
모델 파일을 보겠습니다.
Laravel
의 모델은 기본적으로 모델 이름을 복수형으로 바꾼 것의 snakeCase
을 이름으로 가진 테이블을 찾아 연결됩니다. 모델을 만들때 -m
옵션을 붙여 생성하면 생성되는 마이그레이션의 테이블 이름은 기본적으로 이 형태를 띕니다. 따라서 모델이 어느 테이블에 대한 건지 테이블 이름을 지정해 줄 필요가 없습니다.
나중에 대량 할당(Mass Assignments)를 사용하기 위해 guarded
속성을 []
로 설정하여 모든 프로퍼티를 대량 할당 가능하게 합니다. 참고
모델 준비는 이것으로 끝났습니다. 이제 ChatController
를 수정하면서 Chat
모델을 사용하여 DB
와 연동해보겠습니다.
ChatController
개선
기존 send
메서드에 validation을 추가하고, Chat
모델을 이용해 데이터베이스에 새 채팅 데이터를 넣은 후 브로드캐스팅 하도록 수정합니다.
protected $rules = [
'sender' => 'required',
'message' => 'required'
];
public function send()
{
$validated = request()->validate($this->rules);
$chat = Chat::create($validated);
broadcast(new ChatSent($chat->sender, $chat->message, now()));
return response([
'result' => 'success',
'chat' => $chat
]);
}
프론트에서 ChatApp
이 처음 로드될 때, 서버 데이터베이스에 담겨있는 기존 대화 기록들을 가져올 수 있어야 합니다. 이를 위한 load
메서드를 만듭니다.
public function load()
{
$chats = Chat::orderBy('created_at')->get();
return response([
'result' => 'success',
'chats' => $chats
]);
}
라우트 추가
새로 만든 load
메서드에 대한 라우트를 만들어줍니다. [GET] /chats
요청을 연결하겠습니다.
Route::get('/chats', [ChatController::class, 'load']);
백엔드 쪽 준비는 끝났습니다.
ChatApp
컴포넌트 개선
<template>
<!-- ... -->
</template>
<script>
// ...
export default {
data() {
return {
token: '',
chats: [],
echo: null,
sending: false,
};
},
// ...
async fetch() {
const { data } = await this.$axios.get('http://localhost:8000/chats');
this.chats = data.chats;
},
// ...
};
</script>
기존에서 추가한 부분은 fetch
메서드 하나입니다.
fetch
메서드를 추가하여 그 내부에서 [GET] /chats
요청을 하고 그것을 채팅 목록에 초기화합니다. fetch
메서드는 Nuxt
에서 제공해주는 lifecycle hook 입니다. SSR 모드에서, 페이지를 렌더링 하기 전에 API 호출 등을 통해 데이터를 가져올 수 있게 합니다.
완료
완성입니다. Laravel
프로젝트를 php artisan serve
명령으로 띄우고, Nuxt
프로젝트에서 npm run dev
명령을 실행하고 http://localhost:3000 에 들어가서 채팅을 몇번 쳐보고 새로고침을 눌러보세요.
이전과는 다르게 페이지가 다시 로딩되어도 기존 채팅 기록이 사라지지 않는 것을 확인할 수 있습니다.