[Node.js] 使用TypeScript编写Express – MongoDB CRUD编辑

首先

请查看之前的文章,这篇文章是续集。

    • [Node.js] Express を TypeScript で書く – 環境整備まで

 

    • [Node.js] Express を TypeScript で書く – ルーティング編

 

    [Node.js] Express を TypeScript で書く – MongoDB 接続編

另外,环境的假设如下。

    • Windows 10

 

    • Node.js 10.10.0

 

    • Express 4.16.3

 

    • TypeScript 3.0.3

 

    • MongoDB 4.0.2 Community Server

 

    • MongoDB Node.js 3.1.4

 

    Visual Studio Code 1.26.1

本次工作将实现对用户的CRUD操作。

备办

定义文档

首先,我们来定义文档的结构。这是TypeScript的独特之处。

import * as MongoDB from 'mongodb';

export interface UserDocument {
    _id: MongoDB.ObjectId;
    user_id: string;
    name: string;
    password: string;
}

我定义了用户ID、姓名和密码。为了获取内部ID,我还定义了”_id”。

通过创建这个来获取 collection,然后对于关于 document 的值进行类型标注。

const collection = db.collection<UserDocument>('user');
collection.findOne({ user_id: 'xxx' }, (err, doc) => {
    console.log(doc.user_id);
    console.log(doc.name);
    console.log(doc.password);
});

定义一个索引。

为了确保user_id的唯一性,我创建了一个索引。
我在MongoDB Compass Community (GUI)上进行了设置。

image.png

使 Express 来接收 JSON 数据。

基本上,我希望使用JSON来进行数值交换,因此我将在Express中实现它。

使用body-parser。

npm install --save body-parser

修改server.ts文件。

import * as Express from 'express';
import * as BodyParser from 'body-parser';

const app = Express();

app.use(BodyParser.urlencoded({ extended: true }));
app.use(BodyParser.json());

/** 以下省略 */

如果请求的URL无效,我们将改为返回JSON而不是默认的HTML。

/** 省略 */

// 各ルーティングの定義が終わったあとに書く。
app.use((req, res) => {
    res.status(404).json({ message: 'Not Found API.' });
});

/** 省略 */

实现CRUD功能和API功能。

我們將從這裡開始實現每個API的CRUD操作。

发起请求 创建新用户

このAPIは以下のパラメータをJSON形式で受け取ります。

{
    user_id, // REQUIRED, Allow Characters [a-zA-Z0-9_]
    name, // REQUIRED
    password // REQUIRED
}

user_id は URL で指定できるようにしたいので使える文字種を絞ります。

APIの戻り値として、登録された情報を返します。ただしパスワードは返しません。

{
    user_id,
    name
}

document の追加は collection.insertOne() を使えばできそうです。

以下是实施的内容。

import * as Express from 'express';
import errorJSON from '../../common/errorJSON';
import mongodbClient from '../../common/mongodbClient';
import { UserDocument } from '../../documents/User';

const router = Express.Router();
// ユーザの新規作成
router.post('/new', (req, res, next) => {
    // パラメータ取得
    const user_id = req.body.user_id;
    const name = req.body.name;
    const password = req.body.password;

    // 必須項目が入力済みかチェック
    if (user_id === undefined || name === undefined || password === undefined) {
        res.status(400).json(errorJSON('Parameter', 'Require Parameter.'));
        return next();
    }

    // user_id の書式チェック
    if (user_id.match(/^[a-zA-Z0-9_]+$/) == null) {
        res.status(400).json(errorJSON('Parameter', 'Invalid Parameter.'));
        return next();
    }

    mongodbClient((err, client, db) => {
        if (err) {
            client.close();
            res.status(500).json(errorJSON('MongoDB', err.message));
            return next(err);
        }

        const collection = db.collection<UserDocument>('user');
        collection.insertOne(
            {
                user_id: user_id,
                name: name,
                password: password,
            },
            (err, result) => {
                if (err) {
                    client.close();
                    res.status(500).json(errorJSON('MongoDB', err.message));
                    return next(err);
                }

                res.status(200).json(filterUserDocument(result.ops[0]));
                client.close();
            }
        );
    });
});

errorJSON是一个用于获取错误输出的JSON的函数。
filterUserDocument是一个将传入的document进行过滤,消除不必要项目并返回的函数。实现如下所示。

function filterUserDocument(doc: object) {
    const denied = ['_id', 'password'];
    return Object.keys(doc)
        .filter(key => denied.indexOf(key) === -1)
        .reduce((obj, key) => {
            obj[key] = doc[key];
            return obj;
        }, {});
}

密码被以纯文本形式保存,但实际上应该对其进行某种形式的加密。由于这是一个仅限于本地使用的练习,所以我们不做这样的处理。

获取用户/:user (读取)

下一个是用于获取用户信息的API。只需通过url进行指定,无需参数。

如果有用户存在,该API将返回以下的JSON数据。

{
    user_id,
    name
}

如果不存在,就返回404错误。

如果要从MongoDB中获取特定的文档,可以使用collection.findOne()。

这是以下的实施方案。

router.get('/:user', (req, res, next) => {
    const user_id = req.params.user;

    mongodbClient((err, client, db) => {
        if (err) {
            client.close();
            res.status(500).json(errorJSON('MongoDB', err.message));
            return next(err);
        }

        const collection = db.collection<UserDocument>('user');
        collection.findOne({ user_id: user_id }, (err, result) => {
            if (err) {
                client.close();
                res.status(500).json(errorJSON('MongoDB', err.message));
                return next(err);
            }

            client.close();

            if (result == null) {
                res.status(404).json({ message: 'Not Found.' });
            } else {
                res.json(filterUserDocument(result));
            }
        });
    });
});

PUT /user/:user (更新)

接下来是用户更新的API。

这个API接收以下参数。

{
    user_id,
    name,
    password
}

所有的项目都将变成可选项。仅更新指定的项目。

返回的值是更新后的用户信息。

要更新文件,使用collection.findOneAndUpdate()方法。
在设置值时,需要在第二个参数中提供一个带有$set的对象,例如{ $set: { hoge: ‘xxx’ } }。

需要重新检索以获取更新后的值。

这是我们的实施方案。

router.put('/:user', (req, res, next) => {
    // URLから対象のuser_idを取得
    const user_id = req.params.user;

    // JSONより更新用パラメータを取得
    const new_user_id = req.body.user_id;
    const name = req.body.name;
    const password = req.body.password;

    // 更新用オブジェクト作成
    const updateFields = {};
    if (new_user_id !== undefined) { updateFields['user_id'] = new_user_id; }
    if (name !== undefined) { updateFields['name'] = name; }
    if (password !== undefined) { updateFields['password'] = password; }
    const update = { $set: updateFields };

    mongodbClient((err, client, db) => {
        if (err) {
            client.close();
            res.status(500).json(errorJSON('MongoDB', err.message));
            return next(err);
        }

        const collection = db.collection<UserDocument>('user');
        collection.findOneAndUpdate({ user_id: user_id }, update, (err, result) => {
            if (err) {
                client.close();
                res.status(500).json(errorJSON('MongoDB', err.message));
                return next(err);
            }

            if (result.value === null) {
                // 対象レコードが存在しなかった場合 result.value に null が返る。
                client.close();
                res.status(404).json({ message: 'Not Found.' });
                return;
            }

            // 更新後のdocumentを取得するために再検索する。
            collection.findOne({ _id: result.value._id }, (err, result) => {
                if (err) {
                    client.close();
                    res.status(500).json(errorJSON('MongoDB', err.message));
                    return next(err);
                }

                client.close();
                res.json(filterUserDocument(result));
            });
        });
    });
});

这个 API 目前可以被任何人调用,但稍后将添加登录认证,只允许自己更新。

删除用户/:user (删除)

最后是删除API,没有任何参数,只需传递URL。

如果能够成功删除,则返回200,如果指定的用户不存在,则返回404。

要删除 document,可以使用 collection.findOneAndDelete() 方法。

唯一的不同是除非有更新参数,否则几乎和更新时一样。

router.delete('/:user', (req, res, next) => {
    const user_id = req.params.user;

    mongodbClient((err, client, db) => {
        if (err) {
            client.close();
            res.status(500).json(errorJSON('MongoDB', err.message));
            return next(err);
        }

        const collection = db.collection<UserDocument>('user');
        collection.findOneAndDelete({ user_id: user_id }, (err, result) => {
            if (err) {
                client.close();
                res.status(500).json(errorJSON('MongoDB', err.message));
                return next(err);
            }

            client.close();
            if (result.value == null) {
                res.status(404).json({ message: 'Not Found.' });
            } else {
                res.json({ message: 'Deleted.' });
            }
        });
    });
});

这个API目前任何人都可以调用,但稍后我会加上登录认证,只允许自己删除。

我打算实现用户登录认证过程。

广告
将在 10 秒后关闭
bannerAds