MongoDB
Express
nodejs
AngularJS
MEAN

インフラしかやってこなかった私が、MEAN StackでWebアプリケーションを学ぶ


1. はじめに

MEAN Stackでブログアプリを構築する方法を解説します

初学者向けに、MEAN Stackの仕組みを大まかに理解することを目的にしています。

サンプルソースは、DimiMikadze/Mean-Blog の方のソースを拝借させていただきます。( ソースも読みやすく、クリスマスなデザインも素敵ですね。 )

スクリーンショット 2018-12-20 1.07.40 AM.png

(タイトルについて)

会社に入社してから早4年間、インフラ周りばっかりやってきた自分。インフラ以外のこと、わからないじゃんヤバイ。ろくにjavascriptもかけない。フロントエンドの技術であるとは知っているけど、Angularって??という状態。戒めを兼ねて、ここ1-2ヶ月で、Node.jsの勉強とMEAN Stackでアプリケーションの構築 について勉強してきたので、それのまとめです。

私のように、サーバサイドNode.js、Angular、ExpressやMongoなどを初めて学ばうとする方の参考になれば幸いです。 レベルとしたら、新人研修+α くらいなレベル感になればいいかなと思います。


2. 今回やること

以下のようなWebアプリケーション(ブログアプリ)を MEAN Stackで作成します。

概要を理解することを目的としているため、詳細のソースコードの解説、インストール&実行手順等は省略させていただきます。

image.png


機能


  • ブログ記事の参照 [ユーザ]

  • ブログ記事の管理(作成、更新、削除) [管理者]


3. 目標

ありがちですが、ブログ記事のCreate/Edit/Deleteができて

以下のそれぞれの技術要素のつながりと、アプリが動く大まかな仕組みを理解したいと思います。



  • Mongo


  • Express


  • Angular


  • Nodejs

上記でMEAN Stackですね。私も触ってみて、すごく取っ掛かりやすいと感じました。


4. まずは動かしてみよう

git clone https://github.com/DimiMikadze/Mean-Blog.git

cd ./server & npm install
node start.js

http://localhost:3000 にアクセスしてみましょう。ブログアプリが立ち上がっています。

※ mongodbを起動している必要があります。dockerが使える環境であれば、以下で起動できます

docker run -p 27017:27017 --name mongodb -d mongo


5. ブログアプリの解説

実際に、ソースコード を見ながら、MEAN Stackがどのような仕組みで動いているのかを理解していきたいと思います。


5.1 まずは、Stack 図の概要

MEAN Stackはそれぞれ以下のような役割でつながりがあります。

image.png

今回見ていくブログアプリも、もちろん上図のように構成されています。


5.2 ソースの概要


  • まずは、ソースの大枠を掴みましょう。5.3 個別の解説 で各ソースを見ていくので、その時の地図として使います。

  • ※ 説明に必要な部分だけを抜粋しています。 *は今回取り上げているソース

/server

├── app.js *
├── config
├── models
│   ├── blog.js
│   └── user.js
├── node_modules
├── package.json
└── routes
├── admin.js *
├── index.js *
└── testUser.js

/client
├── app
│   ├── app.js *
│   ├── controllers.js *
│   ├── factories.js *
│   └── scripts.js
├── node_modules
├── package.json
├── public
│   ├── bower
│   ├── css
│   ├── images
└── views
├── admin
│   ├── blog
│   │   ├── create.html *
│   │   ├── index.html *
│   │   └── update.html *
│   ├── layouts
│   │   └── app.html
│   └── login.ejs
├── client
│   ├── blog
│   │   └── blog.ejs
│   ├── index.ejs
│   ├── layouts
│   │   ├── footer.ejs
│   │   └── header.ejs
│   └── test.ejs
└── sitemap.ejs


  • 実行するコードは app.jsです

node app.js

で実行すると、以下の部分に記述されているように、サーバプロセスが、3000portでリッスンします。


server/app.js

var port = process.env.PORT || 3000;

var server = app.listen(port, function() {
var host = server.address().address;
var port = server.address().port;

console.log("app listening on " + host + " " + port);
});



5.3 個別の解説

実際にソースコードが動くフローごとに見て行こうと思います。


  • 5.3.1 (R) ブログ記事一覧を参照 (トップページにアクセス)

  • 5.3.2 (R) ブログ記事を指定して参照 (個別ページ)

  • 5.3.3 (RD) ブログ記事 一覧画面 & 削除

  • 5.3.4 (C) ブログ記事 作成 画面

  • 5.3.5 (U) ブログ記事 更新 画面

※ (R) : Read、 (D) : Delete、 (C) : Create、 (U) : Update


5.3.1 (R) ブログ記事一覧を参照 (トップページにアクセス)

http://localhost:3000/ にアクセスしてみましょう。トップページで、ブログの一覧が参照できます

image.png


server/routes/index.js

/**

* Render Main Page
*/

router.get("/", function(req, res) {
blog.find({}).sort({ created_at: -1 }).exec(function(err, blogs) {
if(err) throw(err);

res.render("client/index", {
title: "Dimitri Mikadze",
blogs: blogs,
desc: "Dimitri Mikadze Personal Blog",
url: "/"
});
});
});


ここで、client/views/client/index.ejs が返されます

ここではEJSというテンプレートエンジンで、トップページのHTMLコードが生成されています。

ブログ記事の一覧は以下の処理で取得していて、その結果をテンプレートエンジンに渡していますね。

blog.find({}).sort({ created_at: -1 }).exec(function(err, blogs) {


5.3.2 (R) ブログ記事を指定して参照 (個別ページ)

トップページのブログ一覧から、個別ページにアクセスしてみましょう。

image.png


server/routes/index.js

/**

* Render blog by url param id
*/

router.get("/blog/programming/:name/:id", function(req, res) {

var name = req.params.name;
var id = req.params.id;

blog.findById(id, function(req, blog) {
res.render("client/blog/blog", {
title: name.split('-').join(' '),
blog: blog,
desc: blog.short_desc,
url: "/blog/programming/" + name + "/" + id
})
});
});


ここで、client/blog/blog.ejs が返されます。

ここでもEJSが使用されています。idを指定して、ブログ記事を取得していて、その結果をテンプレートエンジンに渡していますね。

blog.findById(id, function(req, blog) {


5.3.3 (RD) ブログ記事 一覧画面 & 削除

ここからが本番です。管理画面にアクセスしてみましょう。 http://localhost:3000/login でアクセスできます。(ログインの処理の解説は省略します)

image.png


server/routes/admin.js

/**

* Render Blog Page
*/

router.get("/blogs-page", function(req, res) {
res.render("admin/blog/index.html");
});

ここで、client/views/admin/blog/index.html がレスポンスとして返されます。

このhtmlの中で、Angularが動きます。

以下で、記事の一覧を表示している事がわかります。


client/views/admin/blog/index.html

<tr ng-repeat="b in blogs | orderBy: '-created_at'">

[..]
</tr>

このとき着目するのが、Delete 機能

該当の記事をクリックすると以下が呼び出されるように設定されているのがわかります。


client/views/admin/blog/index.html

<td><a ng-click="showDeleteModal(b._id)" class="delete">Delete</a></td>


このとき呼ばれるのが、以下の controller


client/app/controllers.js

    // Show Delete Modal

$scope.showDeleteModal = function(id) {
$scope.modal = true;
$scope.modal = myBlogFactory.editBlog(id).then(function(res) {
$scope.blog = res.data;
});
};

こんな感じで削除確認のモーダルウィンドウが出てきます。

image.png

client/views/admin/blog/index.html で定義されているModal部分を見ると

"Yes" をクリックすると deleteBlog(blog._id) が呼ばれることがわかります


client/views/admin/blog/index.html

         <h3>Do you really want to delete <span ng-bind="blog.short_name"></span>?</h3>

<div class="cm-buttons">
<button class="btn btn-danger" ng-click="deleteBlog(blog._id)">Yes</button>
<button class="btn btn-default" ng-click="cancelModal()">No</button>
</div>

このdeleteBlog(blog._id) ですが、以下に定義されていますね。


client/app/controllers.js

[..]

// Delete Blog
$scope.deleteBlog = function(id) {
myBlogFactory.deleteBlog(id).then(function(result) {
if(result.data.success) {
$state.go($state.current, {}, { reload: true });
}
});
};
[..]

ここでさらに、myBlogFactory を見てみましょう。http.postで、 サーバの以下のAPIを呼んでいます。

admin/blog/" + id + "/delete


client/app/factories.js

[..]

// delete blog
deleteBlog: function(id) {
return $http.post("/admin/blog/" + id + "/delete");
},
[..]

このAPIどこで定義されているかというと、以下にいました。


server/routes/admin.js

// =========================================

// Angular Routes
// =========================================
[..]
/**
* Angular Post Delete Blog
*/

router.post("/blog/:id/delete", function(req, res) {
blog.findByIdAndRemove(req.params.id, function(err) {
if(err) throw err;

res.json({success: true});
});
});
[..]


これで、clientからblogのidが指定されて、deleteのAPIが呼ばれてブログ記事が削除されるということなのですね。なるほど。


Angularが動く時の登場人物

ちょっと複雑なので、全体を整理したいと思います。Angularはこんな感じで動きます。後述の処理も同じような仕組みで動きます。

image.png

上記は、ブログ記事削除の機能ですが、Angularがどのように動くか、イメージできたと思います。


5.3.4 (C) ブログ記事 作成 画面

Create Blog にアクセスしてみましょう。

image.png


server/routes/admin.js

/**

* render Create new asset page
*/

router.get("/asset/new", function(req, res) {
res.render("admin/asset/create.html");
});

ここで、client/views/admin/blog/create.html がレスポンスとして返されます。

formが定義されている下に、Create 機能があります

formに入力した、ブログのタイトル、本文で、新しい記事が作られるように設定されているのがわかります。


client/views/admin/blog/create.html

<button class="btn btn-primary btn-lg" ng-click="createBlog()"/>Create</button>


このとき呼ばれるのが、以下の controller


client/app/controllers.js

[..]

// Create blog
$scope.createBlog = function() {
myBlogFactory.createBlog($scope.blog).then(function(result) {
if(result.data.success) {
$scope.blog = {};
$state.go("admin-all-blogs");
}
});
};
[..]

ここでさらに、myBlogFactory を見てみましょう。http.postで、 サーバのAPIを呼んでいます。

admin/blog/" + id + "/create


client/app/factories.js

[..]

// create blog
createBlog: function(blog) {
return $http.post("/admin/blog/create", blog);
}
[..]

このAPIどこで定義されているかというと、以下にいます


server/routes/admin.js

// =========================================

// Angular Routes
// =========================================
[..]
/**
* Angular Post Create Blog
*/

router.post("/blog/create", function(req, res) {
blog.create(req.body, function(err) {
if(err) throw err;
res.json({ success: true })
});
});
[..]

これで、formに入力した内容で、ブログの新しい記事が作られるのですね。ちょっと要領をつかめてきました。


5.3.5 (U) ブログ記事 更新 画面

最後に、管理ページから、個別記事(例ではArticle01) にアクセスしてみましょう。

image.png


server/routes/admin.js

/**

* Render Update Asset Page
*/

router.get("/asset/update", function(req, res) {
res.render("admin/asset/update.html");
});

ここで、client/views/admin/blog/update.html がレスポンスとして返されます。

formが定義されている下に、Update 機能があります

formに入力した、ブログのタイトル、本文で、新しい記事が作られるように設定されているのがわかります。

これはもう、Create機能とほぼ同じですね。


client/views/admin/blog/update.html

    <button class="btn btn-primary btn-lg" ng-click="updateBlog(blog)"/>Update</button>


このとき呼ばれるのが、以下の controller


client/app/controllers.js

[..]

// Update blog
$scope.updateBlog = function(blog) {
myBlogFactory.updateBlog(blog).then(function(result) {
if(result.data.success) {
$state.go("admin-all-blogs");
}
});
};
[..]

ここでさらに、myBlogFactory を見てみましょう。http.postで、 サーバのAPIを呼んでいます。

admin/blog/" + id + "/update


client/app/factories.js

[..]

// update blog
updateBlog: function(blog) {
return $http.post("/admin/blog/" + blog._id + "/update", blog);
},
[..]

このAPIどこで定義されているかというと、以下にいます


server/routes/admin.js

// =========================================

// Angular Routes
// =========================================
[..]
/**
* Angular Post Update blog
*/

router.post("/blog/:id/update", function(req, res) {

blog.findByIdAndUpdate(req.params.id, req.body, function(err) {
if(err) throw err;

res.json({success: true});
});
});
[..]


これで、formに入力した内容で、ブログ記事が更新されるのですね。要領をつかめたきがします。


6. まとめ


  • MEAN StackのWebアプリケーションって、こんなふうに動いているんだと、大体のイメージを掴んでいただければと思います。

  • ※ データアクセスの部分等 書きたかったのですが、時間が足りず。 今後追記させていただきます。


さいごに

正直、私自身まだ詳細まで理解できていない部分が多く、学習メモにとどまってしまっているところもあるかと思います。ご容赦ください。今後、詳細部分については本記事に追記していきたいと思っています。

Node.jsについては、入門書で自主学習ができたのですが、Angularに関しては入門書でもなかなか読み進めることができず、そんな中 このサンプルアプリが私にとって非常に理解しやすいものであったため、今回同じような境遇にある人に紹介できればと思い、記事を書かせていただきました。