Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- OpenAPI프로젝트
- webServlet
- 그래프탐색
- Queue
- 탄력근무
- 백엔드스쿨
- 제로베이스
- 백엔드
- YBM전화영어
- npm
- 자료구조
- 탐색알고리즘
- array
- Node.js
- Spring
- 개발자
- BFS
- 백엔드공부
- 전화영어
- 시급합니다
- maven
- 내돈내산
- 자바스크립트
- JavaScript
- 프로젝트진행
- OAuth
- 최단경로문제
- 교육철학과 교육사
- java
- 원격근무
Archives
- Today
- Total
개발자취
TIL | 토이프로젝트 1 - 채팅서비스 본문
1. 프로젝트 설계
1.1 요구사항 설정
- 실시간 채팅 서비스를 간단한 형태로 제작
- 인증처리는 없음
- 채팅 내역은 데이터베이스에 저장하고, 이를 활용할 수 있도록 함.
1.2 Frontend UI 디자인
- 채팅 내역은 polling(폴링) 형태로 처리하지 않고, 실시간으로 채팅내역을 저장할 수 있는 구조로 한다.
- *폴링 : 실시간으로 장치 또는 프로그램의 처리상태를 주기적으로 체크하는 자료처리방식
- Pug : Template engine
- TailwindCss : css framework
1.3 Backend
- Web Socket : 서버-클라이언트가 실시간으로 정보를 주고받을 수 있는 통신 프로토콜(웹 표준에 의하면 2015년부터 완벽하게 사용하게 되었음, 모든 브라우저에서 지원하지 않았을 때는 socket IO나 특수한 방식으로 사실상 polling방식으로 처리하였음), Live networking
- Koa : express를 만들었던 팀이 atomic하게 미니멀하여 필요한 메서드만 담은 웹 프레임워크, 좀 더 현대적(?)이다.
- MongoDB : 채팅 기록을 브로드캐스팅하게 채팅 창에 존재하는 모든 client에게 동일한 정보를 전달하도록 함.
2. 프론트앤드 디자인
2.1 UI 구현
- main.pug
- chat, form, input, send로 Id 값을 붙이고, client.js에서 Id에 따른 eventlistener를 설정함.
html
head
link(href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet")
body
h1.bg-gradient-to-r.bg.from-purple-100.to-gray-200.p-16.text-4xl 나의 채팅 서비스
div.p-16.space-y-8
div#chats.bg-gray-100.p-2 채팅 목록
form#form.space-x-4.flex
input#input.flext-1.border.border-gray-200.p-4.rounded(placeholder="채팅을 입력해 보세요.")
button#send.bg-blue-600.text-white.p-2.rounded 보내기
script(src="/public/client.js")
- client.js
// @ts-check
//IIFE
;(() => {
const socket = new WebSocket(`ws://${window.location.host}/ws`)
const formEl = document.getElementById('form')
const chatsEl = document.getElementById('chats')
/** @type {HTMLInputElement | null} */
// @ts-ignore
const inputEl = document.getElementById('input') // 채팅 창에 입력하는 값
if (!formEl || !inputEl || !chatsEl) {
throw new Error('Init failed!')
}
/**
* @typedef Chat //타입을 명시한다.
* @property {string} nickname
* @property {string} message
*/
/**
* @type {Chat[]}
*/
const chats = []
const adjectives = ['귀여운', '깜찍한', '조그만한', '소중한', '까칠한']
const animals = ['사자', '호랑이', '표범', '독수리', '돌고래']
/**
* @param {string[]} array
* @returns {string}
*/
function pickRandom(array) { // 채팅 닉네임 생성
const randomIdx = Math.floor(Math.random() * array.length)
const result = array[randomIdx]
if (!result) {
throw new Error('array length is 0.')
}
return array[randomIdx]
}
const myNickname = `${pickRandom(adjectives)} ${pickRandom(animals)}`
formEl.addEventListener('submit', (event) => { // 서버로 채팅내용 전송
event.preventDefault()
socket.send(
JSON.stringify({
nickname: myNickname,
message: inputEl.value,
})
)
inputEl.value = ''
})
const drawChats = () => {
chatsEl.innerHTML = ''
chats.forEach(({ nickname, message }) => {
const div = document.createElement('div')
div.innerText = `${nickname}: ${message}`
chatsEl.appendChild(div)
})
}
socket.addEventListener('message', (event) => {
const { type, payload } = JSON.parse(event.data)
if (type === 'sync') {
const { chats: syncedChats } = payload
chats.push(...syncedChats)
} else if (type === 'chat') {
const chat = payload
chats.push(chat)
}
drawChats()
//alert(event.data)
})
})()
- IIFE ; 즉시 실행 함수 표현(IIFE, Immediately Invoked Function Expression)은 정의되자마자 즉시 실행되는 Javascript Function
3. Backend 구현
3.1 Database
3.1.1 DB 관련 환경변수
- DB에 접근하기 위해서는 user id와 pw가 필수적인데, 이를 변수로 비공개하여 node에서 관리할 필요가 있다.
- 환경변수 설정 방법2) root에 .env 파일 생성4) .js파일에서 require('dotenv').config()를 import한 후, ${require('dotenv').config().env.MONGO_PASSWORD}로 사용.
// 변수를 할당해서 적당히 예쁘게 사용할 것(?) - 3) .env 파일에 ex) MONGO_PASSWORD = coconut 로 환경변수 생성
- 1) $ npm i dotenv --save
3.1.2 DB 연동
- mongo.js는 main.js에서 유저의 채팅메세지를 담는 미들웨어로 사용됨.
const config = require('dotenv').config()
const { MongoClient } = require('mongodb')
const uri = `mongodb+srv://${config.parsed.MONGO_USER}:${config.parsed.MONGO_PASSWORD}@${config.parsed.MONGO_CLUSTER}.gwyplzx.mongodb.net/?retryWrites=true&w=majority`
const client = new MongoClient(uri, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
module.exports = client
3.2 WebSocket Chatting
- client.js, mongo.js, main.pug 를 모듈로 불러와서 사용.
// @ts-check
const Koa = require('koa')
const path = require('path')
const Pug = require('koa-pug')
const route = require('koa-route')
const serve = require('koa-static')
const websockify = require('koa-websocket')
const mount = require('koa-mount')
const mongoClient = require('./mongo')
const app = websockify(new Koa())
// @ts-ignore
// eslint-disable-next-line no-new
new Pug({
viewPath: path.resolve(\_\_dirname, './views'),
app,
})
app.use(mount('/public', serve('src/public')))
app.use(async (ctx) => {
await ctx.render('main')
})
const \_client = mongoClient.connect()
async function getChatsCollection() {
const client = await \_client
return client.db('chat').collection('chats') // 채팅 내용을 콜렉션에 담음
}
// Using routes
app.ws.use(
route.all('/ws', async (ctx) => { // chatsCollection 안에 chat을 넣는 형태
const chatsCollection = await getChatsCollection()
const chatsCursor = chatsCollection.find(
{},
{
sort: {
createdAt: 1, // 오름차순 정렬
},
}
)
const chats = await chatsCursor.toArray() //채팅을 다 긁어모아서 array로 만들어서 메세지로 채팅목록에 보내줌
ctx.websocket.send(
JSON.stringify({
type: 'sync',
payload: {
chats,
},
})
)
//ctx.websocket.send('Hello, Client!') // string
ctx.websocket.on('message', async (data) => { //websocket을 사용하여 message를 전송한다.
// @ts-ignore
//if (typeof data !== 'string') {
// return
//}
/** @type {Chat} */
const chat = JSON.parse(data.toString()) // 서버가 재시작 했을 때 이전 데이터가 그대로 보존되기 위해 추가하는
await chatsCollection.insertOne({ // 필자의 작업 환경에서는 입력 문자들이 buffer로 출력되어, toString()을 추가함.
...chat,
createdAt: new Date(),
})
const { message, nickname } = chat
const { server } = app.ws
if (!server) {
return
}
server.clients.forEach((client) => { //broadcast : 주소에 접속한 user들이 같은 내용을 보도록함.
client.send(
JSON.stringify({
type: 'chat', //서버->클라이언트 보내는 메세지
payload: {
message,
nickname,
},
})
)
})
console.log(`${nickname} : ${message}`) // buffer to string
})
})
)
app.listen(5000)
4. 구현 결과
- 구현된 결과는 페이지가 업데이트 되어도 db 내용을 그대로 출력한다.
- User는 새창에서 진입할 때도 broadcast 방식으로 동일한 채팅 내용을 확인할 수 있다.
*패스트캠퍼스 강의를 수강한 내용을 토대로 정리하였습니다.
'개발 > Dev | 웹개발' 카테고리의 다른 글
TIL | 토이프로젝트 3 - Github 관리 CLI 만들기 (0) | 2022.07.27 |
---|---|
TIL | 토이프로젝트 2 - 이미지 리사이징 서버 (0) | 2022.07.27 |
TIL | 자동화 테스트 모듈 파헤치기 (0) | 2022.07.26 |
TIL | REST와 라우팅에 대한 정리 (0) | 2022.07.26 |
TIL | Express.js Framework / 미들웨어 (0) | 2022.07.23 |
Comments