실시간 채팅방 만들기 (NodeJS, Express, Redis, Socket.io) - 구현하기
카테고리 없음2018. 4. 2. 01:27
app.js
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var redis = require('redis');
var client = redis.createClient(6379, "127.0.0.1");
var JSON = require('JSON');
var _ = require('underscore');
var app = express();
var server = require('http').Server(app);
var io = require('socket.io')(server);
server.listen(3001, "127.0.0.1");
function addZero(i) {
if (i < 10) {
i = "0" + i;
}
return i;
}
io.on('connection', function(socket){
var userName = socket.id;
io.to(socket.id).emit('change name', userName);
socket.on('changed name', function(receivedUserName) {
userName = receivedUserName;
io.to(socket.id).emit('change name', userName);
});
socket.on('disconnect', function(){
io.emit('leave', socket.id);
});
socket.on('send message', function(text){
var date = new Date();
client.rpush('chatLogs', JSON.stringify({
userName: socket.id,
message: text,
date: addZero(date.getHours()) + ":" + addZero(date.getMinutes())
}));
io.emit('receive message', {
userName: socket.id,
message: text,
date: addZero(date.getHours()) + ":" + addZero(date.getMinutes())
});
});
});
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')))
app.get('/', function(req, res, next) {
var chatLogs;
client.lrange('chatLogs', -10, -1, (err, charArr) => {
chatLogs = _.map(charArr, function(char){ return JSON.parse(char); });
res.render('index', {
title: 'Chat App',
chatLogs: chatLogs
});
});
});
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
index.ejs
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=0,maximum-scale=10">
<title><%= title %></title>
<link rel="stylesheet" href="/stylesheets/lib/bootstrap.min.css">
<link rel="stylesheet" href="/stylesheets/custom.ui.css">
</head>
<body>
<main id="boxApp"></main>
<script src="/javascripts/lib/require.min.js"></script>
<script src="/javascripts/app/config.js"></script>
<script src="/javascripts/app/chat.js"></script>
<script>
var chatLogs = [];
<% chatLogs.forEach(function(chatLog) { %>
chatLogs.push({
userName: '<%= chatLog.userName %>',
message: '<%= chatLog.message %>',
date: '<%= chatLog.date %>'
});
<% }); %>
</script>
</body>
</html>
config.js
require.config({
urlArgs: 'version=0.0.0',
baseUrl: "/javascripts",
paths: {
jquery : 'lib//jquery-1.12.4.min',
underscore : 'lib/underscore.min',
backbone : 'lib/backbone.min',
backbone_validation : 'lib/backbone-validation.min',
moment: 'lib/moment.min',
bootstrap: 'lib/bootstrap.min',
text: 'lib/text',
io: 'lib/socket.io'
},
shim : {
underscore : {
exports : '_'
},
backbone: {
exports: 'Backbone',
deps: ['jquery', 'underscore']
},
backbone_validation: {
exports: 'BackboneValidation',
deps: ['backbone']
},
bootstrap: {
deps: ['jquery']
}
}
});
app/chat.js
require([
'view/chat'
], function(
ChatView
) {
var chatView = new ChatView({el: '#boxApp'});
chatView.render();
})
collection/chat_logs.js
define([
'backbone',
'model/chat_log'
], function(
Backbone,
ChatLog
) {
return Backbone.Collection.extend({
model: ChatLog
})
});
model/chat_log.js
define([
'backbone'
], function(
Backbone
) {
return Backbone.Model.extend({
defaults: {
userName: null,
message: null,
date: null
}
})
});
template/chat.html
<div class="">
<textarea id="chatLog" class="form-control" readonly><% _.each(chatLogs, function(chatLog){ %>
[<%=chatLog.date%>]<%=chatLog.userName%> : <%=chatLog.message%><% }); %>
</textarea>
<form id="chat">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span id="userName" class="input-group-text"></span>
</div>
<input type="text" name="message" class="form-control" aria-label="message">
<div class="input-group-append">
<button type="submit" class="input-group-text">전송</button>
</div>
</div>
</form>
</div>
view/chat.js
define([
'backbone',
'collection/chat_logs',
'text!template/chat.html',
'io'
], function(
Backbone,
ChatLogs,
ChatTemplate,
io
) {
return Backbone.View.extend({
events: {
"submit #chat": "public"
},
template: ChatTemplate,
initialize: function() {
_.bindAll(this, 'appendMessage', 'setName');
this.collection = new ChatLogs(chatLogs);
this.socket = io.connect('http://localhost:3001');
this.socket.on("receive message", this.appendMessage);
this.socket.on("change name", this.setName);
},
render: function() {
this.$el.html(_.template(this.template)(this.collection.toJSON()));
},
public: function(e) {
var $message = this.$el.find("[name=message]");
this.socket.emit('send message', $message.val());
$message.val("").focus();
e.preventDefault();
},
appendMessage: function(result) {
var $chatLog = this.$el.find("#chatLog");
$chatLog.append("[" + result.date + "]" + result.userName + " : " + result.message + '\n');
$chatLog.scrollTop($chatLog.prop("scrollHeight"));
},
setName: function(userName) {
var $userName = this.$el.find("#userName");
$userName.text(userName);
}
})
});
현재 매우 간단한 App이기 때문에 Express의 Route 기능을 사용하지는 않았습니다.
파일 업로드 기능이 추가된다면 Route를 통해 Controller를 분할하는 작업이 필요할 것 같습니다.
기존 채팅 이력은 Redis를 통해 넘겨받은 Object Array를 chatLogs 변수 안에 Push를 하고
chatView render 시 채팅 이력이 보여지도록 하였습니다.
채팅방 구현에 필요한 각 요소들은 다음 포스팅에서 순차적으로 다뤄보도록 하겠습니다.