本文書はNode.jsのExpressを利用してWEBアプリケーションを構築してみたいという人向けの入門的な文書でNode.jsの基礎からExpressの設定までExpressを使いこなす上で必要となる機能の説明を行っています。シンプルなコードを利用して動作確認を行なっているので本文書だけでExpressの基本的な機能を理解することができます。

  • Node.jsの基礎
  • npmの基礎
  • Expressdサーバの初期設定
  • ルーティングの設定
  • ミドルウェアの理解
  • Routerの設定
  • フロントエンドVueからのアクセス
  • Corsの設定
  • htmlファイルの表示方法
  • 画像ファイルの表示方法
  • 入力フォームから送信されてくるデータの受け取り方法

macOS環境で動作確認を行なっておりNode.jsを事前にインストールしておく必要があります。Node.jsのインストール方法は下記を参考に行なってください。

Node.jsの基礎

JavaScriptは本来、HTMLやCSSで構成された静的なHTML文書に動きを加えるために利用され、ブラウザ上で動作させることを目的にしたプログラミング言語です。ブラウザ上だけではなくサーバサイドでもJavaScriptを動作させるために登場したのがNode.jsです。Node.jsはJavaScriptに関連するプログラミング言語ではなくサーバサイドでJavaScriptを動かしたい場合に利用するJavaScriptランタイムです。JavaScriptに限らずどの言語でもその言語で記述したコードを動かすにはコードを理解して実行する環境が必要です。JavaScriptの場合はその実行環境がNode.jsにあたります。Node.jsによってブラウザ以外でもJavaScriptを実行することができます。

ブラウザのJavaScriptの実行エンジン(ソフトウェア)としてChromeで利用されるV8エンジン、Firefoxで利用されるSpiderMonkeyなどがあります。Node.jsはChoromeのV8エンジンを利用しています。

サーバサイドで動作すると言われると動作を行うためにメールサーバやHTTPサーバのようなサービスを想像し特別なサーバが必要なのかと思うかもしれませんがWindows, MacやLinuxなどさまざまなOSでインストールして利用することができます。OS上でインストールすることができるのでWEBサーバだけではなくデスクトップ用のアプリケーションを構築することができます。

はじめてのNode.js

Node.jsのインストールが完了したサーバ上ではnode -vコマンドでNode.jsのバージョンを確認することができます。


 % node -v
v16.13.0

Node.jsがインストールされた環境ではnodeコマンドを実行するとコンソール上でJavaScriptのコードを実行することができるようになります。コンソール上でJavaScriptのコードが実行できるモードをREPLといいます。REPLは Read Evaluate Print Loopの略です。JavaScriptの簡単なコードを実行するために利用することができます。

console.logを実行すると引数に指定した文字列が表示されます。


 % node
Welcome to Node.js v16.13.0.
Type ".help" for more information.
> console.log('Hello World');
Hello World
undefined

console.logだけではなく作成した関数を実行することもできます。


 % node 
Welcome to Node.js v16.13.0.
Type ".help" for more information.
> function add(a,b){
... return a + b;
... }
undefined
> add(1,2)
3

process, os, path, globalなどのコマンドも実行することができます。

ファイルによるJavaScriptコードの実行

任意の場所にmain.jsファイルを作成してJavaScriptのコードを記述します。


console.log('Hello World')

作成したmain.jsをnodeコマンドで指定すると指定したファイルの内容を実行することができます。これがNode.js上でJavaScriptを実行するための一般的な方法です。


 % node main.js
Hello World

モジュールの読み込み

プログラムの記述する量が増えていくると1つのファイルでは管理が難しくなるための複数のファイルに分割して管理を行う必要が出てきます。JavaScriptでは複数のファイルを利用するための仕組みを備えておりNode.jsではモジュールとして他のファイルに記述したコードを読み込むことができます。JavaScriptではモジュールを読み込む方法には複数の方法がありNode.jsではCommonJSを利用しているためモジュールを読み込む際にrequireを利用します。フロントエンドの開発でモジュールを読み込む際に利用するimportに慣れている人の場合はrequireを利用する方法は最初は違和感があるかもしれませんがNode.jsのコードで何度かrequireを利用するとすぐに違和感がなくなるので安心してください。

ブラウザ上で動作するJavaScriptでのモジュールの読み込みにはimportが利用されます。ブラウザ上ではCommonJSではなくECMAScript(ES Modules)のため読み込みの方法が異なります。読み込みだけではなくexportの方法もCommonJSとECMAScriptでは異なります。

モジュールの読み込みの動作確認を行うためにmain.jsと同じフォルダにmodule_test.jsファイルを作成します。module_test.jsファイルではmodule.exports の後にexportを行う関数を直接記述します。requireでモジュールを読み込みためには読み込みたいモジュール側でmodule.exportsを利用してexportの処理を行う必要があります。


module.exports = function (a, b) {
  return a + b;
};

module_test.jsファイルでexportした関数をmain.jsでrequireを利用して読み込み実行します。requireではファイル名を指定しますが同じフォルダに存在するファイルを指定しているので”./module_test.js”となります。拡張子の.jsは省略することができます。requireで読み込んだ関数をadd変数に保存しています。add変数の名前は任意です。


const add = require('./module_test.js');
console.log(add(100, 200));

nodeコマンドを利用してmain.jsファイルを実行します。module_test.jsファイルから読み込んだ関数をadd関数として実行し300が表示されます。複数のファイルを利用してコードを記述していく場合はrequireとmodule.exportsを利用しなければならないことを理解することができました。


 % node main.js
300

module_test.jsファイルでは関数にアロー関数を利用することもできます。


module.exports = (a, b) => a + b;

module_test.jsでは1つの関数のみexportを行っていましたが1つのファイル(モジュール)に複数の関数を記述してexportすることも可能です。module.exportsにオブジェクトを利用して複数の関数を設定しています。先ほどとは複数の関数をexportする際はexportする関数に名前をつける必要があります。


const add = (a, b) => a + b;
const sub = (a, b) => a - b;

module.exports = {
  add,
  sub,
};
//上記は下記の略
module.exports = {
  add: add,
  sub: sub,
};

module_test.jsファイルを読み込み側ではオブジェクトとして受け取るため実行する際は下記のように記述します。オブジェクトの中のプロパティとして関数が含まれているのでmodule_test.addで関数を実行することができます。module_testという変数の名前な任意の名前です。


const module_test = require('./module_test.js');
console.log(module_test.add(100, 200));
console.log(module_test.sub(100, 200));

オブジェクトの分割代入(destructuring statement)を利用して下記のように記述することもできます。


const { add, sub } = require('./module_test.js');
console.log(add(100, 200));
console.log(sub(100, 200));

module_test.jsファイルでのexportsの設定はオブジェクトではなく下記のようにも記述することができます。requireした場合はオブジェクトとして受け取るので読み込んだ側での利用方法はexportsをオブジェクトで設定した場合と同じです。


exports.add = (a, b) => a + b;
exports.sub = (a, b) => a - b;

Expressを利用する場合はrequireを利用して頻繁にモジュールを読み込むのでしっかりとrequireとexportの設定を理解しておきましょう。

コアモジュール

先ほどは自分で作成したモジュール(module_test.js)の読み込みを行いました。Node.jsではインストール直後から利用できるコアモジュール(path, os, http, fsなど)があります。コアモジュールの利用方法を確認することでさらにNode.jsの理解を深めていきます。

pathモジュールの利用例

pathモジュールを例に動作確認を行います。モジュールの読み込みはrequireで行い引数にpathを設定します。


const path = require('path');

pathモジュールが持つjoinメソッドを利用してパスの結合を行うことができます。join関数にはコンマで区切って複数の文字列を設定することでパスを作成することができます。実行するとファイルのパスが表示されます。join関数を利用する際に”/(スラッシュ)”は必要ありません。


const path = require('path');
let imageName = 'logo.svg';
let filepath = path.join(__dirname, 'public', imageName);
console.log(filepath);
///Users/mac/Desktop/javascript/public/logo.svg

__dirnameは環境変数で現在実行しているファイルの絶対パスを取得することができます。__dirnameとpathモジュールのjoinを利用したファイルのパス情報の取得はよく利用されるので覚えておいてください。

osモジュールの利用例

pathモジュール以外のコアモジュールの動作確認も行ってみましょう。

osモジュールを利用することでOSの情報を取得することができます。


const os = require('os');
console.log(os.arch());
console.log(os.version());
console.log(os.totalmem());
console.log(os.platform());
console.log(os.release());

実行するとコードを実行しているOSの情報を取得することができます。


 % node main.js
x64
Darwin Kernel Version 20.6.0: Wed Nov 10 22:23:07 PST 2021; root:xnu-7195.141.14~1/RELEASE_X86_64
8589934592
darwin
20.6.0

コアモジュールを含めNode.jsの情報はNode.jsの公式サイトから確認することができます。Node.jsのバージョンによって利用できるAPIに違いがあるのでバージョンを確認の上ドキュメントで確認してください。

Node.jsのドキュメントの確認
Node.jsのドキュメントの確認

async, awaitによる非同期処理

コアモジュールのfsを利用してNode.jsでasync, awaitの非同期処理を確認していきます。fsはFile System(ファイルシステム)の略でOS上のファイルシステムを操作するために利用することができます。ファイルの中身の読み出し/書き込みやファイル名の変更、フォルダの作成/削除をfsモジュールを通して行うことができます。

Node.jsのfsのドキュメントの説明を確認するとfsにはpromise-basedのAPIとsyncのAPIの2つが存在することがわかります。利用する際のrequireの方法が異なります。

モジュールFile Systemのドキュメント
モジュールFile Systemのドキュメント

2つのAPIの違いを確認しながらNode.jsでのasync, awaitの非同期処理の記述方法を確認していきます。

fsモジュールを利用してファイルの中身を読み出す処理を実行するためにmain.jsファイルと同じフォルダにtest.txtファイルを作成します。test.txtのファイルの中身を以下の通りです。


File Systemのモジュールで非同期処理を確認

main.jsからfsモジュールのreadFile関数を利用してファイルの中身を取得します。callback関数の引数のdataにファイルの中身が保存されるのでconsole.logを利用してコンソールに出力しています。


const fs = require('fs');

fs.readFile('./test.txt', 'utf-8', (err, data) => {
  if (err) throw err;
  console.log(data);
});

実行するとファイルの中身が表示されます。このようにfsモジュールのreadFileを利用してファイルの中身を読み出して表示することができます。


 % node main.js
File Systemのモジュールで非同期処理を確認

readFileメソッドでは第一引数にファイルのパスを指定、第二引数にencodingのためutf-8、第三引数にcallback関数を設定しています。第二引数を設定しない場合にmain.jsを実行すると下記が表示されるので’utf-8’の設定は必要です。


 % node main.js
<Buffer 46 69 6c 65 20 53 79 73 74 65 6d e3 81 ae e3 83 a2 e3 82 b8 e3 83 a5 e3 83 bc e3 83 ab e3 81 a7 e9 9d 9e e5 90 8c e6 9c 9f e5 87 a6 e7 90 86 e3 82 92 ... 6 more bytes>

fsモジュールを利用して非同期処理を確認していくためにconsole.logでstartとendを表示させます。


const fs = require('fs');

console.log('start');

fs.readFile('./test.txt', 'utf-8', (err, data) ='> {
  if (err) throw err;
  console.log(data);
});

console.log('end');

上記のコードを上から順番に読んでいくとstart, ファイルの中身、endの順番にコンソールに表示されるのではと思う人もいるかもしれませんが実行するとJavaScriptの非同期処理により以下の順番で表示されます。readFileという処理が実行され完了する前に’end’の表示処理が行われ、readFileの処理が完了してファイルの中身が表示されています。


% node main.js
start
end
File Systemのモジュールで非同期処理を確認

これを順番通りに表示させるためにはcallback関数の中のconsole.log(data)の後に’end’を設定する必要があります。


fs.readFile('./test.txt', 'utf-8', (err, data) => {
  if (err) throw err;
  console.log(data);
  console.log('end');
});

実行すると期待通りの動作になります。


 % node main.js
start
File Systemのモジュールで非同期処理を確認
end

これがfsのAPIを利用した場合の動作です。fs.readFileが完了した後に別の処理を行いたい場合はcallback関数の中に記述する必要があります。

PromiseベースのAPIではfs.readFileを実行するとPromiseが戻されるので下記のようにthenとcatchを利用して記述することができます。ファイルからの読み込みが成功した場合はthenの中身が実行されエラーが発生した場合にはcatchの中身が実行されます。


const fs = require('fs').promises;

console.log('start');

fs.readFile('./test.tx', 'utf-8')
  .then((data) => {
    console.log(data);
    console.log('end');
  })
  .catch((err) => console.log(err));

Promiseが利用されているのでaync, await関数を利用して書き換えることができます。async, await関数を利用することでfs.readFileの処理が完了するまで次の処理を行わないため下記のように記述することができます。fs.readFileの処理が完了した後に実行したい処理はawait fs.readFileの下に記述することができコードも読みやすくなることもわかるかと思います。


const fs = require('fs').promises;

console.log('start');

const fspromise = async () => {
  try {
    const data = await fs.readFile('./test.txt', 'utf-8');
    console.log(data);
    console.log('end');
  } catch (err) {
    console.log(err);
  }
};

fspromise();

このようにNode.jsではモジュールによってはPromiseベースのものが存在するのでドキュメントを見て確認する必要があります。Expressの処理ではasync, await関数を利用する機会が多いのでasync, await関数をどのように設定を行うかと設定することでどのような違いがあるのかはしっかり理解しておく必要があります。

npmの基礎

Expressを含めNode.js上でアプリケーションを構築していく場合にコアモジュールとカスタムモジュール(自作のモジュール)を利用しただけでは複雑なアプリケーションを効率的に構築することはできません。すべて一覧から自分で作成することもできますがパッケージとして公開されているコードを利用することができます。

パッケージのインストールと管理を行うためにnpm(Node Package Manager:ノードパッケージマネージャー)を利用することができます。npmを利用したパッケージのインストールとインストールしたパッケージを使って動作確認を行うことでnpmの理解を深めていきます。任意の場所にプロジェクトフォルダnpm-basicを作成します。このフォルダの中にパッケージをインストールしていきます。


% cd npm-basic 

作成したフォルダに移動してpackage.jsonファイルを作成するためにnpm init -yコマンドを実行します。package.jsonファイルはパッケージのインストール情報を保存するだけではなくプロジェクトに必要な情報を保存する重要なファイルです。npm init -yを実行するとpackage.jsonファイルの中身が表示されます。


% cd npm-basic 
% npm init -y
Wrote to /Users/mac/Desktop/npm-basic/package.json:

{
  "name": "npm-basic",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
npm initでは対話式にpackage.jsonファイルの中身を設定することができます。その方法については後ほどExpressのインストールの際に利用します。

npm-basicフォルダの中にはpackage.jsonファイルのみ作成されます。


 % ls
package.json

package.jsonのmainに設定されているindex.jsファイルを作成します。package.jsonファイルのindex.jsをmain.jsやserver.jsなど別のファイル名に変更することも可能です。

作成したindex.jsには下記を記述します。


console.log('Hello npm');

index.jsを実行するためはnodeコマンドを利用します。実行するとコンソールに”Hello npm”が表示されます。


 % node index.js
Hello npm

パッケージのインストール

npmを使ってパッケージをインストールすることができます。index.jsファイルを更新した場合、更新する度にnodeコマンドを実行する必要があります。nodeコマンドをファイルを更新する度に実行するのは効率が悪いのでファイルの更新を検知してnodeコマンドを再実行してくれるパッケージnodemon(Node Monitor)をインストールします。

パッケージのインストールにはnpm installコマンドを利用することができます。パッケージをインストールする際に本番時に利用するパッケージと開発時に利用するパッケージをわけてインストールを行うことができます。

nodemonは開発時のみに利用するパッケージなのでオプションに–save-dev(saveの前の-は2つ)をつけます。


 % npm install nodemon --save-dev

インストール完了後にpackage.jsonファイルを見るとdevDependenciesにパッケージ名のnodemonとインストールしたバージョンが表示されます。開発に利用するためにつけた–save-devオプションによりdevDependenciesに情報が保存されることを覚えておいてください。


{
//略
  "devDependencies": {
    "nodemon": "^2.0.15"
  }
}

インストール後にプロジェクトフォルダを確認すると新たにnode_modulesフォルダが作成されそのフォルダの中にインストールしたnodemonのフォルダを見つけることができます。パッケージをインストールするとすべてnode_modulesフォルダの中に保存されます。

nodemonの他に本番のコード中で利用する時刻を扱うためのライブラリdayjsのインストールを行います。nodemonは–save-devオプションをつけましたが今回は何もオプションをつけずインストールを行います。


 % npm install dayjs

インストール完了後にpackage.jsonを確認するとnodemonとは異なりdayjsはdependenciesに登録されていることがわかります。


{
 //略
  "devDependencies": {
    "nodemon": "^2.0.15"
  },
  "dependencies": {
    "dayjs": "^1.10.7"
  }
}

–save-devsオプションをつけるかどうかでpackge.jsonファイルに登録される場所がわかりますがパッケージの中身はどちらもnode_modulesの中に保存されます。

npmはパッケージをインストールするだけではなくパッケージを管理する機能を持っているのでpackage.jsonを別のフォルダに持っていきnpm installを実行するとpackage.jsonに記載されているパッケージをすべてインストールしてくれます。

もしdependenciesにインストールしたパッケージのみインストールしたい場合にはnpm install –productionを実行することでpackage.jsonのdependenciesのみをインストールすることができます。

パッケージの利用

インストールしたnodemonを利用してみましょう。nodemonを利用する場合はindex.jsファイルの前にnodemonをつけて実行します。しかし実行するとコマンドが見つからないというエラーが発生します。


 % nodemon index.js
zsh: command not found: nodemon

コマンドがインストールされているのに見つからない場合はOSにパスの設定が行われていないことがほとんどの場合原因です。Node.jsではnpxをつけることでインストールしたnode_modulesの中からコマンドを見つけてくれるのでエラーが解消します。


% npx nodemon index.js
[nodemon] 2.0.15
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node node index.js`
Hello npm
[nodemon] clean exit - waiting for changes before restart

nodemonで実行中にindex.jsファイルの中身を更新してみてください。Hello npmからHello nodemonに変更して保存します。


console.log('Hello nodemon');

するとnodemonがファイルの更新を検知してindex.jsファイルが再実行されるのでコンソールには”Hello nodemon”が表示されます。

npxをつけて実行することが確認できましたがpackage.jsonファイルのscriptに実行するコマンドを登録することができます。


{
//略
  "main": "index.js",
  "scripts": {
    "dev": "nodemon index.js"
  },
//略
}

登録後はnpm runの後にscriptsに登録したdevをつけることでnodemonを利用することができます。scriptに登録したコマンドが短いので利点は感じられないかもしれませんがオプションなどがある場合はコマンドが長くなるのでコマンドの打ち間違いもなくなります。またdevやproductionという名前でコマンドを登録することで開発環境のコマンドなのか本番環境のコマンドなのか使い分けも容易になります。


 % npm run dev

> npm-basic@1.0.0 dev
> nodemon node index.js

[nodemon] 2.0.15
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node node index.js`
Hello nodemon
[nodemon] clean exit - waiting for changes before restart

dayjsはnodemonとは異なりコードの中で利用することができます。インストールしたパッケージを利用したい場合はrequireを利用します。dayjsは時刻のフォーマットを設定することができるのでdayjsを使って以下のように設定します。


const dayjs = require('dayjs');

console.log(dayjs('2021-04-01').format('YYYY年M月D日'));

nodemonでファイルの監視を行っている場合はファイルの更新を検知してindex.jsファイルの再実行が行われフォーマットで指定した形式で時刻が表示されます。


[nodemon] clean exit - waiting for changes before restart
[nodemon] restarting due to changes...
[nodemon] starting `node node index.js`
2021年4月1日
[nodemon] clean exit - waiting for changes before restart

npmコマンドの基本的な使い方とインストールしたパッケージの利用方法の確認ができました。

Express

Node.jsとnpmの利用方法の説明が終わったのでExpressをインストールをする準備は整いました。ここからはExpressの説明を行っていきます。

Expressとは

ExpressはNode.js上で利用することができるWebアプリケーションフレームワークです。フレームワークは一般的に使用頻度の高い機能があらかじめ組み込まれて最適化されているため開発を効率的に行うことができます。

Expressの公式のページにアクセスするとインストール方法やドキュメントを確認することができます。

Expressの公式サイト
Expressの公式サイト

Expressのインストール

Node.jsと基礎とnpmの基礎で得た知識を利用しながらExpressのインストールと初期設定を行っていきます。

Expressをインストールするためにはプロジェクトフォルダを作成する必要があります。フォルダ名は任意の名前をつけることができ、ここではmyappという名前をつけています。


 $ mkdir myapp
 $ cd myapp/

Expressで使用するJavaScriptのパッケージ管理を行うためnpm initコマンドでpackage.jsonファイルの作成を行います。


 $ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help init` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.

npm init実行後、対話モードでいくつか質問をされますが、すべてEnterボタンを押してください。npm の基礎で説明した通りinit -yコマンドを実行するとpackage.jsonファイルが対話モードなしで作成されます。


Press ^C at any time to quit.
package name: (myapp) 
version: (1.0.0) 
description: 
entry point: (index.js) 
test command: 
git repository: 
keywords: 
author: 
license: (ISC) 

npm initを実行フォルダにはpackage.jsonファイルが作成されます。

npm init -yコマンドとnpm initの対話モードでEnterのみを押した場合に作成されるpackage.jsonファイルは同じ内容です。

package.jsonファイルを作成したら、公式のドキュメント通りにExpressのインストールを行います。npm installコマンドでexpressを指定します。


$ npm install express --save
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN myapp@1.0.0 No description
npm WARN myapp@1.0.0 No repository field.

+ express@4.17.1
added 50 packages from 37 contributors and audited 126 packages in 6.607s
found 0 vulnerabilities
npm 5.0+だと–saveをつけなくてもpackage.jsonのdependenciesにパッケージ情報が追加されるので、–saveは必須ではありません。

インストール完了後は、node_modulesフォルダとpackage-lock.jsonファイルが作成されます。ファイル構成は以下のようになります。


$ ls
node_modules		package-lock.json	package.json

package.jsonファイルを確認するとdependenciesにexpressを確認することができます。インストール時点での最新版がインストールされます。


mac@macnoMBP myapp % more package.json 
{
//略
  "dependencies": {
    "express": "^4.17.2"
  }
}

Hello Worldをブラウザに表示

Expressのインストールが完了したので、Expressを使ってブラウザにHello Worldを表示させます。たったの5行のコードでブラウザからアクセスすると”Hello World”を画面上に表示させることができます。

myappフォルダにindex.jsファイルを作成して以下のコードを記述します。


const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => res.send('Hello World!'))

app.listen(port, () => console.log(`Example app listening on port ${port}!`))

上記のコードは3つの部分に分割できるので各部分について説明を行なっていきます。

  1. Expressアプリケーションの作成
  2. ポートの設定
  3. ルーティングの設定

const express = require('express')
const app = express()

expressモジュールをrequireで読み込んでExpressアプリケーションを作成し変数appに保存しています。このappに保存したExpressのインスタンスがコアになる部分でappが持つ関数を利用してさまざまな設定を行なっていきます。


const port = 3000

app.listen(port, () => console.log(`Example app listening on port ${port}!`))

ブラウザからアクセスするためのポートを3000に設定し、そのポートをリスニングする設定を行なっています。ブラウザからアクセスする際はURLにhttp://localhost:3000を指定します。console.logの中身のメッセージはExpressサーバの起動時にコードを実行したターミナルに表示されます。メッセージの内容は自由に記述することができます。Expressにアクセスしてきたブラウザ上に表示されるものではありません。


app.get('/', (req, res) => res.send('Hello World!'))

上記ではルーティングの設定を行なっています。/(ルート)にアクセスが行われるとres(response)のsendメソッドでHello World!を返します。req(request)はRequest(リクエスト)を表し、アクセスしてきたブラウザ情報やリクエストデータを保持しています。res(response)はResponse(レスポンス)を表しており、アクセスしてきたブラウザに戻すデータを含んでいます。またgetはGETメソッドのことを表しています。/(ルート)にブラウザからアクセスがきたら”Hello World”をブラウザに戻すことを意味します。

index.jsファイルを設定したらExpressサーバを起動する必要があります。起動はindex.jsを実行することで行うことができます。起動が完了するとコンソールにはlistenメソッドのcallback関数で設定したconsole.logのメッセージが表示されます。


$ node index.js
Example app listening on port 3000!

index.jsファイルの中の設定でポート3000に設定しているのでブウラザからポート3000経由でExpressサーバにアクセスすることが可能になります。ブラウザからポート3000に対してアクセスを行うと画面にHello Worldが表示されます。

Hello Worldがブラウザに表示
Hello Worldがブラウザに表示

まだ/(ルート)しかルーティングの設定を行なっていないので、例えば/(ルート)以外の場所である/test等にアクセスを行うとブラウザ上にCannot GET /testとエラーメッセージが表示されます。エラーメッセージの/testの部分はアクセスした場所によって変わります。またExpressからはステータスコード404のNot Foundが戻されます。

ポートの3000以外(localhost:4000)を設定するとエラーが表示されアクセスすことはできません。もし異なるポートでアクセスしたい場合は、index.jsのポート番号を別の番号に変更する必要があります。3000を利用するアプリケーションがすでに稼働している場合もExpressサーバは起動することができません。

reqの中身の確認

/(ルート)のルーティングのcallback関数の引数に入っているreqがどのような情報をもっているかはconsole.logを利用して確認することができます。


app.get('/', (req, res) => {
  console.log(req);
  return res.send('Hello World!');
});

設定後ブラウザから/(ルート)にアクセスを行うとreqの中身を確認できます。たくさんの情報が入っていることがわかります。


<ref *2> IncomingMessage {
  _readableState: ReadableState {
    objectMode: false,
    highWaterMark: 16384,
    buffer: BufferList { head: null, tail: null, length: 0 },
    length: 0,
    pipes: [],
    flowing: null,
    ended: false,
    endEmitted: false,
    reading: false,
//略

reqが持つプロパティを確認したい場合はドキュメントを参考に確認することができます。

ドキュメント記載のRequestのプロパティ
ドキュメント記載のRequestのプロパティ

どのようなデータが取得できるのかプロパティの中からいくつかピックアップして確認してみましょう。


app.get('/', (req, res) => {
  console.log(req.baseUrl);
  console.log(req.hostname);
  console.log(req.ip);
  console.log(req.method);
  console.log(req.route);

  return res.send('Hello World!');
});

コンソールには下記の情報が表示されます。hostnameやmethodを確認することができます。このようにreq(request)にはさまざまな情報が含まれていることを覚えておいてください。


localhost
::1
GET
Route {
  path: '/',
  stack: [
    Layer {
      handle: [Function (anonymous)],
      name: '<anonymous>',
      params: undefined,
      path: undefined,
      keys: [],
      regexp: /^\/?$/i,
      method: 'get'
    }
  ],
  methods: { get: true }
}

nodemonインストール

index.jsのファイルを更新した場合、再度node index.jsを再実行しなければ変更したファイルの内容は反映されません。更新したファイルの変更を監視し自動でnode index.jsを再実行してくれるnodemonパッケージのインストールを行います。

npm installコマンドでnodemonのインストールを行います。


$ npm install --save-dev nodemon

インストール後は、下記のnpxコマンドとnodemonを利用してExpressサーバの起動を行います。


$ npx nodemon index.js
[nodemon] 2.0.8
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node index.js`
Example app listening on port 3000!

index.jsファイルの更新があるとnodemonがファイルの更新を検知してくれるためnode index.jsコマンドの再実行を自動で行ってくれます。ターミナルにも再スタートのメッセージが表示されます。


[nodemon] restarting due to changes...
[nodemon] starting `node app.js`
Example app listening on port 3000!

ルーティングとは

例えば/userという場所(URL)をExpressの設定に追加した場合、ブラウザからGETやPOSTメソッドを使って/userにアクセスがあった場合にどのような処理やレスポンスを返すのかを決めるのがルーティングです。/userという一つの場所(URL)であってもブラウザから送信されてくるメソッド毎に独立した処理を設定することができます。

ブラウザからページの閲覧だけを行いたい場合はGETメソッドを使ってページのデータを取得します。POSTメソッドは入力フォームなどでユーザが入力したデータをサーバに渡す際に利用します。その他に削除を行いたい場合のDELETEメソッド、更新を行いたい場合のPUT, PATCHメソッドなどがあります。

/userにGETメソッド、POSTメソッドの異なる方法でリクエストがあった場合はExpress側で下記のようにappのgetメソッドとpostメソッドを使い、ブラウザに返す値やサーバで実行する処理を変えることができます。

/userにGETメソッドでリクエストがあった場合はappのgetメソッドにURLと処理を記述します。


app.get('/user', function (req, res) {
  res.send('Hello World!')
})

/userにPOSTメソッドのリクエストがあった場合はappのpostメソッドにURLと処理を記述します。


app.post('/user', function (req, res) {
  res.send('Got a POST request')
})

ルーティングを追加

新たに”/”にPOSTメソッドがあった場合のルーティングを追加します。先ほど説明した通り、POSTメソッドの場合はapp.postメソッドを利用します。


app.get('/', (req, res) => res.send('Hello World!'))
app.post('/', (req, res) => res.send('Hello World! by Post Request'))

同じ/(ルート)にGETとPOSTの設定を行なっていますがブラウザから通常のアクセスを行うとGETメソッドでのアクセスになるため”Hello World!”が表示されます。

通常のWEBサイトでは入力フォームから入力した値をサーバに対して送信する際にPOSTメソッドを送信します。Expressでの入力フォームの作成方法は後ほど確認するので代わりにPOSTメソッドを実行するためにcurlコマンドを利用します。-XオプションにPOSTを指定することでPOSTメソッドでのcurlコマンドを利用してExpressサーバにアクセスを行うことができます。


$ curl -X POST http://localhost:3000
Hello World! by Post Request

上記のcurlコマンドから-X POSTを削除するとGETメソッドでアクセスすることができます。


$ curl http://localhost:3000
Hello World!

同じURLにアクセスしているのにも関わらずGETメソッド、POSTメソッドと送信するリクエストを変更することでサーバから異なる返答があることがわかります。同じURLでもルーティングの設定を適切に行うことでGETメソッドとPOSTメソッドで異なる処理をサーバ側で行うことができます。

app.get, app.postを別の行として登録を行っていましたがapp.routeを利用することで一つにまとめることができます。下記のように記述しても動作は変わりません。


app
  .route('/')
  .get((req, res) => {
    return res.send('Hello World!');
  })
  .post((req, res) => res.send('Hello World! by Post Request'));

curlコマンドを利用してPOSTメソッドを利用してJSONデータを一緒に送りたい場合は下記のように行うことができます。JSONデータをPOSTメソッドで送信したい場合に利用してください。


% curl -d '{"id":"aaa"}' -X POST http://localhost:3000/test --header 'content-type: application/json'

index.jsファイルにルーティングを追加することでブラウザからアクセスできる場所を増やすことが可能です。


app.get('/user', (req, res) => res.send('Hello John Doe'))

上記のコードにindex.jsファイルに追加することでhttp://localhost:3000/userにアクセスすれば、”Hello John Doe”が表示されます。

ファイル更新後は忘れずにnode app.jsコマンドを再実行する必要があります。

リダイレクトの設定

あるルーティングのURLに来たアクセスを別のルーティングのURLにリダイレクトすることもできます。resのredirect関数を使います。redirectの引数にはリダイレクト先のURLを設定します。


app.get('/redirect', (req, res) => {
  res.redirect('/');
});

ブラウザからlocalhost:3000/redirectにアクセスすると”/”にリダイレクトされます。デフォルトではステータスコードの302が戻されるので戻すステータスコードを301を設定したい場合は以下のように設定を行うことができます。


app.get('/redirect', (req, res) => {
  res.redirect(301,'/');
});

存在しないルーティング

存在しないルーティングがあった場合にブラウザでアクセスすると”Cannot GET /XXX”と表示されます。XXXはアクセスしたURLが表示されます。もしExpressに設定したルーティング以外にアクセスがあった場合は下記のように”*”(アスタリスク)を設定することで表示させる内容を設定することができます。


app.get('/', (req, res) => res.send('Hello World!'));
app.post('/', (req, res) => res.send('Hello World! by Post Request'));

app.get('/redirect', (req, res) => {
  res.redirect('/');
});

app.get('*', (req, res) => res.send('ページは存在しません。'));

追加したルーティングの一番最後に設定を行ってください。Expressでは上から順番にURLのチェックが行われるため先頭に”*”のルーティングを追加するとどのURLにアクセスしても”ページが存在しません。”が表示されます。

ページが存在しませんと表示はされますがExpressからステータスコードの200が戻されリクエストが正常に処理されたことになります。実際に存在しないURLなので404のステータスコードを戻したい場合はresにstatusを設定することで戻すResponseのステータスコードを設定することができます。


app.get('*', (req, res) => res.status(404).send('ページは存在しません。'));

HTMLを戻す

ルーティングの追加設定によりブラウザから/userにアクセスを行うと文字列を返すことができました。

HTMLタグで文字列を囲むと戻り値を受け取ったブラウザはHTMLタグを解釈してくれるためh1タグをつけると文字は大きく表示されます。


app.get('/user', (req, res) => res.send('<h1>Hello John Doe!</h1>'))
h1タグで文字が大きくなった
h1タグで文字が大きくなった

sendメソッド内にhtmlタグを付与することでブラウザ上で付与したタグが認識できることが確認できましたが、sendメソッドの中にhtml文をすべて記述するのは非効率なので新たにhtmlファイルを作成し、htmlファイルの内容を表示させる方法を確認していきます。

静的ファイルの保存場所の設定

静的ファイルを扱うためには、ミドルウェアのexpress.staticを下記のように設定する必要があります。ミドルウェアについては後ほど説明を行います。publicは任意の名前をつけることができます。staticの中には静的ファイルを保存するパスを指定します。


app.use(express.static(path.join(__dirname, 'public')))

pathモジュールについてはNode.jsのコアモジュールの利用例で説明済みですがconst path = require(‘path’)でpathモジュールを読み込む必要があります。


const path = require('path')

__dirnameによりindex.js”ファイルが存在するフォルダのパスがわかります。path.joinで結合することでindex.js”ファイルが保存されているフォルダ下のpublicフォルダを設定しています。

myappフォルダの中にpublicフォルダを作成してtest.htmlファイルを作成します。


<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Express.js</title>
</head>
<body>
    <h1>Hello World</h1>
    <p>Express.jsを使ってtest.htmlファイルを表示しています。</p>
</body>
</html>

ブラウザからhttp://localhost:3000/test.htmlファイルに直接アクセスすると下記の画面が表示されます。express.staticを利用することでpublic以下に保存した静的ファイルにブラウザから直接アクセスできることがわかりました。

test.htmlファイルでHello World
test.htmlファイルでHello World

画像の設定方法

publicフォルダにimgフォルダを作成し画像を保存します。このファイルにtest.htmlからアクセスできるか確認します。srcのパスはimg/express.pngとしています。


<body>
    <h1>Hello World</h1>
    <p>Express.jsを使ってtest.htmlファイルを表示しています。</p>
    <img src="img/express.png">
</body>

express.staticでpublicを設定しているので、その下のフォルダへはpublic以下のパスを記述するだけでアクセスできることがわかります。

test.htmlから画像を読み込む
test.htmlから画像を読み込む

ルーティングを利用した表示

ブラウザから直接publicフォルダに保存しているhtmlファイルにアクセスして表示させることができることはわかりました。次はルーティングを利用してhtmlファイルをブラウザに表示させる方法を確認します。これまで文字列を戻すのにsendメソッドを利用していましたがファイルを戻す場合はsendFileメソッドを利用します。この場合は、ファイルのパスはpublicからの相対パスではなく絶対パスを設定します。


app.get('/test', (req, res) => res.sendFile(path.join(__dirname, 'public/test.html')))

path.joinの設定は下記の方法でも可能です。


app.get('/test', (req, res) =>
  res.sendFile(path.join(__dirname, 'public', 'test.html'))
);

http://localhost:3000/testにアクセスすると先程と同じ内容が表示されます。

/testにアクセス
/testにアクセス

先ほどはURLがtest.htmlになっていましたが、追加したルーティングに対してアクセスをおこなっているので今回はURLが/testになっています。

設定したミドルウェアのexpress.staticをコメントアウトするとsendFileメソッドでhtmlファイルのtest.htmlを指定しているためtest.htmlの中身は表示されますが、画像へのパスが未設定となり下記のように画像が表示されません。sendFileメソッドでHTMLファイルを指定する場合は画像のパス設定とミドルウェアの設定に注意が必要です。

画像へのパスが見つからない
画像へのパスが見つからない

ミドルウェアとは

静的ファイルを扱うためにミドルウェアのexpress.staticを設定を行いました。Expressを使いこなすためにミドルウェアを理解しておく必須です。ミドルウェアはクライアント(ブラウザ、API)からリクエストが送信されてきてからレスポンスを戻す前に実行される処理です。

Express本体に事前に備わっているビルトインミドルウェアがいくつかあります。ビルトインミドルウェアの一つがexpress.staticです。express.staticを設定した時のようにapp.useを利用してミドルウェアを設定します。

これから最もシンプルで簡単なミドルウェアを設定してミドルウェアの動作確認を行います。この例を通してミドルウェアは”リクエストが送信されてきてからレスポンスを戻す前に実行される処理”という意味を確認することができます。


app.use((req, res, next) => {
  console.log('1つ目のミドルウェアを通った?');
  next();
});

app.get('/', (req, res) => res.send('Hello World!'));

app.useの引数にcallback関数を利用してミドルウェアの処理を記述することができます。callback関数はreq, res, nextの3つの引数をとります。ミドルウェアは1つではなく複数設定を行うためことができるため現在のミドルウェアの処理が完了したら次のミドルウェアに処理を渡すためにnextを利用します。nextがないと次の処理に進まないのでルーティングの処理を完了させることができません。引数の中にreq, resがあるためミドルウェアの処理の中でreq,resにアクセスすることができます。

上記のコードを設定後ブラウザで/(ルート)にアクセスするとindex.jsを実行しているコンソールに”1つ目のミドルウェアを通った?”が表示されます。console.logでメッセージを表示するというだけのものですがリクエストとレスポンスの処理の間で実行されています。では次にapp.get(‘/’,…)のルーティングの下に2つ目のミドルウェアを設定してみましょう。


app.use((req, res, next) => {
  console.log('1つ目のミドルウェアを通った?');
  next();
});

app.get('/', (req, res) => res.send('Hello World!'));

app.use((req, res, next) => {
  console.log('2つ目のミドルウェアを通った?');
  next();
});

この場合はapp.get(‘/’,…)の処理が実行されブラウザにレスポンスが戻ってしまっているので追加したミドルウェアが実行されることはありません。今度はapp.get(‘/’,…)の前に2つ目のミドルウェアを設定してみてください。

コンソールには”1つ目のミドルウェアを通った?”と”2つ目のミドルウェアを通った?”が表示されます。

ここまでの動作確認でミドルウェアがリクエストが送信されてきてからレスポンスを戻す前に実行される処理”という意味を理解することができたのではないでしょうか。

カスタムミドルウェア

自作のミドルウェアを作成することでミドルウェアの理解を深めていきましょう。

index.jsファイルの中にミドルウェアのコードを記述することもできますが自作のミドルウェアをモジュールとして読み込むためにプロジェクトフォルダにmiddlewareフォルダを作成してその中にlogger.jsファイルを作成します。

logger.jsファイルの中ではreqのpathプロパティとmethodプロパティを使ってブラウザからのアクセスがある度にコンソールにメッセージを表示させます。


module.exports = (req, res, next) => {
  console.log(`URL:${req.path} METHOD:${req.method}`);
  next();
};

index.jsではrequireでlogger.jsファイルを読み込みapp.useでミドルウェアとしてloggerを設定します。


//略
const logger = require('./middleware/logger');
app.use(logger);
//略

これでloggerのミドルウェアの設定は完了です。ブラウザからルーティングが登録されている/(ルート)と/user、ルーティングが登録されていない/aaaにアクセスを行います。nodemonを実行したコンソールにURLとMETHODの情報が表示されることがわかります。


Example app listening on port 3000!
URL:/ METHOD:GET
URL:/user METHOD:GET
URL:/aaaa METHOD:GET

Node.jsの基礎で学習したfsモジュールを利用してコンソールではなくファイルへの書き込みを行ってみましょう。プロジェクトフォルダの直下にlogsフォルダを作成してその下にexpress.logファイルを作成します。デフォルトの中身は空です。そのファイルにfsモジュールのappendFileを利用してメッセージを書き込んでいきます。appendFileの書式がわからない場合はNode.jsのドキュメントで確認することができます。fsにはpromisedベースのものもあるので書式を間違わないように利用しましょう。下記ではSYNC APIを利用しています。


const fs = require('fs');

module.exports = (req, res, next) => {
  const message = `URL:${req.path} METHOD:${req.method}\n`;
  const file_path = 'logs/express.log';
  fs.appendFile(file_path, message, (err) => {
    if (err) console.log(err);
  });
  next();
};

改行を行うためにmessageの最後に\nを追加しています。

設定後/(ルート), /userにアクセスするとlogsフォルダのexpress.logファイルにメッセージが書き込まれます。


URL:/ METHOD:GET
URL:/user METHOD:GET

Promiseベースを利用した場合も記述しておきます。fs.appendFileの3番目の引数にcallback関数が必要かどうかの違いがあります。


const fs = require('fs').promises;

module.exports = async (req, res, next) => {
  const message = `URL:${req.path} METHOD:${req.method}\n`;
  const file_path = 'logs/express.log';
  try {
    await fs.appendFile(file_path, message);
  } catch (err) {
    console.log(err);
  }
  next();
};

Routesの設定

アプリケーションが大きくなりルーティングが増えてくるとindex.jsファイルの中だけでルーティングの管理をすることが難しくなってきます。Expressではルーティングをモジュール化することでルーティングの管理を容易することができます。

プロジェクトフォルダの直下にroutersフォルダを作成してuser.jsファイルを作成します。作成したuser.jsファイルには以下を記述します。Express.Router()でrouterインスタンスを作成しindex.jsファイルのrequireで読み込めるようにexportします。ルーティングの記述方法についてはrouterインスタンスのgetメソッドを設定していますが設定方法はindex.jsと変わりません。


const express = require('express');
const router = express.Router();

router.get('/', (req, res) => res.send('<h1>Hello John Doe!</h1>'));

module.exports = router;

exportしたrouterをindex.jsのrequireで読み込み下記のように設定を行います。


const userRouter = require('./routes/user');
app.use('/user', userRouter);

ブラウザから/userにアクセスするとuser.jsファイルに記述した/(ルート)のcallback関数の中の処理が実行されます。

/userルーティング以外にも別のルーティング例えば/postルーティングを増やしたい場合はroutesフォルダにpost.jsファイルを作成しuserファイルを参考にルーティングを設定します。


const express = require('express');
const router = express.Router();

router.get('/', (req, res) => res.send('<h1>POST一覧</h1>'));

module.exports = router;

作成したrouterを下記のようにindex.jsで読み込み設定します。


const userRouter = require('./routes/user');
const postRouter = require('./routes/post');
app.use('/user', userRouter);
app.use('/post', postRouter);

ルーティング/postだけではなく/post/1, /post/2のように動的に変わるルーティングは以下のように:idと設定することができます。idは任意の名前です。idはreq.params.idで取得することができます。もしidを変更した場合はreq.paramsのidも変更した名前に変更する必要があります。


router.get('/', (req, res) => res.send('<h1>POST一覧</h1>'));
router.get('/:id', (req, res) => res.send(req.params.id));

ブラウザからlocalhost:3000/post/3にアクセスすると画面には3が表示されます。/post/100にアクセスした場合は100と表示されます。

API routesの設定

Vue.jsやReact, Svelteなどのフロントエンドのフレームワークのバックエンドサーバとして構築したい場合にはjsonデータを戻すAPIのルーティングを設定することができます。

routesフォルダにapiフォルダを作成してmovie.jsファイルを作成します。

通常はmovieの情報はデータベースなどから取得しますがここでは配列で作成しています。JSONデータとして戻すためres.send()からres.json()に変更します。res.jsonの引数には配列moviesを設定します。


const express = require('express');
const router = express.Router();

const movies = [
  { id: 1, title: 'キングスマン' },
  { id: 2, title: 'スパイダーマン' },
];

router.get('/', (req, res) => res.json(movies));

module.exports = router;

index.jsではルーティングは/movieではなく/api/movieとして登録します。


const userRouter = require('./routes/user');
const postRouter = require('./routes/post');
const movieRouter = require('./routes/api/movie');
app.use('/user', userRouter);
app.use('/post', postRouter);
app.use('/api/movie', movieRouter);

ブラウザから/api/moviesにアクセスするとJSONとして取得できていることが確認できます。

JSONデータとして受け取る
JSONデータとして受け取る

res.sendで戻した場合はContent-Typeはtest/htmlになりますがres.jsonの場合はContent-Typeがapplication/jsonになります。

getリクエストの場合のJSONデータの戻し方は分かりましたがJSONでPOSTリクエストがあった場合に送信されてくるデータの取得方法について確認を行います。

movie.jsではPOSTリクエストから送られてきたデータはreq.bodyから取得します。取得したデータを配列moviesに追加してmoviesをJSONデータでクライアントに戻します。


const express = require('express');
const router = express.Router();

const movies = [
  { id: 1, title: 'キングスマン' },
  { id: 2, title: 'スパイダーマン' },
];

router.get('/', (req, res) => res.json(movies));

router.post('/', (req, res) => {
  movies.push({
    id: req.body.id,
    title: req.body.title,
  });
  res.json(movies);
});

module.exports = router;

上記の設定だけでは送信されてきたデータを取り出すことはできません。送られてきたJSONデータからデータを取得するためにはミドルウェアのexpress.json()を設定する必要があります。movie.jsにPOSTメソッドのルーティングを追加した後はindex.jsにexpress.json()を設定します。


app.use(express.json());

クライアントからid, titileを設定したJSONデータを送信すると送信したデータが追加されたmoviesが戻されます。

フロントエンドからのアクセス

ExpressにAPIのルーティングを設定することができたのでExpressをバックエンドサーバとして利用することができます。フロンドエンドのフレームワークからのアクセスでExpressからデータが取得できるか確認を行います。フロントエンドにここではVueを利用します。Vueの開発サーバはポート番号3000で起動するのでExpress側のポート番号を3000から4000に変更します。


const port = 4000;

Vueのプロジェクトを作成します。npm init vue@latestコマンドを実行します。


 % npm init vue@latest

プロジェクト名を聞かれるので任意の名前を入力してください。プロジェクト名設定後プロジェクトで利用する機能を選択することができますがデフォルトの”NO”を選択するためそのままEnterボタンを押してください。


 % npm init vue@latest
Need to install the following packages:
  create-vue@latest
Ok to proceed? (y) y

Vue.js - The Progressive JavaScript Framework

✔ Project name: … front_vue
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit Testing? … No / Yes
✔ Add Cypress for both Unit and End-to-End testing? … No / Yes
✔ Add ESLint for code quality? … No / Yes

Scaffolding project in /Users/mac/Desktop/front_vue...
Done. Now run:

  cd front_vue
  npm install
  npm run dev

プロジェクトの作成が完了したら、入力したプロジェクトフォルダに移動してnpm installコマンドを実行しします。npm installが完了したらnpm run devコマンドを実行します。


 % cd front_vue
 % npm install
 % npm run dev

プロジェクトフォルダの直下にあるsrcフォルダのApp.vueファイルを以下のように更新します。fetch関数を利用してExpressの/api/movieにアクセスを行いmovie情報を取得してv-forで展開してブラウザ上に表示しているだけです。


<script setup>
import { ref } from 'vue';
const movies = ref([]);
fetch('http://localhost:4000/api/movie')
  .then((response) => response.json())
  .then((json) => (movies.value = json));
</script>

<template>
  <h1>Express BackEnd</h1>
  <ul>
    <li v-for="movie in movies" :key="movie.id">
      {{ movie.title }}
    </li>
  </ul>
</template>

Expressはnodemonで起動を行っている状態でブラウザでlocalhost:3000にアクセスします。

コンソールを見るとCorsのエラーメッセージが表示されます。

”localhost/:1 Access to fetch at ‘http://localhost:4000/api/movie’ from origin ‘http://localhost:3000’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.”

Corsの設定

フロントエンドのVueからExpressにアクセスすることができないのでExpress側でCorsのインストールと設定を行う必要があります。


 % npm install cors

corsのインストール後index.jsファイルでミドルウェアでCorsの設定を行います。


const express = require('express');
const app = express();
const port = 4000;
const cors = require('cors');

app.use(express.json());
app.use(cors());
//略

ブラウザをリロードするとCorsの問題は解消してExpressから取得したデータがブラウザ上に表示されます。

Expressサーバから取得したデータを表示
Expressサーバから取得したデータを表示

Expressをバックエンドサーバとして利用できることも確認できました。

Controllersの作成

routes¥apiのmovie.jsファイルに記述していた処理のコードをControllerファイルとして別のファイルに記述します。プロジェクトフォルダの直下にcontrollersフォルダを作成しmovieController.jsファイルを作成します。

movie.jsファイル内での処理をControllerに移動する前に確認しておきます。Controllerに移動する処理はget, postのcallback関数の部分です。


const express = require('express');
const router = express.Router();

const movies = [
  { id: 1, title: 'キングスマン' },
  { id: 2, title: 'スパイダーマン' },
];

router.get('/', (req, res) => res.json(movies));

router.post('/', (req, res) => {
  movies.push({
    id: req.body.id,
    title: req.body.title,
  });
  console.log(req.body);
  res.json(movies);
});

module.exports = router;

movieController.jsファイルでgetMovies、createMovie関数を追加してget, postのcallback関数を設定します。


const getMovies = (req, res) => res.json(movies);

const createMovie = (req, res) => {
  movies.push({
    id: req.body.id,
    title: req.body.title,
  });
  res.json(movies);
};

関数の追加が完了したらexportの処理とmoviesの配列をmovieController.jsに追加します。


const movies = [
  { id: 1, title: 'キングスマン' },
  { id: 2, title: 'スパイダーマン' },
];

const getMovies = (req, res) => res.json(movies);

const createMovie = (req, res) => {
  movies.push({
    id: req.body.id,
    title: req.body.title,
  });
  res.json(movies);
};

module.exports = {
  getMovies,
  createMovie,
};

Controllersで作成した関数はmovie.jsファイルでrequireを利用して読み込みます。


const express = require('express');
const router = express.Router();

const { getMovies, createMovie } = require('../../controllers/movieController');

router.get('/', getMovies);
router.post('/', createMovie);

routesファイルの中身は処理を分離することですっきりしました。処理をControllerとしてわけましたが動作に違いはありません。

入力フォームからデータ取得したい

HTMLファイルの表示方法を確認できたのでHTMLに入力フォームを追加しブラウザに渡した入力フォームに入力した値をExpress側で取得する方法を確認します。

入力フォームからデータを取得する場合はexpress.json()かbody-parserを利用して行うことができます。

express.json()の利用

以前は入力フォームから送られてくるデータを取得する際にはbody-parserをインストールする必要がありましたがExpressのバージョンが4の場合はbody-parserではなくexpress.json()+exporess.urlencode()を利用することができます。その場合は下記を設定してください。


app.use(express.json())
app.use(express.urlencoded({ extended: true }));

body-parserの利用

express.jsonではなくbody-parserを使った場合の入力フォームから送られてくるデータの取得方法を確認します。

body-parserをnpmコマンドでインストールします。


 $ npm install body-parser

インストール完了後にindex.jsファイルでbody-parserをrequireします。


const bodyParser = require('body-parser');

送られてくるデータのContent-Typeがapplication/x-www-form-urlencodedの場合body-parserのurlencoded()メソッドを利用します。これらの設定により、送られてくるデータはreq.bodyに保存されます。

POSTリクエストを送る場合には2つの方法があり、x-www-form-urlencodedはサーバに対してテキストデータを送る際に使用されます。ファイルを送る場合には、multipart/form-dataが使用されます。後ほど確認するフォームではx-www-form-urlencodedで送信されてきます。

app.use(bodyParser.urlencoded({ extended: true }));
送られてくるContent-Typeを確認したい場合は、res.send(request.header(‘Content-Type’))で確認することができます。

入力フォームの作成

入力フォームは/testにブラウザがアクセスがあった場合に返すtest.htmlファイルの中に記述します。


app.get('/test', (req, res) => res.sendFile(path.join(__dirname, 'public/test.html')))

test.htmlに入力フォームを追加し、送信ボタンを押すと/testに入力したデータをPOSTします。


<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Express.js</title>
</head>
<body>
    <h1>入力フォーム</h1>
    <form action="/test" method="POST">
        <input type="text" name="name">
        <button type="submit">送信</button>
    </form>
</body>
</html>

入力データの取得

作成したフォームにユーザ名を入力して、送信ボタンを押します。サーバ側のPOSTメソッドに対するルーティングが設定されていないので、Cannot POST /testエラーが表示されます。

入力フォームを作成
入力フォームを作成

サーバ側でルーティングの追加を行います。入力フォームから送信されてくるデータはPOSTリクエストで送られてくるのでapp.postメソッドを利用します。

送信したデータはbody-parserまたはexpress.urlencoded()により、req.bodyの中に保存されるので、req.body.nameで入力した値を取り出します。受け取ったデータの内容を確認するためにsendメソッドでブラウザに戻します。


app.post('/test', (req, res) => res.send('送信したユーザ名は' + req.body.name))

入力フォームに入れて送信ボタンを押すとExpress側でPOSTデータを受け取りbody-parserまたはexpress.urlencoded()で受け取ったデータを扱える形に変換して、その結果がブラウザに表示されます。

入力した内容が戻される
入力した内容が戻される

express.urlencoded()またはbody-parserを利用することでPOSTで送られてきたデータをExpress.jsで取得できることが確認できました。送信されてくるデータがJSONではない場合はapp.use(express.json())は必要ありません。

まとめ

Expressの設定方法だけではなくExpressを理解する上で必要になるNode.jsやnpmの使い方の基礎まで説明することができました。本文書では Expressを利用した実践的なアプリケーションを作成していないので次は実際のアプリケーショの構築にチャレンジしてください。