LoginSignup
4
1

More than 3 years have passed since last update.

WebpackでTypeScript、Express、Mongoose、Bootstrapの簡易チャットを作成してみた

Last updated at Posted at 2019-08-12

JavaScript、MongoDB初心者です。わからないところはググりながら書きました。間違いなどありましたら教えていただけるとありがたいです。:bow:

↑こちらのリンクで、express、mongodbのチュートリアルがあったので、それを自分なりにTypeScriptで書き直してみたという投稿になります。よろしくお願いいたします。m(_ _)m

こう言った感じのチャットが作成できました。

Screen Shot 2019-08-12 at 12.07.51.png

環境

  • macOS Mojave 10.14.5
  • node 12.7.0
  • MongoDB 4.0.3

私が実際に作成してみたコードはこちらです↓

MongoDBのインストール

こちらのリンクを参考にMongoDBをインストールしました。m(_ _)m

#参考リンクにあるように、こちらでmongodbを起動している状態にしておきます。
sudo mongod --dbpath /var/lib/mongodb --logpath /var/log/mongodb.log

インストール

yarn add express mongoose socket.io bootstrap
yarn add -D webpack webpack-cli webpack-merge webpack-node-externals nodemon-webpack-plugin typescript ts-loader @types/express @types/mongoose @types/node @types/socket.io @types/socket.io-client @types/webpack css-loader style-loader
  • express
  • mongoose
  • socket.io
  • bootstrap
  • webpack
  • webpack-cli
  • webpack-merge
    • webpackの共通設定をマージしてくれて、見やすくなる。
  • webpack-node-externals
    • サーバー側をバンドルする時に使う、node_modulesを無視して外部関数として扱うようにバンドルしてくれる
  • nodemon-webpack-plugin
    • 開発時に便利なnodemonのwebpack用プラグイン
  • typescript
  • ts-loader
  • @types/express
  • @types/mongoose
  • @types/node
    • nodeのhttpモジュールも一部使うときに使用する型定義
  • @types/socket.io
    • socket.ioのサーバーで動く機能の型定義
  • @types/socket.io-client
    • socket.ioのブラウザで動く機能の型定義
  • @types/webpack
    • webpack.config.jsで補完が効くようにするためだけなので、なくてもOK
  • css-loader
    • bootstrapのcssをjsから読み取るので必要
  • style-loader
    • bootstrapのcssをjsから読み取るので必要

webpack.config.js

webpack.config.js
const merge = require("webpack-merge");
const nodeExternals = require("webpack-node-externals");
const NodemonPlugin = require("nodemon-webpack-plugin");

/** @type import("webpack").Configuration */
const baseConfig = {
    mode: process.env.NODE_ENV || "development",
    module: {
        rules: [
            {
                test: /\.ts$/,
                use: "ts-loader",
            },
        ],
    },
    resolve: {
        extensions: [".js", ".ts"],
    },
};
module.exports = [
    // ブラウザで動く機能をバンドル
    merge(baseConfig, {
        entry: "./src/client",
        output: {
            filename: "client.js",
            path: `${__dirname}/dist`,
        },
        module: {
            rules: [
                {
                    // bootstrapのcssを読み込む際に必要
                    test: /\.css$/,
                    use: ["style-loader", "css-loader"]
                }
            ]
        }
    }),
    // Nodeサーバーで動く機能をバンドル
    merge(baseConfig, {
        entry: "./src/server",
        output: {
            filename: "server.js",
            path: `${__dirname}/dist`,
        },
        target: "node",
        node: {
            // expressを使うときにはこの設定をしないと失敗します
            // 参考:https://medium.com/@binyamin/creating-a-node-express-webpack-app-with-dev-and-prod-builds-a4962ce51334
            __dirname: false,
            __filename: false,
        },
        externals: [
            // webpackで生成したファイルを、`node bundle.js` と実行する場合に
            // node_modulesのファイルを一緒にバンドルしている必要はないので、
            // node_modulesを無視して外部関数として扱うようにバンドルしてくれる
            nodeExternals()
        ],
        plugins: [
            // nodeのサーバ用のjsファイルを編集した時に自動でサーバを再起動してくれる
            // nodemonのwebpack用のプラグイン
            new NodemonPlugin(),
        ],
    }),
];

tsconfig.json

こちらを参考にして設定しました。間違っていたら教えていただきたいです。m(_ _)m

tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "module": "esnext",
    "strict": true,
    "allowJs": true,
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules",
    "**/*.spec.ts"
  ]
}

サーバー側を作成

src/server.ts
import * as express from "express";
import * as http from "http";
import * as net from "net";
import * as socketio from "socket.io";
import * as mongoose from "mongoose";

import { ISendMsg } from "./global";

interface ISendMsgModel extends mongoose.Document, ISendMsg {}

const app: express.Express = express();
const server: http.Server = http.createServer(app);
const io: socketio.Server = socketio(server);

let Message: mongoose.Model<ISendMsgModel>;

(async () => {
    try {
        await mongoose.connect("mongodb://localhost/simple-chat");
        Message = mongoose.model<ISendMsgModel>("Message", new mongoose.Schema({ name: String, message: String }))
    } catch (err) {
        console.log(err)
    }
})();

app.use(express.static(`${__dirname}/../`));

app.get("/messages", async (req: express.Request, res: express.Response) => {
    try {
        const messages = await Message.find({});
        res.send(messages);
    } catch (err) {
        console.log(err)
    }
})

io.on("connection", (socket: socketio.Socket) => {
    socket.on("message", async (msg: ISendMsg) => {
        try {
            const message = new Message(msg);
            await message.save();
            io.emit("message", msg);
        } catch (err) {
            console.log(err)
        }
    });
})

server.listen(3000, () => {
    console.log(`server is running on port ${(server.address() as net.AddressInfo).port}`);
});

メッセージの内容は共通の設定なのでglobal.d.tsに定義してみました。

src/global.d.ts
export interface ISendMsg {
    name: string;
    message: string;
}

ブラウザ側を作成

src/client.ts
import * as io from "socket.io-client";
import "bootstrap/dist/css/bootstrap.min.css";

import { ISendMsg } from "./global";

const input: HTMLInputElement = <HTMLInputElement>document.getElementById("name");
const message: HTMLTextAreaElement = <HTMLTextAreaElement>document.getElementById("message");
const send: HTMLButtonElement = <HTMLButtonElement>document.getElementById("send");
const messages: HTMLDivElement = <HTMLDivElement>document.getElementById("messages");

const socket: SocketIOClient.Socket = io();

(async () => {
    const res = await fetch("http://localhost:3000/messages");
    const data: ISendMsg[] = await res.json();
    data.forEach(addMessages);
})();

send.addEventListener("click", () => {
    socket.emit("message", { name: input.value, message: message.value });
});

socket.on("message", addMessages)

function addMessages({ name, message }: ISendMsg) {
    messages.insertAdjacentHTML("beforeend", `
        <h4> ${name} </h4>
        <p>  ${message} </p>
    `);
}
index.html
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Hello, world!</title>
<script src="dist/client.js" defer></script>
</head>
<body>
    <div class="container p-5">
        <div class="jumbotron">
            <h1 class="display-4">Send Message</h1>
            <br>
            <input id="name" class="form-control" placeholder="Name">
            <br>
            <textarea id="message" class="form-control" placeholder="Your Message Here"></textarea>
            <br>
            <button id="send" class="btn btn-success">Send</button>
        </div>
        <div id="messages"></div>
    </div>
</body>
</html>

実行

yarn webpack -w #http://localhost:3000/ をブラウザで開く

EDyJFoqPv5.gif

簡易チャットが動かすことができました。


MongoDBのGUIクライアントでStudio 3Tを使用しながら作成させていただきました。m(_ _)m


最後まで読んでいただいてありがとうございました。m(_ _)m

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1