NODEJS,ソケットIO,MongoDBを用いた実時間チャットルームシステムの実現


“Socket . ioは、ブラウザとサーバーの間のリアルタイム、双方向、イベントベースの通信を可能にするライブラリです”.基本的にはソケット.IOは、アプリケーション間のリアルタイム通信を即座に許可します.それは他のアプリにイベントを放出するアプリを許可することによって動作し、イベントを受信するアプリケーションは、彼らが好きなように処理することができます.また、トラフィックを分離する名前空間とチャットルームを提供します.WebSocketとソケットの最善の用途の一つです.iOSは、リアルタイムのチャットアプリです.
この記事では、我々はゼロからのリアルタイムのチャットルームシステムを構築します.フロントエンド(クライアント側)については話をしません.その結果、フロントエンドのための準備された反応プロジェクトを使用し、バックエンドのために(ノード. js)を表現します.ソケット.IOサーバはバックエンドで使用されます、そして、認可はMongoDBのデータベースとマングースパッケージによって提供されます.したがって、このブログでは、チャットルームの仕組みの基礎を説明しようとしますが、CSS(Style Parts)を必要としていて、ファイルに反応してくださいGitHub レポ.
私はあなたが興奮してほしい!
これは、我々が何を構築するかのプレビューです

前もって
JavaScript、MongoDB、Expressの基本的な知識は、反応が必要です.私はNPMとノードがインストールされていると仮定し、どのように彼らは(少なくとも基本)を動作知っている.
それで始めましょう.
最初のステップはindex.js サーバー側のファイルを開き、次のコードを端末/コマンドラインウィンドウに書き込みます.npm i express socket.io mongoose cors一旦終了すると、以下のコードを使用してモジュールを必要とし、サーバーを実行できます.
const express = require('express');
const app = express(); 
const http = require('http').createServer(app);
const mongoose = require('mongoose');
const socketio = require('socket.io');
const io = socketio(http);  
const mongoDB = "Your MongoDB Connection Address";

const PORT = process.env.PORT || 5000;
app.use(express.json());  //it help us to send our data to the client side
mongoose.connect(mongoDB, 
{useNewUrlParser: true, useUnifiedTopology: true })
.then(() => console.log('connected'))
.catch(err => console.log(err))


http.listen(PORT, () => {
            console.log(`listening on port ${PORT}`);
});
続ける前に、いくつかのヒントがあります.
CORSエラー:
私は、誰でもCorsエラーと戦っていると思っています.これらのエラーを解決することは、CORS構成をセットアップして、以下のコードを適用することによって、より挑戦的でありません;
const cors = require('cors');
const corsOptions = {
            origin: 'http://localhost:3000', // your frontend server address
            credentials: true,
            optionsSuccessStatus: 200 
}   
app.use(cors(corsOptions));

しかし、ソケットIOに接続する際にCORSエラーがあるならば、IOは以下のように構成されなければなりません;
const io = socketio(http,{
            cors: {
            origin: "http://localhost:3000", // your frontend server address
            methods: ["GET", "POST"]
            }
});

MongoDBモデルの作成
私たちは3つのモデルを持っているMessage.js , Room.js , and User.js . それぞれのモデルには特定の設定があります.部屋JSはただの部屋の名前を保存します.JSは認証のためにユーザの名前、電子メール、およびパスワードを格納します.メッセージ.JSは、名前、ユーザー名ID、RoomRound ID、テキスト、タイムスタンプフィールドを格納します.これらのモデルの構築に違いがないので、ユーザーの作成に役立ちます.jsモデル.あなたが私の2つの他のモデルを見ることができると言及する価値がありますGitHub .
ユーザーの作成に飛び込みましょう.JSモデル
このモデルでは、入力フィールドをバリデータパッケージをターミナルにインストールすることで検証する必要があります.
また、私たちはpre-save このモデルでフックをデータベースに格納する前にハッシュをハッシュします.Pre スキーマレベルで定義されたミドルウェアであり、実行されるときにクエリまたはドキュメント自体を変更できます.エーPre-save フックは、ドキュメントが保存されるときに実行されるミドルウェアです.
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const { isEmail } = require('validator');
const userSchema = new mongoose.Schema({
    name: {
        type: String,
        required: [true, 'Please enter a name']
    },
    email: {
        type: String,
        required: [true, 'Please enter a email'],
        unique: true,
        lowercase: true,
        validate: [isEmail, 'Please enter a valid email address']
    },
    password: {
        type: String,
        required: [true, 'Please enter a password'],
        minlength: [6, 'The password should be at least 6 characters long']
    },
})
userSchema.pre('save', async function (next) {
    const salt = await bcrypt.genSalt();
    this.password = await bcrypt.hash(this.password, salt);
    next()
})
const User = mongoose.model('user', userSchema);
module.exports = User;
ルーティングの実装
ルーティングは、クライアントの要求がアプリケーションのエンドポイントによってどのように扱われるかを定義します.ルートを実装するための2つの方法があります.フレームワークを使用して、フレームワークを使用せずに.このプロジェクトでは、Expressフレームワークを使用します.
データベースモデルを作成した後に、必要なルートを実行する必要があります/signup , /login , /logout , and /verifyuser . 我々は、ログイン経路には、ログインしていないユーザーをガイドし、チャットへのアクセスを防ぐために、クライアント側の認証を調査するVerifyUserルートを使用します.
まず、サーバー側のルートにルートフォルダを作成し、このフォルダにファイルを作成し、名前を付けますauthRoute.js , 次に以下のコードを書きます:
const { Router } = require('express');
const authController = require('../controllers/authControllers');
const router = Router();
router.post('/signup', authController.signup)
router.post('/login', authController.login)
router.get('/logout', authController.logout)
router.get('/verifyuser',authController.verifyuser)

module.exports = router;

その後、auperteを使用します.この短いコードをインデックスに追加する必要があります.jsファイル
const authRoutes = require('./routes/authRoutes');
app.use(authRoutes);

コントローラファイルの作成
まず、ユーザを登録する必要があります.これについては、入力データを使用してデータベースに保存します(パスワードを事前に保存するフックを使用するので、ここでハッシュする必要はありません).それから、JSONWebTokenパッケージの助けを借りて、トークンを構築し、クッキーとして保存します(関数をビルドし、createJWTという名前のトークンを作成するために).最後に、ビルドしたユーザーをJSONコマンドを通じてクライアント側に返します.
明らかに、クッキーを読むには、クッキーパーサーパッケージをインストールする必要があります.jsファイル
const cookieParser = require('cookie-parser');
app.use(cookieParser());
既に知っているかもしれませんが、コードを書くためには、サーバー側のルートでコントローラという名前のフォルダを作成し、このフォルダにファイルを作り、名前をつける必要がありますauthController.js , 次に以下のコードを書きます:
const User = require('../models/User');
const jwt = require('jsonwebtoken');
const maxAge = 24 * 60 * 60   // equal one day in second
const createJWT = id => {
    return jwt.sign({ id }, 'chatroom secret', {
        expiresIn: maxAge
    })
}
'チャットルーム秘密'我々はトークンをデコードするために使用する
signup関数:
module.exports.signup = async (req, res) => {
    const { name, email, password } = req.body;
    try {
        const user = await User.create({ name, email, password });
        const token = createJWT(user._id);
      // create a cookie name as jwt and contain token and expire after 1 day
      // in cookies, expiration date calculate by milisecond
        res.cookie('jwt', token, { httpOnly: true, maxAge: maxAge * 1000 })
        res.status(201).json({ user });
    } catch (error) {
        let errors = alertError(error);
        res.status(400).json({ errors });
    }

}
ログイン機能
マングースは楽しむがcreate メソッドを使用して、signup関数でユーザを作成しますlogin メソッドとユーザの終わりに手動で設定する必要があります.以下のコードを使用するJSモデル
userSchema.statics.login = async function (email, password){
    const user = await this.findOne({email});
    if(user){
        const isAuthenticated = await bcrypt.compare(password,user.password);
        if(isAuthenticated){
            return user;
        }else{
            throw Error('Incorrect password');
        }
    }else{
        throw Error('Incorrect email');
    }
}
このメソッドは、ユーザーのメールとパスワードが必要です.人の情報がデータベースで利用できるならば、それはエラーを返す他の情報を返します.ユーザー情報を返す場合、CreateJWT関数を使用して、クッキーを作成します.最後に、ユーザー情報またはエラーをクライアント側に返します.
module.exports.login = async (req, res) => {
    const { email, password } = req.body;
    try {
        const user = await User.login(email, password );
        const token = createJWT(user._id);
        res.cookie('jwt', token, { httpOnly: true, maxAge: maxAge * 1000 })
        res.status(201).json({ user });
    } catch (error) {
        let errors = alertError(error);
        res.status(400).json({ errors });
    }
}
ログアウト関数
さて、私たちは1 ms後に期限切れになる空の代替クッキーを作るべきです.その後、{logout:true} クライアント側に送信する必要があります
module.exports.logout = (req, res) => {
    res.cookie('jwt',"",{maxAge:1});
    res.status(200).json({logout: true});
}
verifyuser関数:
クライアント側では、この関数を使用してユーザーのログをチェックします.このチェックを行うのはJWTクッキーをデコードし、データベース内のユーザーの存在を確認することで可能です.トークンのデコードはverify JSONWebTokenパッケージのメソッド.ユーザーが既にログインしている場合は、ユーザー側の情報をクライアント側に返します.
module.exports.verifyuser = (req, res, next)=>{
    const token = req.cookies.jwt;
    if(token){
        jwt.verify(token,'chatroom secret',async (err,decodedToken)=>{
            if(err){
                console.log(err.message);
            }else{
                let user = await User.findById(decodedToken.id);
                res.json(user);
                next();
            }
        })
    }else{
        next();
    }
}
ソケット上で作業を始めましょう.IO論理
今、インデックスに戻ります.JSはソケットで動作を開始します.IO、しかし、その前に、我々は3つの変数、すなわち部屋、メッセージとユーザーで我々のモデルを必要としなければなりません.
プロジェクトをきれいにするには、まずファイル名を作成しますutil.js サーバー側のルートフォルダでaddUser , getUser , and removeUser このファイルの関数.最後に、これらの関数をindex.js ファイル.
Utiljsファイル
このファイルでは、各部屋のすべてのユーザーの情報がユーザー配列で保存されます.
adduser関数では、まずユーザ配列のユーザ情報の有無をチェックします.ユーザがユーザ配列に存在しない場合、push この配列へのメソッド.最後に、この関数はユーザを返します.
RemoveUser関数では、ログアウトしたユーザーのソケットIDを受け取り、ユーザの配列のインデックスを探します.最後に、splice ユーザーを配列から削除します.
GetUser関数では、ソケットIDを受け取り、ユーザー配列からユーザーの情報を要求し、それを返します.
const users = [];
const addUser = ({ socket_id, name, user_id, room_id }) => {
    const exist = users.find(user => user.room_id === room_id && user.user_id === user_id);
    if (exist) {
        return { error: 'User already exist in this room' }
    }
    const user = { socket_id, name, user_id, room_id };
    users.push(user)
    console.log('users list', users)
    return { user }
}

const removeUser = (socket_id) => {
    const index = users.findIndex(user => user.socket_id === socket_id);
    if (index !== -1) {
        return users.splice(index, 1)[0]
    }
}
const getUser = (socket_id) => users.find(user => user.socket_id === socket_id)
module.exports = { addUser, removeUser, getUser }
ソケットの実装
使用してソケットにアクセスできますio.on(‘connection’,(socket)=>{ … }) また、このコードを通してソケットに変更を加えることもできます.
を返します.コードを使うsocket.emit('channel name',variable or text message to send) 送信およびコードsocket.on('channel name',variable to receive) 情報と変数を必要とするために.さて、どのように我々はデータベースからクライアント側にクライアントを送信する方法を知っている必要があります.
join channel , クライアント側からユーザー情報を受け取り、AddUser関数を使用してユーザー配列に保存します.その後、コードを使ってsocket.join(room_id) , 我々は、希望の部屋にユーザーを保存することができますし、他のユーザーは、その部屋のメンバーであることを条件に人のポストが表示されます.このように、私たちはソケットを整理します.
チャンネル登録者'get-message-history' , 我々は、クライアント側からのIDを受信し、メッセージのモデルを介して部屋チャットを必要とします.そして、結果をクライアント側に返します.その結果、ログインしているユーザーは、データベースに保存される過去のメッセージを見ることができます.
io.on('connection', (socket) => {
    console.log(socket.id);
    Room.find().then(result => {
        socket.emit('output-rooms', result)
    })
    socket.on('create-room', name => {
        const room = new Room({ name });
        room.save().then(result => {
            io.emit('room-created', result)
        })
    })
    socket.on('join', ({ name, room_id, user_id }) => {
        const { error, user } = addUser({
            socket_id: socket.id,
            name,
            room_id,
            user_id
        })
        socket.join(room_id);
        if (error) {
            console.log('join error', error)
        } else {
            console.log('join user', user)
        }
    })
    socket.on('sendMessage', (message, room_id, callback) => {
        const user = getUser(socket.id);
        const msgToStore = {
            name: user.name,
            user_id: user.user_id,
            room_id,
            text: message
        }
        console.log('message', msgToStore)
        const msg = new Message(msgToStore);
        msg.save().then(result => {
            io.to(room_id).emit('message', result);
            callback()
        })

    })
    socket.on('get-messages-history', room_id => {
        Message.find({ room_id }).then(result => {
            socket.emit('output-messages', result)
        })
    })
    socket.on('disconnect', () => {
        const user = removeUser(socket.id);
    })
});
最後に、私はあなたがすべてのこの記事を気に入っていただければ幸いです、そして、あなたが質問をするならば、あなたはコメントセクションに彼らを置くことができます.できるだけ早く帰ってきます.再びあなたの時間を感謝します.あなたの将来の努力のすべてのあなたのベストを祈ります.
心から
孫徳華