1. はじめに
MEAN Stackでブログアプリを構築する方法を解説します
初学者向けに、MEAN Stackの仕組みを大まかに理解することを目的にしています。
サンプルソースは、DimiMikadze/Mean-Blog の方のソースを拝借させていただきます。( ソースも読みやすく、クリスマスなデザインも素敵ですね。 )
(タイトルについて)
会社に入社してから早4年間、インフラ周りばっかりやってきた自分。インフラ以外のこと、わからないじゃんヤバイ。ろくにjavascriptもかけない。フロントエンドの技術であるとは知っているけど、Angularって??という状態。戒めを兼ねて、ここ1-2ヶ月で、Node.jsの勉強とMEAN Stackでアプリケーションの構築 について勉強してきたので、それのまとめです。
私のように、サーバサイドNode.js、Angular、ExpressやMongoなどを初めて学ばうとする方の参考になれば幸いです。 レベルとしたら、新人研修+α くらいなレベル感になればいいかなと思います。
2. 今回やること
以下のようなWebアプリケーション(ブログアプリ)を MEAN Stackで作成します。
概要を理解することを目的としているため、詳細のソースコードの解説、インストール&実行手順等は省略させていただきます。
機能
- ブログ記事の参照 [ユーザ]
- ブログ記事の管理(作成、更新、削除) [管理者]
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はそれぞれ以下のような役割でつながりがあります。
今回見ていくブログアプリも、もちろん上図のように構成されています。
5.2 ソースの概要
/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でリッスンします。
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/
にアクセスしてみましょう。トップページで、ブログの一覧が参照できます
/**
* 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) ブログ記事を指定して参照 (個別ページ)
トップページのブログ一覧から、個別ページにアクセスしてみましょう。
/**
* 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
でアクセスできます。(ログインの処理の解説は省略します)
/**
* Render Blog Page
*/
router.get("/blogs-page", function(req, res) {
res.render("admin/blog/index.html");
});
ここで、client/views/admin/blog/index.html がレスポンスとして返されます。
このhtmlの中で、Angularが動きます。
以下で、記事の一覧を表示している事がわかります。
<tr ng-repeat="b in blogs | orderBy: '-created_at'">
[..]
</tr>
このとき着目するのが、Delete
機能
該当の記事をクリックすると以下が呼び出されるように設定されているのがわかります。
<td><a ng-click="showDeleteModal(b._id)" class="delete">Delete</a></td>
このとき呼ばれるのが、以下の controller
// Show Delete Modal
$scope.showDeleteModal = function(id) {
$scope.modal = true;
$scope.modal = myBlogFactory.editBlog(id).then(function(res) {
$scope.blog = res.data;
});
};
こんな感じで削除確認のモーダルウィンドウが出てきます。
client/views/admin/blog/index.html で定義されているModal部分を見ると
"Yes" をクリックすると deleteBlog(blog._id)
が呼ばれることがわかります
<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)
ですが、以下に定義されていますね。
[..]
// 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
[..]
// delete blog
deleteBlog: function(id) {
return $http.post("/admin/blog/" + id + "/delete");
},
[..]
このAPIどこで定義されているかというと、以下にいました。
// =========================================
// 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はこんな感じで動きます。後述の処理も同じような仕組みで動きます。
上記は、ブログ記事削除の機能ですが、Angularがどのように動くか、イメージできたと思います。
5.3.4 (C) ブログ記事 作成 画面
Create Blog
にアクセスしてみましょう。
/**
* 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に入力した、ブログのタイトル、本文で、新しい記事が作られるように設定されているのがわかります。
<button class="btn btn-primary btn-lg" ng-click="createBlog()"/>Create</button>
このとき呼ばれるのが、以下の controller
[..]
// 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
[..]
// create blog
createBlog: function(blog) {
return $http.post("/admin/blog/create", blog);
}
[..]
このAPIどこで定義されているかというと、以下にいます
// =========================================
// 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)
にアクセスしてみましょう。
/**
* 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機能とほぼ同じですね。
<button class="btn btn-primary btn-lg" ng-click="updateBlog(blog)"/>Update</button>
このとき呼ばれるのが、以下の controller
[..]
// 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
[..]
// update blog
updateBlog: function(blog) {
return $http.post("/admin/blog/" + blog._id + "/update", blog);
},
[..]
このAPIどこで定義されているかというと、以下にいます
// =========================================
// 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に関しては入門書でもなかなか読み進めることができず、そんな中 このサンプルアプリが私にとって非常に理解しやすいものであったため、今回同じような境遇にある人に紹介できればと思い、記事を書かせていただきました。