JS.12 Node.jsでコマンドメモアプリケーション

Node.jsとは

Node.js(略してNode)は、ブラウザ側で機能する言語であったJavaScriptをサーバー側で動作するようにしたもの。

使い方

以下の hello.js というファイルを node hello.js(または拡張子を無視して node hello)というコマンドで立ち上げる事ができる。

console.log("Hello, world!");
$ node hello.js
> Hello, world!

npm

npmとは、"Node Package Manager"の略で、Node関連の便利ツールを扱うもの。代表的なものには以下のモジュールがある。

パッケージ名 説明
express ホームページを表示させる
cordova モバイルデバイスとの連携をしやすくする
forever サーバーが落ちてから再起動する際、Nodeプログラムも同時にかつ自動的に再起動する。

第三者が無償で提供しているものがたくさんあるので、使えるものがあるのであれば積極的に使うべき。

作ったもの

コマンドラインから操作するメモ帳アプリケーションを作成した。なお、コードはECMAScriptを採用して書いている。

メモ追加

$ node app.js add --title=memo1 --body=initial commit.
保存されました。
-------------
タイトル:memo1
内容:initial

ここで、notes-data.json の中身は以下のようになっている。

[{"title":"memo1","body":"initial"}]
$ node app.js add --title=memo2 --body=second commit.
保存されました。
-------------
タイトル:memo2
内容:second
[{"title":"memo1","body":"initial"},{"title":"memo2","body":"second"}]

メモ表示(全部)

$ node app.js list
表示数:2
-------------
タイトル:memo1
内容:initial
-------------
タイトル:memo2
内容:second

メモ表示(一部)

$ node app.js read --title=memo2
見つかりました。
-------------
タイトル:memo2
内容:second

メモ削除

$ node app.js remove --title=memo3
削除されませんでした。
$ node app.js remove --title=memo2
削除されました。
[{"title":"memo1","body":"initial"}]

コード

以下のようなディレクトリ構造下で行う。

├── app.js # メモアプリの入り口となるスクリプト
├── node_modules # npm install したライブラリが入っている。
│   └── ...
├── notes-data.json # メモが保存されるjsonファイル
├── notes.js # app.jsで呼び出す関数が書かれている。
├── package-lock.json # 同じpackage.jsonからのモジュールインストールが、違ったインストールに陥る結果を回避するためのもの。
└── package.json # どのようなライブラリをインストールしたかなど

app.js

// メモアプリの入り口となる関数。
const fs = require("fs");
const notes = require("./notes.js");

const yargs = require("yargs"); // argv よりも多機能なのでこちらを採用。
const argv = yargs.argv;
const command = argv._[0]

if (command === "add"){
  let note = notes.addNote(argv.title, argv.body);
  // 保存された内容をアウトプット
  if (note){
    console.log("保存されました。");
    notes.logNotes(note);
  } else {
    console.log("タイトルが重複しています。");
  }
} else if (command === "list"){
  let allNotes = notes.showAll();
  console.log(`表示数:${allNotes.length}`);
  allNotes.forEach(note => notes.logNotes(note));
} else if (command === "read"){
  let note = notes.readNote(argv.title);
  if (note){
    console.log("見つかりました。");
    notes.logNotes(note);
  } else {
    console.log("データが見つかりませんでした。");
  }
} else if (command === "remove"){
  let noteRemoved = notes.removeNote(argv.title);
  let message = noteRemoved ? "削除されました。" : "削除されませんでした。";
  console.log(message);
}

notes.js

const fs = require("fs")

// ノートの内容を取得する。(存在しなければ空の配列)
let fetchNotes = () => {
  try {
    let notesString = fs.readFileSync("notes-data.json");
    return JSON.parse(notesString);
  } catch(e){
    return [];
  }
};

// ノートの内容を保存する。
let saveNotes = notes => {
  fs.writeFileSync("notes-data.json", JSON.stringify(notes));
}

// ノートに書き込む関数
let addNote = (title, body) => {
  let notes = fetchNotes();
  let note = {
    title,
    body
  };
  // 同じタイトルのデータがあれば何もしない。
  let duplicatedNotes = notes.filter(note => note.title === title);
  if (duplicatedNotes.length === 0){
    notes.push(note);
    saveNotes(notes);
    return note;
  }
}

let showAll = () => {
  return fetchNotes();
}

let readNote = title => {
  let notes = fetchNotes();
  let filteredNotes = notes.filter(note => note.title === title);
  return filteredNotes[0]; // 1つしか見つからないはずだからindexは0。
}

// 指定したタイトルのデータを削除。
let removeNote = title => {
  let notes = fetchNotes();
  // 指定したタイトルにマッチしないデータのみを残して保存。
  let filteredNotes = notes.filter(note => note.title !== title);
  saveNotes(filteredNotes);
  // データが削除されたか(データ数が減ったか)のbool値を返す。
  return notes.length !== filteredNotes.length;
}

let logNotes = (note) => {
  console.log("-------------");
  console.log(`タイトル:${note.title}`);
  console.log(`内容:${note.body}`);
};

module.exports = {
  addNote,
  showAll,
  readNote,
  removeNote,
  logNotes
};

package.json

{
  "name": "memo",
  "version": "1.0.0",
  "description": "Command line memo application",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "shuto",
  "license": "ISC",
  "dependencies": {
    "express": "*",
    "grunt": "^1.0.4",
    "yargs": "^4.7.1"
  }
}
other contents
social