meen(mean)でtodoアプリを作成してみた。

概要

いまさらながら、気になっていたmean環境をごにょごにょ触って、
mongo + express + ejs + node.js(meen(angularjs不使用))にて、定番のtodoアプリを作ってみました。
なんてことないゴミのようなアプリです。
bower | npm | mongo などの説明は割愛します。

環境

ローカル:
CentOS 6.8
node.js 9.2.0
npm 5.5.1
express 4.15

公開サーバ:
IBM Cloud(bluemix)無料枠

やったこと(メモ)

node.js + npm + bower + mongo は既にローカルで使えるようになっていたので(たぶん気になったときにいれたまま)
expressだけnpmからインストールしました。

webアプリのひな型作成

expressコマンドでひな型を、まず作成します。
デフォルトでviewがjadeになっているようですが、わかりやすそうなejsを指定しました。

express todoapp -e ejs

上記でひな型サイト(アプリ名)のディレクトリができるので、cd todoappをやって、npm startで実行!
するのですが、そのままやるとモジュールないよーエラーになります。

Error: Cannot find module 'express'

npm install or npm i でインストール後、npm startで動きます。
package.jsonに定義してあるscriptsに書いてあるものが動くようです。

ポートはbin/wwwに書いてあって、デフォルト3000になっています。

bowerで使うjsやcssを取得

bower install jquery bootstrap4 underscore

/public/vendor 直下に配置されます。

viewsの修正

views/index.ejsがトップページになっています。app.jsあたりをごにょごにょすれば返れそう。

  <%- include('_head'); %>

などとやれば、ファイルを分けて読み込むことができるのでheader, footer等を外出しにしました。

javascriptをごにょごにょ

javascriptのほうで、jQuery(ajax)を使って登録や読み込みなどを書きました。

main.js

$(function(){
function getDate(task_date) {
let date = new Date (task_date);
let y = date.getFullYear();
let m = date.getMonth() + 1;
let d = date.getDate();
let h = date.getHours();
let i = date.getMinutes();
if (m < 10) {
m = '0' + m;
}
if (d < 10) {
d = '0' + d;
}
if (h < 10) {
h = '0' + h;
}
if (i < 10) {
i = '0' + i;
}
return y+'/'+m+'/'+d+' '+h+':'+i;
}
function render_task_list(task_list) {
let tasks = task_list;
let list_tag = '';
for(let i = 0; i < tasks.length; i++) {
list_tag +=
'<li class="list-group-item" style="padding-left:35px;">'+
'<label class="form-check-label">'+
'<input class="form-check-input" type="checkbox" value="" style="margin-top: 20px;margin-left: -25px;">'+
getDate(tasks[i].create_date)+'<br />'+
tasks[i].content+
'</label>'+
'<button type="button" class="close close_task_list"  data-id="'+tasks[i]._id+'">'+
'<span>&times;</span>'+
'</button>'+
'</li> ';
}
$("#task_list").html(list_tag);
}
if(!_.isEmpty(localStorage.getItem('task_input_user'))) {
$('#user_name').val(localStorage.getItem('task_input_user'));
$('#user_name').prop('disabled', true);
$.ajax({
type: 'POST',
url:'/selecttask',
dataType: "json",
data: {user_name:$('#user_name').val()}
}).done(function(data, textStatus, jqXHR){
if(data.task_list.length != 0){
render_task_list(data.task_list);
} else {
$("#task_list").html('<li class="list-group-item">'+$('#user_name').val()+'さんのタスクはありません。'+'</li>');
}
}).fail(function(jqXHR, textStatus, errorThrown){
});
}
// 閉じるボタン押下時
$(document).on('click', '.close', function () {
$(this).parent().slideUp();
});
// 取り消し線
$(document).on('change', '.form-check-input', function () {
if($(this).prop('checked')){
$(this).parent().attr('style', 'text-decoration: line-through;');
} else {
$(this).parent().attr('style', '');
}
});
// ユーザー名変更 一覧取得
$("#user_name").change(function(){
let current_user = $(this).val();
$.ajax({
type: 'POST',
url:'/selecttask',
dataType: "json",
data: {user_name:current_user}
}).done(function(data, textStatus, jqXHR){
if(data.task_list.length != 0){
render_task_list(data.task_list);
} else {
$("#task_list").html('<li class="list-group-item">'+current_user+'さんのタスクはありません。'+'</li>');
}
}).fail(function(jqXHR, textStatus, errorThrown){
});
});
// 一覧削除ボタン押下
$(document).on('click', '.close_task_list', function () {
$.ajax({
type: 'POST',
url:'/deletetask',
dataType: "json",
data: {id:$(this).attr('data-id')}
}).done(function(data, textStatus, jqXHR){
$('#info_delete_success').slideDown();
}).fail(function(jqXHR, textStatus, errorThrown){
$('#error_delete_faild').slideDown();
});
});
// タスク追加ボタン押下
$("#btn_add").click(function(){
// バリデーション
if(_.isEmpty($('#user_name').val())) {
$('#alert_empty_user').slideDown();
return false;
}
if(_.isEmpty($('#contents').val())) {
$('#alert_empty_task').slideDown();
return false;
}
// タスク追加
$.ajax({
type: 'POST',
url:'/addtask',
dataType: "json",
data: {user_name:$('#user_name').val(), content:$('#contents').val()}
}).done(function(data, textStatus, jqXHR){
$('#info_add_success').slideDown();
if(data.task_list.length != 0){
$('#user_name').prop('disabled', true);
localStorage.setItem('task_input_user', $('#user_name').val());
render_task_list(data.task_list);
}
}).fail(function(jqXHR, textStatus, errorThrown){
$('#error_add_faild').slideDown();
});
});
});

routersの修正

routers/index.jsに取得・登録・削除時にpostでアクセスするので、それぞれ処理を書きました。
mongodbを使うためにmongooseをインストールしました。
後、登録されるデータのXSS対応のためにhtmlspecialcharsモジュールも一緒にいれてみました。

index.js

let mongoose = require('mongoose');
let express = require('express');
let router = express.Router();
let htmlspecialchars = require('htmlspecialchars');
mongoose.connect('mongodb://localhost/todoapp');
const Task = mongoose.model('Task', {user_name: String, content: String, create_date:Date});
/*
* 初期表示
*/
router.get('/', function(req, res, next) {
res.render('index', {task_list:null});
});
/*
* タスク取得処理
*/
router.post('/selecttask', function(req, res) {
let post_data = req.body;
Task.find({user_name:post_data.user_name}, function (err, tasks) {
if(err) throw err;
res.send({ task_list:tasks });
});
});
/*
* タスク追加処理
*/
router.post('/addtask', function(req, res) {
let post_data = req.body;
let task = new Task({user_name: htmlspecialchars(post_data.user_name), content: htmlspecialchars(post_data.content), create_date:new Date()});
task.save(function(err){
if(err) throw err;
Task.find({user_name:post_data.user_name}, function (err, tasks) {
if(err) throw err;
res.send({message:'ok', task_list:tasks});
});
});
});
/*
* タスク削除処理
*/
router.post('/deletetask', function(req, res) {
let post_data = req.body;
Task.remove({_id:post_data.id}, function(err){
if(err) throw err;
});
res.send({message:'ok'});
});
module.exports = router;

所感

npmでいろいろインストールして、expressでひな型を作るだけで簡単にページが作れるので楽だと思いますた。
angularjsを現在見ていたりしますが、angular2からangularになって、typescriptになっているので全然作り方が変わるぽいです。
ibm cloudを使ってみましたが、メリットは特に感じませんでした。herokuでもいいかも。

ソース

ソースはこちら。
https://github.com/YasuakiHirano/todoapp
デモ
https://codelike-todoapp-timely-impala.mybluemix.net/
無料枠なので10日後には落ちてるかと思われます。(´・ω…:.;::..

元のサイトへ