Node.js + Express + TypeScriptでWebAPIを作る（3/4）

## 概要

* メモ的な書き残し
* MongoDBと接続してデータの取得をやってみる
* セキュリティ云々は考えていません

## 参考

[[Node.js] Express を TypeScript で書く - MongoDB 接続編](https://qiita.com/kuroneko8960/items/fa8665017d98774d3c06)

ありがたく参考にさせて頂きました。

## MongoDB接続

この辺りから未知の領域に突入です。公式で調べ調べ進めていきます。まずは必要になるであろうものをインストール。

```
npm install --save-dev @types/mongoose @types/jsonwebtoken
```

ライブラリの扱い方はこちら  
[mongoose v5.4.0](https://mongoosejs.com/docs/connections.html)
[MongoDB v3.x](http://mongodb.github.io/node-mongodb-native/3.1/quick-start/quick-start/)

MongoDBの使い方はこちら
[The mongo Shell](https://docs.mongodb.com/manual/mongo/)

```ts
import * as mongoose from "mongoose";

const uri = "mongodb://localhost:27017";
const dbName = "js-web-api";
mongoose.connect(uri + "/" + dbName, (e) => {
    console.log(e);
});
```
mongooseの最小構成だとこんな感じですかね。流れでDBを用意しておきます。DB名は「js-web-api」でコレクション名は「users」でいきます。

```
> mongo
> show dbs
any dbs
> use js-web-api
switched to db js-web-api
> db.createCollection('users');
{ "ok" : 1 }
> show collections
users
> exit
```

DB接続をmongoDBで書き換え。ついでに汎用性を持たせておく。

```ts
// ./src/dbClient.ts
import mongoDB from "mongodb";

const url = "mongodb://localhost:27017";
export const dbName = "js-web-api";

export default (callback: (err: mongoDB.MongoError, client: mongoDB.MongoClient, db: mongoDB.Db) => void) => {
    const client = mongoDB.MongoClient;
    client.connect(url, { useNewUrlParser: true }, (err, db) => {
        if (err) { throw err; }
        callback(err, db, db.db(dbName));
    });
};
```
こんな感じで利用します。

```ts
dbClient((err, client) => {
  if (err) {
    client.close();
    return;
  }
  // 何か処理する
  client.close();
});
```
接続テスト用のtsを作成します。

```ts
// ./src/routes/test/index.ts
import express from "express";
import mongodbClient from "../../dbClient";

const router = express.Router();

router.get("/dbConnect", (req, res, next) => {
    mongodbClient((err, client) => {
        if (err) {
            res.status(500).json({
                msg: ":-(",
            });
            return next(err);
        }
        res.json({
            msg: ":-)",
        });
        client.close();
    });
});

export default router;
```

server.tsに割当をして、サーバを起動して`localhost:3000/test/dbConnect`にGetするとレスポンスを得られます。

```ts
// ./src/server.ts
// 差分だけ記述
import test from "./routes/test/index";
app.use("/test", test);
```

```sh
> npm run ts-node ./src/server.ts
> url localhost:3000/test/dbConnect
{"msg":":-)"}
```

curlは便利なので使えるようになっておこう。オプションありすぎて自分も何がなんやらですが。terminal開くと震えが止まらない方はPostmanとかおすすめです。  
DB接続周りができたので引き続きコレクションからデータの取得を行います。まずDBの（js-web-api）のコレクション（users）の方にドキュメントを追加しておきます。uidにはユニーク制約を適用しておきます。以下、ログ。

```sh
> mongo
> use js-web-api
switched to db js-web-api
> db
js-web-api
> show collections
users
> db.users.insert({uid:"1",name:"hoge",password:"piyo"})
WriteResult({ "nInserted" : 1 })
> db.users.find()
{ "_id" : ObjectId("5c20ede66b9d3f8aefd9112a"), "uid" : "1", "name" : "hoge", "password" : "piyo" }
> db.users.createIndex({uid:1},{unique:true})
{
        "createdCollectionAutomatically" : false,
        "numIndexesBefore" : 1,
        "numIndexesAfter" : 2,
        "ok" : 1
}
> db.users.insert({uid:"1",name:"hogehoge",pass
word: "piyopiyo"})
WriteResult({
        "nInserted" : 0,
        "writeError" : {
                "code" : 11000,
                "errmsg" : "E11000 duplicate key error collection: js-web-api.users index: uid_1 dup key: { : \"1\" }"
        }
})
> db.users.insert({uid:"2",name:"hogehoge",pass
word: "piyopiyo"})
WriteResult({ "nInserted" : 1 })
> db.users.find()
{ "_id" : ObjectId("5c20ede66b9d3f8aefd9112a"), "uid" : "1", "name" : "hoge", "password" : "piyo" }
{ "_id" : ObjectId("5c20f3596b9d3f8aefd9112c"), "uid" : "2", "name" : "hogehoge", "password" : "piyopiyo" }
> exit
```

続いてdocumentを定義していきます。

```ts
// ./src/documents/IDocumentUser.ts
import * as MongoDB from "mongodb";

export interface IDocumentUser {
    _id: MongoDB.ObjectId;
    uid: string;
    name: string;
    password: string;
}
```

現状のままだとrequest.bodyがundefinedになってしまうので、対応していきます。ついでに定義されていないURLを叩かれた場合、404を返すようにしておきます。寄り道しまくりな気がしますが、まー気にしない。

```ts
// ./src/server.ts
import bodyParser from "body-parser";

app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use((req, res) => {
    res.status(404);
    res.json({
        message: "API Not Found",
        requestPath: req.path,
    });
});
```
404を返す処理はルーティング処理の後に書かないとどこにアクセスしても404が返ってくるようになるので注意です。実際には以下のようになります。

```ts
app.use("/auth", auth);
app.use("/users", users);
app.use("/test", test);

app.use((req, res) => {
    res.status(404);
    res.json({
        message: "API Not Found",
        requestPath: req.path,
    });
});
```
とりあえずここまででDB接続は完了です。この記事のラストとして先程作成したinterfaceを利用してDBからデータを取得してみます。

```ts
// ./src/routes/users/index.ts
import express from "express";
import mongodbClient from "../../dbClient";
import IDocumentUser from "../../documents/IdocumentUser";
import noImpl from "../../noImpl";
const router = express.Router();

router.post("/signup", noImpl);
router.put("/:user", noImpl);
router.delete("/:user", noImpl);

router.get("/:user", (req, res, next) => {
    const uid = req.params.user;
    mongodbClient((ce, client, db) => {
        if (ce) {
            client.close();
            res.status(500);
            res.json({
                message: ce.message,
                requestPath: req.path,
            });
            return next(ce);
        }

        const collection = db.collection<IDocumentUser>("users");
        collection.findOne({ uid: String }, (e, result) => {
            if (e) {
                client.close();
                res.status(500);
                res.json({
                    message: e.message,
                    requestPath: req.path,
                });
                return next(e);
            }

            client.close();

            if (result === null) {
                res.status(404).json({
                    message: "Not Found.",
                });
            }
            res.json(result);
        });
    });
});

export default router;
```

```sh
> curl localhost:3000/users/1
{"_id":"5c20ede66b9d3f8aefd9112a","uid":"1","name":"hoge","password":"piyo"}
```
とりあえずGetだけ実装しておきます。Getも認証後としたいのですが、一旦DB接続の流れで作成しておきます。Post/Put/Deleteは認証関連実装後に進めようと思います。