用MongoDB+Express+AngularJS+Node.js创建一个简单的CRUD应用

自制的MEAN Stack

我在使用 AngularJS+PHP+MySQL 构建业务应用程序时,由于 JavaScript 和 PHP 语法的微妙差异,导致出现了很多疏忽错误。

所以,我考虑尝试流行的MEAN Stack(MongoDB+Express+AngularJS+Node.js)来统一前端和后端的JavaScript。

有很多工具可以创建MEAN Stack的模板,但这次我打算自己手工制作。
我会提供Windows和Ubuntu的安装方法,但我认为在其他操作系统上也差不多。

M: 安装和配置MongoDB

这是广为人知的NoSQL界的巨头。

在Windows系统下

从此处下载并安装:http://www.mongodb.org。本次安装在C:\MongoDB路径下完成。

设定

根据标准,DB存储文件夹将被创建在 /data/db ,但是为了更好理解,我们决定将其存储在 C:\MongoDB\data 中。请事先创建 C:\MongoDB\data 文件夹。

如果作为服务安装,则还需要创建一个用于存储日志的路径,因此我也创建了 C:\MongoDB\log 文件夹。

作为服务安装

mongod --install --dbpath=C:\MongoDB\data --logpath=C:\MongoDB\log\mongo.log --logappend

请注意,在使用–logpath参数时需要指定文件名。

对于Ubuntu来说

请参考此链接,在Ubuntu上添加存储库并进行安装。请使用root权限进行操作。

apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10
echo "deb http://repo.mongodb.org/apt/ubuntu "$(lsb_release -sc)"/mongodb-org/3.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.0.list
apt update
apt install mongodb-org

数据存储在 /var/lib/mongodb ,日志存储在 /var/log/mongodb。

Mongo命令行工具

安装完成后,启动Shell(mongo命令)并尝试各种功能。

玩弄Mongo Shell
充分利用MongoDB的shell

我参考了一些文章。由于JavaScript可以直接使用,所以可以做很多事情。

GUI工具

在GUI中有很多可以操作数据库的工具,但我认为其中Robomongo和RockMongo是最易用的。

安装Node.js

这是服务器端的JavaScript。

如果是Windows的情况

可以通过官方网站http://nodejs.org/轻松安装,无需特别设置。

如果是Ubuntu的情况下

通过PPA添加存储库并进行安装。

add-apt-repository -y ppa:chris-lea/node.js
apt update
apt install nodejs

E:安装和配置Express

这是一个为Node.js设计的网络应用框架。虽然只使用Node.js也能做些什么,但是如果引入这个框架,可以极大地方便地创建应用程序。

您可以通过使用Node.js的包管理器npm来安装Express。请创建一个适当的文件夹用于此应用,并在其中执行以下命令。

npm install express

将安装在node_modules文件夹及其子文件夹中。

此外,您也可以选择进行全局安装,并在那里创建符号链接。

npm install -g express
npm link express

最近的Windows系统可以正确地创建符号链接。

安装 body-parser

为了解释请求主体,需要在附加模块中安装 body-parser。

npm install body-parser

安装node-mongodb-native驱动程序

npm install mongodb

這是一個用於從Node.js訪問MongoDB的基本模組。Mongoose很有名,但這次我們選擇更本地化的這個。

A: 按需 JavaScript (AngularJS)

由于AngularJS是从CDN获取的,因此不需要特别安装。

创建CRUD应用程序

投入初始数据

当您启动Mongo Shell时,

connecting to: test

因为显示已连接到数据库test,所以直接使用它。执行以下命令。

db.users.insert([
  {name: "勅使河原", age: 19},
  {name: "長宗我部", age: 20},
  {name: "小比類巻", age: 21}
])

以下是对原文的中文翻译:
数据库(db)当前连接的是名为test的数据库,集合(collections)名为users(相当于关系型数据库中的表)。当集合不存在时,会自动创建。您可以插入类似JSON格式的文档(相当于关系型数据库中的记录)。

投入的数据可以通过以下命令确认。

db.users.find()

自動地分配了ObjectId(在关系数据库中被称为主键)。

原始碼

如果不想麻烦地创建,可以将所有文件一起放在这里。
https://github.com/naga3/mean-basic

文件夹结构

アプリのフォルダ
├app.js →エントリポイント
├node_modules/ →npmで導入したファイル群
└api/
 └users →usersコレクションを読み書きするAPIアドレス
└front/ →フロントエンド
  ├index.html
  ├list.html
  └edit.html

需要创建的东西是app.js文件、front/文件夹和其中的所有文件。

后端

var express = require('express');
var bodyParser = require('body-parser');
var mongodb = require('mongodb');

var app = express();
var users;

app.use(express.static('front'));
app.use(bodyParser.json());
app.listen(3000);

mongodb.MongoClient.connect("mongodb://localhost:27017/test", function(err, database) {
  users = database.collection("users");
});

// 一覧取得
app.get("/api/users", function(req, res) {
  users.find().toArray(function(err, items) {
    res.send(items);
  });
});

// 個人取得
app.get("/api/users/:_id", function(req, res) {
  users.findOne({_id: mongodb.ObjectID(req.params._id)}, function(err, item) {
    res.send(item);
  });
});

// 追加・更新
app.post("/api/users", function(req, res) {
  var user = req.body;
  if (user._id) user._id = mongodb.ObjectID(user._id);
  users.save(user, function() {
    res.send("insert or update");
  });
});

// 削除
app.delete("/api/users/:_id", function(req, res) {
  users.remove({_id: mongodb.ObjectID(req.params._id)}, function() {
    res.send("delete");
  });
});

前端

指数

<!doctype html>
<html lang="ja" ng-app="app">
<meta charset="utf-8">
<title>ユーザー管理</title>
<div ng-view></div>
<script src="//code.angularjs.org/1.3.15/angular.min.js"></script>
<script src="//code.angularjs.org/1.3.15/angular-resource.min.js"></script>
<script src="//code.angularjs.org/1.3.15/angular-route.min.js"></script>
<script>
  var app = angular.module('app', ['ngResource', 'ngRoute']);

  app.config(function($routeProvider) {
    $routeProvider.when('/users', {
      templateUrl: 'list.html', controller: 'ListCtrl'
    }).when('/users/:_id', {
      templateUrl: 'edit.html', controller: 'EditCtrl'
    }).otherwise({
      redirectTo: '/users'
    });
  });

  app.factory('User', function($resource) {
    return $resource('/api/users/:_id');
  });

  app.controller('ListCtrl', function($scope, $route, User) {
    $scope.users = User.query();
    $scope.delete = function(_id) {
      User.delete({_id: _id}, function() {
        $route.reload();
      });
    };
  });

  app.controller('EditCtrl', function($scope, $routeParams, $location, User) {
    if ($routeParams._id != 'new') $scope.user = User.get({_id: $routeParams._id});
    $scope.edit = function() {
      User.save($scope.user, function() {
        $location.url('/');
      });
    };
  });
</script>
</html>

清单

<a ng-href="#/users/new">新規</a>
<table border="1">
  <tr><th>&nbsp;</th><th>氏名</th><th>年齢</th></tr>
  <tr ng-repeat="user in users">
        <td>
          <a ng-href="#/users/{{user._id}}">編集</a>
          <button ng-click="delete(user._id)">削除</button>
        </td>
        <td>{{user.name}}</td>
        <td>{{user.age}}</td>
  </tr>
</table>

添加和修改

氏名 <input ng-model="user.name"><br>
年齢 <input ng-model="user.age"><br>
<button ng-click="edit()">登録</button>
<a ng-href="/#/users">戻る</a>

执行方式

在应用程序的主文件夹中

node app

打开app.js文件并在浏览器中运行。

http://localhost:3000/

進行訪問。

列表界面

list.png

编辑界面

edit.png

app.js 说明

var app = express();

创建 express 应用实例。

app.use(express.static('front'));
app.use(bodyParser.json());

正在加载Express的附加功能,即中间件。

静态中间件可以将指定文件夹中的文件作为静态文件公开。

bodyParserミドルウェアは、POSTメソッドで送られてきたリクエストボディを解析し、JSONデータは自動的にオブジェクトにしてくれます。

app.listen(3000);

3000番ポートで接続を待ち受けます。

mongodb.MongoClient.connect("mongodb://localhost:27017/test", function(err, database) {

localhostのMongoDBのデータベース「test」に接続します。コールバック引数のdatabaseにデータベースオブジェクトが返ってきます。

MongoDB的默认端口号是27017。

  users = database.collection("users");

集合users中的对象将被存储在变量users中。

发送请求到/api/users

app.get("/api/users", function(req, res) {
  users.find().toArray(function(err, items) {
    res.send(items);
  });
});

这个函数会返回一个包含集合所有内容的数组列表。它通过使用 `users.find()` 来获取整个集合,并通过 `toArray()` 将其转换成数组形式进行输出。这里对应了 `$resource` 的 `query()` 部分。

获取/api/users/:_id

app.get("/api/users/:_id", function(req, res) {
  users.findOne({_id: mongodb.ObjectID(req.params._id)}, function(err, item) {
    res.send(item);
  });
});

我将返回一个文档。通过使用users.findOne()方法获取和输出指定ObjectId的文档。由于ObjectId不是字符串类型,所以需要进行转换。这是与$resource的get()相对应的部分。

发表/接收 /api/users 请求

app.post("/api/users", function(req, res) {
  var user = req.body;
  if (user._id) user._id = mongodb.ObjectID(user._id);
  users.save(user, function() {
    res.send("insert or update");
  });
});

根据请求体中的JSON数据插入或更新文档。如果save方法中的_id不存在,则进行插入操作;如果存在,则进行更新操作。将_id从字符串类型转换为ObjectId类型。

删除/api/users/:_id

app.delete("/api/users/:_id", function(req, res) {
  users.remove({_id: mongodb.ObjectID(req.params._id)}, function() {
    res.send("delete");
  });
});

删除指定ObjectId的文档。

index.html 解释

<div ng-view></div>

ngRoute的功能使得模板的HTML文件可以嵌入此处。

<script src="//code.angularjs.org/1.3.15/angular.min.js"></script>
<script src="//code.angularjs.org/1.3.15/angular-resource.min.js"></script>
<script src="//code.angularjs.org/1.3.15/angular-route.min.js"></script>

从CDN加载AngularJS,ngResource用于处理REST API,ngRoute用于进行路由。

  app.config(function($routeProvider) {
    $routeProvider.when('/users', {
      templateUrl: 'list.html', controller: 'ListCtrl'
    }).when('/users/:_id', {
      templateUrl: 'edit.html', controller: 'EditCtrl'
    }).otherwise({
      redirectTo: '/users'
    });
  });

这是关于路由设置的内容。

当用户访问 /#/users 时,加载并展示 list.html 文件。

当访问 /#/users/_id(其中_id为每个文档的ObjectId)时,加载并展示edit.html页面。

其他時候將重定向到 /#/users。

  app.factory('User', function($resource) {
    return $resource('/api/users/:_id');
  });

我定义了一个名为User的服务,用于使用在app.js中定义的REST API。

  app.controller('ListCtrl', function($scope, $route, User) {
    $scope.users = User.query();
    $scope.delete = function(_id) {
      User.delete({_id: _id}, function() {
        $route.reload();
      });
    };
  });

这是一个列表页面的控制器。当调用 User.query() 方法时,将获取到集合 users 的列表数据 GET /api/users。
当点击删除链接时,将调用 DELETE /api/users/:_id 方法来删除对应的文档,并更新页面。

  app.controller('EditCtrl', function($scope, $routeParams, $location, User) {
    if ($routeParams._id != 'new') $scope.user = User.get({_id: $routeParams._id});
    $scope.edit = function() {
      User.save($scope.user, function() {
        $location.url('/');
      });
    };
  });

這是編輯畫面的控制器。當 URL 是 /#/users/new 時,它是用於新增功能;其他情況下,它是用於編輯功能。
當按下註冊按鈕時,會呼叫 POST /api/users,進行文件的插入和更新。

结束

由于这是一种我不熟悉的技术组合,所以可能存在一些多余或错误的地方。如果您能指出来,我将不胜感激。

bannerAds