Node.jsのまとめ

はじめに

Node.jsについて学んでみました。

Vue.jsのAPIサーバーとしてExpressも選択肢の1つとしてあることを知った(気づいた)のがきっかけです。

※Node.jsについて深掘りした結果長くなったので、Expressは記事を分けました。

Expressについて

✅ゴール
Node.jsとExpressの理解を深める

✅環境
Windows

✅参考教材
📚Node.js超入門

以下、超入門。

テンプレートエンジンにEJS活用。fetchメソッド使っていたり割とモダン。

以前はejs使うのでそこで抵抗感が出たけど悪くなさそう。Kindleで購入したのがミス。

後半はXAMPPでサーバー立ててMySQLを活用したExpressでの掲示板アプリ制作をする。

📚Nodeクックブック

以下、クックブック

サンプルコード

Node.js + Express入門

Node.js公式ドキュメント

Node.js API集(公式)

Node.js

✅Node.jsとは

JavaScript言語のランタイム環境(プログラムを実行するための環境)兼ライブラリの管理を担う(開発プラットフォーム)。

Webサーバー(httpオブジェクト)まで作ることができるコマンドプログラム。(つまり、サーバープログラムそのものから作ることができる)

✅使ってみる

起動と動作確認。

node
> console.log("hello");
hello
undefined

サーバー起動

node
> .editor // エディター起動
require('http').createServer((rq, rs)=>{rs.end('Hello Node.js');}).listen(8080);
// ctrl + Dで実行

http://localhost:8080/へアクセス。

基本的なサーバー構築

方法をまとめると以下。

①インターネットアクセスをするhttpオブジェクトを読み込む
②httpからサーバー用オブジェクトを作成
③サーバーオブジェクトを待ち受け状態にする※外部アクセスが来たら対応するもの

hotnodeモジュールで自動更新

インストール

npm -g i hotnode

server.jsの作成

const http = require('http');

const server = http.createServer((request, response) => {
  response.writeHead(200, {'Content-Type': 'text/html'});
  response.end('Hello Node.js');
});

server.listen(8080);

httpモジュール(オブジェクト)をロード。requireはNode.js固有のモジュールローディングシステム。
※httpモジュールでHTTPアクセスのための機能を提供する。

変数 = require( ID );

http.createServer()メソッドでhttp.Serverというサーバーのオブジェクトを作成。

引数に関数を用意し、以下のように定義する。

変数 = http.createServer( (request, response) => {
 ~~処理~~
});

※requestにはhttp.ClientRequest、responseにはhttp.ServerResponse(ヘッダー情報も扱える)というオブジェクトが引数に収められている。

.end()メソッドはクライアントへの返信を終了するメソッド。引数にテキストを用意しておくと出力して終了する。

※endメソッドですべてのテキストを書きだすのは大変なのでresponse.write()メソッドで短く区切って出力する方法もある。

http.Serverオブジェクトの.listen()メソッドで待ち受けるポート番号を指定している。

起動

hotnode server.js

http://localhost:8080/へアクセス

※別パターンでの確認

curl http://localhost:8080/
>> <h1>Title</h1>Hello Node.js

URLルーティングの設定

pathモジュールの活用。basenameメソッドを用いてrequestオブジェクトのに格納されたURLのbasenameを抽出。

const http = require('http');
const path = require('path');

const pages = [
  {route: '',output: 'TOP'},
  {route: 'about',output: 'about'},
];

const server = http.createServer((request, response) => {
  let lookup = path.basename(decodeURI(request.url));
  pages.forEach(function(page) {
    if(page.route === lookup ) {
      response.writeHead(200, {'Content-Type': 'text/html'});
      response.end(typeof page.outpuut === "function" ? page.output():page.output);
    }
  });
  if(!response.finished) {
    response.writeHead(404);
    response.end("not found!");
  }
});

server.listen(8080);

確認

 

ルーティングが設定できているのがわかりますね◎

複数階層でのルーティングの場合

path.basenameの代わりにrequest.urlを使う。

const http = require('http');

const pages = [
  {route: '/',output: 'TOP'},
  {route: '/about',output: 'about'},
  {route: '/about/this',output: 'about/this'}, // add
];

const server = http.createServer((request, response) => {
  let lookup = decodeURI(request.url); // edit
~~割愛~~

確認

URLモジュールを利用した場合

※超入門参考

url.parse(request.url)でクライアントがアクセスしたURLを整理して取り出し。

switch部分でパース処理した値のpathnameを取り出し。/helloだとhelloの部分が取り出される。

const url = require('url');
~~~~
function getFromClient(request, response){
    var url_parts = url.parse(request.url, true);
    switch (url_parts.pathname) {
        case '/':
            response_index(request, response);
            break;
        case '/other':
            response_other(request, response);
            break;
        default:
            response.writeHead(200, {'Content-Type': 'text/plain'});
            response.end('no page...');
            break;
    }
}</pre>
url.parseを用いてURLデータをパース処理(データ解析して本来の状態に組み立てなおす)、ドメインやパス部分など、URLを構成するそれぞれの要素に分けて整理、ルーティングを分岐。⇒CSSの適用にも使われる※超入門P109

ためしにパース処理されたURLをコンソールに出力してみました。

編集
<div>
<pre>            response.end('no page...');
            break;
    }
    console.log(url_parts);
}</pre>
確認

</div>
<pre>Url {
protocol: null,
slashes: null,
auth: null,
host: null,
port: null,
hostname: null,
hash: null,
search: null,
query: [Object: null prototype] {},
pathname: '/other',
path: '/other',
href: '/other'
}</pre>
ついでにconsole.log(request)を出力したところ、ターミナル内に表示しきれない程、膨大な情報が表示されました。まだまだ知らないことが多すぎる。

<strong>✅スタイルの適用</strong>

index.ejsのヘッドタグ内に以下を追記。
<div>
<pre></pre>
stylesheet.cssの作成
<div>
<pre>h1 {
  color: blue;
}</pre>
</div>
</div>
</div>
app.jsに以下を追記
<div>
<pre>const style_css = fs.readFileSync('./style.css', 'utf8');
~~~~
case '/style.css':
  response.writeHead(200, {'Content-Type': 'text/css'});
  response.write(style_css);
  response.end();
  break;

確認

http://localhost:3000/style.cssへアクセス

HEAD情報を加える

①HTMLの<head>タグ内に用意する

http.ServerResponseのメソッドを利用する

ヘッダー情報の設定

response.setHeader( 名前, 値 );

ヘッダー情報の取得

変数 = response.getHeader( 名前 );

ヘッダー情報の出力

response.writeHead( コード番号, メッセージ );

※ステータスコードを付けて出力する。

✅HTMLタグの適用

.endメソッドにそのまま記入すればOK

response.end('<h1>Title</h1>Hello Node.js');

.end()メソッドの1文で記述するのが困難な場合は、response.writeメソッドを活用する。

const http = require('http');

var server = http.createServer(
    (request,response)=>{ 
      response.setHeader('Content-Type','text/html');
      response.write('<!DOCTYPE html><html lang="ja">');
      response.write('<head><meta charset="utf-8">');
      response.write('<title>Hello</title></head>');
      response.write('<body><h1>Hello Node.js</h1></body></html>');
      response.end();
    }
);

server.listen(3000);

HTMLファイルの利用

参考:node.jsでファイルの入出力操作、クックブックP8~

fs(File System)モジュールを活用する。

一般的にファイルの入出力は時間がかかる処理のため、同期処理、非同期処理のメソッドが用意されている。

変数 = require('fs');

✅ファイルの読込

⭐非同期処理メソッドはコールバック関数で処理を明示しないとエラーになるので注意!!

readFileSync:同期処理メソッド

ファイルの読み込みが完了してから、後続の処理を行いたい場合に用いる。

sample.jsを作成

const hoge = require('fs').readFileSync('hogehoge.txt','utf-8');
console.log(hoge);

※別途hogehoge.txtを作成。

確認

node sample.js
>> hogehoge

utf-8を指定していないとバイナリデータで→のように表示されました。<Buffer 68 6f 67 65 68 6f 67 65>

readFile:非同期処理メソッド

ファイルの読み込みを待たずに後続処理を捌きたい場合に用いる。第3引数の関数は読込終了後実行される。

fs.readFile( ファイル名, エンコーディング, 関数);

✅txtファイルの書き換え

const fs = require('fs');
let text = "書き出したい内容";
fs.writeFileSync("hoge.txt", text);

実行するとhoge.txtファイルの中身が書き換わる。

✅HTMLファイルの書き換え

index.html

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta http-equiv="content-type"
    content="text/html"; charset="UTF-8">
    <title>Index</title>
  </head>
  <body>
    <h1>dummy_title</h1>
    <p>dummy_content</p>
  </body>
</html>

server.js

const http = require('http');
const fs = require('fs');

var server = http.createServer(getFromClient);

server.listen(3000);
console.log('Server start!');

function getFromClient(request,response){
  fs.readFile('./index.html', 'UTF-8', 
    (error, data)=>{
      var content = data.
        replace(/dummy_title/g, 'title').
        replace(/dummy_content/g, 'content');
      response.writeHead(200, {'Content-Type': 'text/html'});
      response.write(content);
      response.end();
    });
};

replace();メソッドを活用。 参考

任意の文字列を別の文字列に置き換える(置換)ことができるメソッド。

以下のように、gフラグを付与することで複数の文字列を置き換えることが可能。

処理をしたい変数.replace(/置き換えたい文字列/g, '換わりになる文字列');

正規表現でも用いられるとのこと。ついでに見つけた正規表現の記事。いろいろ言われるsejukuだけど参考になる。

JSONデータの活用

✅基本的な使い方

参考:クックブック3章

JSON.stringifyを用いてJSON形式にフォーマットされた文字列を返す。

json-sample.jsの作成

module.exports = {
  shopA:{
    name: 'メロン',
    price: 500,
    place: '東京'
  }, shopB:{
    name: 'スイカ',
    price: 500,
    place: '宮城'
  }
};

out.jsの作成

var data = require('./json-sample');

// profilesオブジェクト全体を文字列に変換
data = JSON.stringify(data);

// 置換後の文字列をJSONに戻す
data = JSON.parse(data);

console.log(data);
console.log(data.shopA.name);

実行

> node out.js
{
  shopA: { name: 'メロン', price: 500, place: '東京' },
  shopB: { name: 'スイカ', price: 500, place: '宮城' }
}
メロン

参考:Node.jsのexportsとmodule.exportsの違いについてのメモ

ためしにexportsに書き換えて実行してみる。

// module.exports = {
exports.obj = {

実行

> node out.js
{
  obj: {
    shopA: { name: 'メロン', price: 500, place: '東京' },
    shopB: { name: 'スイカ', price: 500, place: '宮城' }
  }
}

実際に書き換えて実行すると違いが分かりやすいですね。階層を考慮しなくて済むので、module.exportsの方が楽ですね。

ちなみに文字列でJSONに戻さないと以下になる。

var data = require('./json-sample');

// dataオブジェクト全体を文字列に変換
data = JSON.stringify(data);

console.log(data);

実行

> node out.js
{"shopA":{"name":"メロン","price":500,"place":"東京"},"shopB":{"name":"スイカ","price":500,"place":"宮城"}}

また、console.log(data.shopA)とやるとエラーになることも確認。

🙄やっとJSON変換&文字列変換の重要性に気付くことができました。

文字列だと正規表現を適用できて、JSONだとオブジェクトでデータを処理することができるイメージ。

✅JSのオブジェクトとJSONの違い

参考:

JavaScriptのオブジェクトとJSONは別物
JavaScriptのJSONを理解してデータを活用できるようにしよう

JSONはテキストデータ(=通信に最も適した形式)。テキストデータが最も軽量のため、オブジェクトをテキストデータに変換している。
⇒そのため、.つなぎでデータを参照したり、pushメソッドやdeleteメソッドを使うこともできない。

JavaScriptのオブジェクトはキーがクォーテーションなし、値の文字列はクォーテーションあり。
JSONデータはキーをダブルクォーテーションで囲み、文字列も囲む。

JSON.stringifyでJSのオブジェクトをJSON文字列に変換。
JSON.parseでJSON文字列をJSオブジェクトに変換。

✅.jsonファイルを扱う

参考:JSONフォーマットとは?具体的な書き方についても徹底解説!!

.jsonファイルを作成

{
  "name": "メロン",
  "price": 500,
  "place": "東京"
}

app.jsの作成

const http = require('http');
const fs = require('fs');
const url = require('url');
const json = fs.readFileSync('./sample.json', 'utf8');

var server = http.createServer(getFromClient);
server.listen(3000);

console.log('Server start!');
// createServerの処理
function getFromClient(request, response){
    var url_parts = url.parse(request.url, true);
    switch (url_parts.pathname) {
      case '/':
      response.writeHead(200, {'Content-Type': 'text/html'});
      response.write('<head><meta charset="utf-8"></head>');
      response.write(json);
      response.end();
      break;
      
    default:
        response.writeHead(200, {'Content-Type': 'text/plain'});
        response.end('no page...');
        break;
    }
}

確認

🙄自前のJSONデータをサーバーに上げることができた◎headタグにメタ情報を入れないと文字化けする点が気になる。。。

CSVファイルを扱う

データ自体にカンマが入っていたりなどの問題点を解消するため、ya-csvモジュールをインストール。

npm i ya-csv

✅CSVファイルの生成

csv.jsの作成

var csv = require('ya-csv');

var writer = csv.createCsvFileWriter('data.csv');
var data = [['a','b','c','d','e','f','g'],['h','i','j','k','l','m','n']];

data.forEach(function(rec){
  writer.writeRecord(rec);
});

createCsvFileWriter()メソッドでya-csvのCsvWriterインスタンスの生成。ファイルに書き込むためのwriteStreamオブジェクトが格納されている。

writeRecordメソッドはそれぞれの配列をデータ行に再構築(一次元配列をCSVデータの1行として処理。)して、fs.WriteStreamインスタンスに渡す。

実行

node csv.js

data.csv生成

"a","b","c","d","e","f","g"
"h","i","j","k","l","m","n"

✅CSVファイルの読込

read-csv.jsを作成

var csv = require('ya-csv');
var reader = csv.createCsvFileReader('data.csv');
var data = [];

reader.on('data', function(rec){
  data.push(rec);
}).on('end', function(){
  console.log(data)
});

実行

> node read-csv.js
[
  [
    'a', 'b', 'c',
    'd', 'e', 'f',
    'g'
  ],
  [
    'h', 'i', 'j',
    'k', 'l', 'm',
    'n'
  ]
]

クエリパラメーターの活用

※参考:超入門P124~

クエリーパラメーターとは、URLの末尾に付ける値のこと。

取り出し方

①request.urlの値をパース処理。第2引数のtrueでクエリ-パラメーターもパース処理される。

var url_parts = url.parse(request.url, true);

②queryプロパティにパース処理されたクエリーパラメーターのオブジェクトが保管されているので代入。

var query = url_parts.query;

/?msg=helloだと、{‘msg’:’hello’}というオブジェクトになる。

※parseメソッドでtrue(もしくはfalse)を付けておかないと’meg=hello’とただのテキストになる。

あとはquery.msgで取り出せばOK。

フォーム送信(POST)を扱う

※超入門参考(クックブックでは2章)

PHPでは$_POST[]を使い、プログラムの処理を行う時点でPOSTデータの値に直接アクセス可能。
NodeではPOSTデータを含んだHTTPメッセージそのものを扱うことができるため、どのように扱うかは開発者判断。

流れ①データ取得②データをパース③必要な値を取り出してパース

GETはいつ、どこからどうアクセスされても常に同じ結果が返されるもの。POSTはその時、その状況で表示を行うものに使われる。

Query Stringモジュール(クエリーテキストを処理するための機能を提供)を使う。

requestのmethodプロパティでリクエストの方式(GET/POSTなど)を判断。

if ( request.method === 'POST' ){~~~

イベント処理。on()メソッドの中でイベントに応じて呼び出される関数を設定できる。

オブジェクト.on(イベント名, 関数);

dataイベント(クライアントからデータを受け取ると発生するイベント)で情報を取得。関数の引数にクライアントから受け取ったデータが入る。

endイベント(データの受け取りが完了すると発生するイベント)で、dataイベントが小分けに送られてくる場合を配慮。全てのデータを受け取ったら、データをパース処理(クエリーテキストからエンコードしないといけない)。

// データ受信のイベント処理
request.on('data', (data) => {
  body +=data;
});

// データ受信終了のイベント処理
request.on('end',() => {
  var post_data = qs.parse(body); // データのパース
  msg += 'POST送信で値「' + post_data.msg + '」を受け取り。';
  var content = ejs.render(other_page, {
    title:"Other",
    content:msg,
  });
  response.writeHead(200, {'Content-Type': 'text/html'});
  response.write(content);
  response.end();
});

✅諸々の変数をコンソール出力

・dataイベントで渡されるデータ

request.on('data', (d) => {
  body +=d;
  console.log(d);
});

適当にフォームに値を入力して送信したところこうなる。

<Buffer 6d 73 67 3d 25 45 33 25 38 31 25 38 32 25 45 33 25 38 31 25 38 32>

・dataイベントの値パース処理後

var post_data = qs.parse(body); // ★データのパース
msg += 'POST送信で値「' + post_data.msg + '」を受け取り。';
console.log(post_data);
var content = ejs.render(other_page, {

確認

[Object: null prototype] { msg: 'ああ' }

index.ejsの関連部分抜粋

 <form method="post" action="/other">
  <p>
      <input type="text" name="msg">

index.ejsで記載したinputのname属性と連携しているのがわかりますね。

今後深掘りしたい知識

・Websocketで双方向コミュニケーション

・1000番未満のポート番号はrootアクセス権限が必要らしい

・Expressでセッション機能(ミドルウェア)の追加

躓いた点

✅文字化け

ヘッドタグ内で文字エンコーディング指定されるよう追記して解決。

これにより、クライアント側にUTF-8の日本語テキストでデータが送信されることを伝える。

res.write('<head><meta charset="utf-8"><title>Hello</title></head>');

最初はresponse.write(‘<html lang=”ja”>’);で解決すると思いきや違った。

✅hotnodeでエラー

たとえば以下のようなスクリプト。

至って普通科と思いきや。。。

~~~
server.listen(3000);
console.log('Server start!');

実行するとエラー。

 hotnode node process restarted
C:\Users\ユーザー\AppData\Roaming\npm\node_modules\hotnode\javascript\hotloader.js:112
return util.print(data.toString());

nodeでは実行される。

> node server.js 
Server start!

原因はわからないが、とりあえずnodeコマンドで試していこうと思う。

おわりに

Expressを用いても、MySQLでDB層を用意することに代わりはない。

であれば、Laravelを使うと思った。

また、サーバーレスであり、API層を考えずにDBを活用、さらには認証機能、デプロイまでできてしまうFirebaseはとてもいいシステムだなと改めて思った。

6/7追記:

TechpitのReact教材でAPIサーバーを叩くのに、プロジェクト下にfrontとserverディレクトリを設けていて、serverディレクトリで別途サーバーを起動しているのが印象的だった。

これの仕組みは至ってシンプルで、nodeでサーバーを起動。React側からnodeで指定したポート番号のlocalhostに通信しているだけだったことにようやく気付けた。

また、JSONデータの変換の意味を理解できたのは収穫。

次はAPIサーバー周りにチャレンジしたい。

※超入門もなんだかんだ勉強になる。

サルでも分かるExpressでのjsonAPIサーバーの作り方

Node.js + Expressで超簡単API

API+JSONのサーバを作るにはExpress使うのが良さげ(ドキュメントがそれしかない)。

✅参考
Vue.js + ExpressでSSRをやってみた

Windows10 + Express & Pug(旧Jade)

.jadeはExpressにデフォルトで使われる拡張子。今はPugに名前が変わったらしい。(Expressのテンプレートは自由!!)

JavaScriptテンプレートエンジンのPug(Jade)とEJSの比較

JSにこんなテンプレートエンジンがあったなんて。

Content-Typeの一覧

decodeURI()|MDN

# CommonJS と ES6の import/export で迷うなら

CommonJSだとmodule.exports⇔ES6だとexport default

CommonJSだとrequire(‘module’)⇔ES6だとimport a from ‘module’

上記の感じで書き換えができる。

学習の備忘録

割愛した部分

超入門

P140 パーシャル、アプリケーション変数、クッキー
P262 jQueryでのAjax通信※JSON活用サンプル~RSS取得
P299 XAMPP&MySQL活用サンプル Queryメソッドを用いて実装

クックブック

P12~P30

メモ

・TCP(Transmission Control Protocol)はHTTP通信のバックボーンを提供。

・通常のWebサーバーのポート番号は80

CommonJSとは、JavaScriptでサーバサイドやコマンドラインツール、GUIツールなど色んなアプリを開発するための標準的なAPIの仕様

コメントを残す