오늘은 Node.js, express와 Socket.io를 이용하여 웹에 실시간 채팅 서비스를 구현해 보았다.
소스코드 설명
파일 구조
NODE_CHAT
├─ node_modules
├─ public
│ ├─ css
│ │ └─ style.css
│ ├─ js
│ │ └─ main.js
│ └─ index.html
├─ server.js
├─ package-lock.json
└─ package.json
server.js
const path = require('path')
const express = require('express')
const app = express()
const SocketIO = require('socket.io')
var os = require('os');
Path 모듈은 Node.js에 내장되어 있는 라이브러리이기에 별도로 설치하지 않아도 바로 불러와서 사용할 수 있다
Express는 웹 및 모바일 애플리케이션을 위한 기능을 제공하는 간결하고 유연한 Node.js 웹 애플리케이션 프레임워크이다
Socket.IO는 실시간 웹 애플리케이션을 위한 Javascript 웹소켓 라이브러리이다
Os는 운영체제와 시스템의 정보를 가져올 수 있는 모듈이다
app.use((express.static(path.join(__dirname, "public"))))
app.set("port", process.env.PORT || 3000);
const server = app.listen(app.get("port"), () =>{
console.log("server address:", "http://" + getIp() + ":" + app.get("port"));
})
const io = SocketIO(server);
// root
app.get("/", (req,res)=>{
res.sendFile(path.join(__dirname, "public/index.html"));
})
__dirname : 현재 폴더 경로
join : 운영체제에 맞춰 경로 지정하기
express.static
- express 변수에 포함된 stastic이라는 메서드를 미들웨어로서 로드해준다
- static의 인자로 전달되는 파일경로는 밑에 있는 데이터들은 웹브라우저의 요청에 따라 서비스를 제공해줄 수 있다
app.set("port", process.env.PORT || 3000);
- Node.js에서 환경 변수에 접근할 때는 process.env라는 내장 자바스크립트 객체를 사용한다
- process는 전역 객체여서 별도로 임포트 할 필요 없으며 애플리케이션 어디에서든지 접근이 가능하다
- 위 코드는 Node.js에서 환경변수에 PORT를 3000으로 설정하는 코드이다.
const io = SocketIO(server); : 웹소켓 생성
getIp() : IPv4주소를 찾아 반환
function getIp() {
var ifaces = os.networkInterfaces();
var ip = '';
for (var dev in ifaces) {
var alias = 0;
ifaces[dev].forEach(function(details) {
if (details.family == 'IPv4' && details.internal === false) {
ip = details.address;
++alias;
}
});
}
return ip;
}
io.on('connection', (socket)=>{
socket.on("message", (data)=>{
socket.broadcast.emit("message", data)
})
})
소켓 연결 후 클라이언트로부터 메세지를 받으면 broadcast해준다
broadcast : 어느 한 클라이언트로부터 받은 메세지를 현재 접속중인 모든 클라이언트한테 메세지를 전달하는 것
main.js
const socket = io();
const btnSend = document.getElementById("send-message");
const message = document.getElementById("message-area");
const boxMessages = document.getElementById("chat-box");
btnSend : 메세지 보내는 버튼
message : 보내는 메세지
boxMessages : 채팅창
btnSend.addEventListener("click", () => {
if (message.value == "") {
message.focus();
}
else {
boxMessages.innerHTML += `
<div class="chat from-message">
<div class="detalles">
<p>${message.value}</p>
</div>
</div>
`;
scrollBottom();
socket.emit("message", { msg: message.value });
message.value = null;
}
});
if문 True : 다시 입력 대기
if문 False : 메세지를 보내는 클라이언트 채팅창에 메세지를 추가하고 scrollBottom() 호출 후 메세지 변수 값 초기화
scrollBottom() : 채팅창에 스크롤을 가장 아래로하는 함수
function scrollBottom() {
boxMessages.scrollTop = boxMessages.scrollHeight;
}
function enterkey() {
keyenter = event.keyCode;
if (keyenter == 13) {
btnSend.click();
scrollBottom();
}
}
window.onkeydown = enterkey;
ENTER Key를 누르더라도 메세지 보내는 버튼과 같은 로직을 수행하게끔 하는 함수
socket.on("message", (data) => {
boxMessages.innerHTML += `
<div class="chat to-message">
<div class="detalles">
<p>${data.msg}</p>
</div>
</div>
`;
scrollBottom()
});
서버로부터 받은 메세지를 채팅창에 추가 후 scrollBottom() 호출
전체 소스코드
index.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="css/style.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.1/font/bootstrap-icons.css"/>
<title>Node Chat</title>
</head>
<body>
<div class="chat-wrap">
<div class="header">
<div class="detalles">
<span id="nombre-to-name">Chat Room</span>
</div>
</div>
<div class="chat-box" id="chat-box"></div>
<div class="text-area">
<button type="button" id="btn-archive">
<i class="bi bi-paperclip"></i>
</button>
<input type="text" class="mensaje" id="message-area" placeholder="텍스트를 입력하세요." />
<button type="button" class="btn-stycker">
<i class="bi bi-emoji-smile"></i>
</button>
<button type="button" id="send-message">
<i class="bi bi-send"></i>
</button>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script src="js/main.js"></script>
</body>
</html>
style.css
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+Hebrew:wght@100;200;300;400;500;600;700;800;900&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
scroll-behavior: smooth;
font-family: 'Poppins', sans-serif;
text-decoration: none;
}
body {
background: linear-gradient(to left, #3140FF, #47ADFF);
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
width: 100%;
}
img.img-perfil-user {
object-fit: cover;
max-width: calc(100% - 70px);
}
img.img_perfil {
border-radius: 50%;
width: 50px !important;
}
p {
display: block;
margin-inline-start: 0px;
margin-inline-end: 0px;
}
.chat-wrap {
width: 100%;
max-width: 654px;
background: #F7F7F7;
margin: auto;
border-radius: 15px;
box-shadow: 0 0 128px 0 rgb(0 0 0 / 10%), 0 32px 64px -48px rgb(0 0 0 / 50%);
}
.chat-wrap .header {
width: 100%;
height: 60px;
padding: 18px 30px;
display: flex;
align-items: center;
}
.chat-wrap .header .img-perfil-user {
width: 45px;
height: 45px;
margin: 0px 15px;
border-radius: 50px;
object-fit: contain;
box-shadow: 0px 0px 10px rgba(128, 128, 128, 0.2);
background: #ffff;
}
.chat-wrap .header .detalles {
flex: auto;
text-align: center;
}
.chat-wrap .header .detalles span {
font-size: 24px;
font-weight: 500;
background-image: linear-gradient(to left, #3140FF, #47ADFF);
background-clip: text;
-webkit-background-clip: text;
color: transparent;
}
.chat-wrap .chat-box {
background: #f7f7f7;
width: 100%;
min-height: 654px;
max-height: 654px;
position: relative;
overflow-y: auto;
padding: 10px 30px 20px 30px;
box-shadow: inset 0 42px 32px -32px rgb(0 0 0 / 5%), inset 0 -42px 32px -32px rgb(0 0 0 / 5%);
}
.chat-wrap .chat-box::-webkit-scrollbar {
width: 12px;
}
.chat-wrap .chat-box::-webkit-scrollbar-track {
border-radius: 10px;
}
.chat-wrap .chat-box::-webkit-scrollbar-thumb {
border-radius: 10px;
}
.chat-wrap .text-area {
width: 100%;
height: 81px;
max-height: 81px;
padding: 18px 30px;
display: flex;
justify-content: space-between;
}
.chat-wrap .text-area .mensaje {
height: 45px;
width: calc(100% - 58px);
max-height: 45px;
font-size: 16px;
padding: 0 13px;
border: 1px solid #e6e6e6;
outline: none;
border-radius: 5px 5px 5px 5px;
margin-left: 5px;
}
.chat-wrap .text-area #send-message {
color: #3140FF;
width: 55px;
border: none;
outline: none;
background: none;
font-size: 19px;
cursor: pointer;
opacity: 0.7;
pointer-events: painted;
border-radius: 0 5px 5px 0;
transition: all 0.3s ease;
}
.chat-wrap .text-area button {
color: #fff;
width: 55px;
border: none;
outline: none;
background: linear-gradient(to left, #3140FF, #47ADFF);
font-size: 19px;
cursor: pointer;
opacity: 0.7;
pointer-events: painted;
border-radius: 5px 5px 5px 5px;
transition: all 0.3s ease;
}
.chat-wrap .text-area .btn-stycker {
color: #3140FF;
width: 55px;
border: none;
outline: none;
background: none;
font-size: 19px;
cursor: pointer;
opacity: 0.7;
pointer-events: painted;
border-radius: 0 5px 5px 0;
transition: all 0.3s ease;
}
.chat-wrap .text-area button i {
font-size: 22px;
}
.chat-box .chat {
margin: 15px 0;
}
.chat-wrap .chat-box .chat p {
word-wrap: break-word;
padding: 8px 16px;
box-shadow: 0 0 32px rgb(0 0 0 / 8%), 0rem 16px 16px -16px rgb(0 0 0 / 10%);
}
.chat-wrap .from-message {
display: flex;
}
.chat-wrap .from-message .detalles {
margin-left: auto;
max-width: calc(100% - 70px);
}
.chat-wrap .from-message .detalles p {
border-radius: 18px 0px 18px 18px;
background: linear-gradient(to left, #3140FF, #47ADFF);
color: #fff;
}
.chat-wrap .to-message {
display: flex;
justify-content: flex-end;
}
.chat-wrap .to-message img.img-perfil-user {
width: 35px;
height: 35px;
border-radius: 50px;
object-fit: contain;
box-shadow: 0px 0px 10px rgba(128, 128, 128, 0.2);
background: #ffff;
}
.chat-wrap .to-message .detalles {
margin-right: auto;
margin-left: 10px;
max-width: calc(100% - 70px);
}
.to-message .detalles p {
background: #fff;
background-image: linear-gradient(to left, #3140FF, #47ADFF);
background-clip: text;
-webkit-background-clip: text;
color: transparent;
border-radius: 0px 18px 18px 18px;
}
.msg-x {
width: 400px;
text-align: center;
padding: 15px 20px;
position: absolute;
top: calc(50% - 34px);
left: calc(50% - 200px);
}
.register {
width: 350px;
display: flex;
flex-direction: column;
}
.register span {
font-size: 18px;
margin-bottom: 15px;
font-weight: 500;
color: #333;
}
.register .images {
width: 100%;
}
.register .images img {
width:60px;
height: 60px;
margin: 5px;
border-radius: 50%;
cursor: pointer;
}
.register input[type="text"] {
height: 45px;
width: 100%;
border: 0.5px solid rgb(0 0 0 / 20%);
outline: none;
padding: 10px;
background: #f1f1f1;
}
.register button {
height: 45px;
width: 100%;
border: 0.5px solid rgb(0 0 0 / 20%);
outline: none;
padding: 10px;
background: #c4c4c4;
color: #333;
cursor: pointer;
}
@media (max-width: 800px) {
body {
background: #f7f7f7;
position: relative;
height: 100vh;
}
.chat-wrap {
background: none;
box-shadow: none;
height: 100vh;
}
.chat-wrap .header {
position: fixed;
top: 0px;
background: #FFFF;
z-index: 100;
box-shadow:0 1px 6px 0 rgb(32 33 36 / 28%);
}
.chat-wrap .chat-box {
height: 100%;
background: #f7f7f7;
min-height: 100px;
max-height: 100%;
box-shadow: none;
padding-top: 81px;
padding-bottom: 81px;
}
.chat-wrap .text-area {
position: fixed;
bottom: 0px;
background: #FFFF;
box-shadow:0 1px 6px 0 rgb(32 33 36 / 28%);
}
.msg-x {
top: 50%;
}
}
main.js
const socket = io();
const btnSend = document.getElementById("send-message");
const message = document.getElementById("message-area");
const boxMessages = document.getElementById("chat-box");
btnSend.addEventListener("click", () => {
if (message.value == "") {
message.focus();
}
else {
boxMessages.innerHTML += `
<div class="chat from-message">
<div class="detalles">
<p>${message.value}</p>
</div>
</div>
`;
scrollBottom();
socket.emit("message", { msg: message.value });
message.value = null;
}
});
function enterkey() {
keyenter = event.keyCode;
if (keyenter == 13) {
btnSend.click();
scrollBottom();
}
}
window.onkeydown = enterkey;
function scrollBottom() {
boxMessages.scrollTop = boxMessages.scrollHeight;
}
socket.on("message", (data) => {
boxMessages.innerHTML += `
<div class="chat to-message">
<div class="detalles">
<p>${data.msg}</p>
</div>
</div>
`;
scrollBottom()
});
server.js
const path = require('path')
const express = require('express')
const app = express()
const SocketIO = require('socket.io')
var os = require('os');
app.use((express.static(path.join(__dirname, "public"))))
app.set("port", process.env.PORT || 3000);
const server = app.listen(app.get("port"), () =>{
console.log("server address:", "http://" + getIp() + ":" + app.get("port"));
})
const io = SocketIO(server);
// root
app.get("/", (req,res)=>{
res.sendFile(path.join(__dirname, "public/index.html"));
})
io.on('connection', (socket)=>{
socket.on("message", (data)=>{
socket.broadcast.emit("message", data)
})
})
function getIp() {
var ifaces = os.networkInterfaces();
var ip = '';
for (var dev in ifaces) {
var alias = 0;
ifaces[dev].forEach(function(details) {
if (details.family == 'IPv4' && details.internal === false) {
ip = details.address;
++alias;
}
});
}
return ip;
}
실행화면
소스코드 링크
https://github.com/levhyun/node_chat
GitHub - levhyun/node_chat: Implement simple web chat services with node.js server using socket.io
Implement simple web chat services with node.js server using socket.io - GitHub - levhyun/node_chat: Implement simple web chat services with node.js server using socket.io
github.com
'NodeJS' 카테고리의 다른 글
Nodejs 로그인&회원가입 구현 (2) | 2023.02.03 |
---|---|
Node.js 다중 클라이언트 웹 소켓 채팅 (2) | 2023.01.29 |