[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)上进行了设置。
使 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目前任何人都可以调用,但稍后我会加上登录认证,只允许自己删除。
我打算实现用户登录认证过程。