공부하고


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 시 채팅 이력이 보여지도록 하였습니다.



채팅방 구현에 필요한 각 요소들은 다음 포스팅에서 순차적으로 다뤄보도록 하겠습니다.