📖

Expressで学ぶべきこと

2023/03/26に公開

Expressで学ぶべきこと

目次

  • ルーティング
  • ミドルウェア
  • テンプレートエンジン
  • 静的ファイルの配信
  • データベース
  • セッション管理
  • CSRF対策
  • WebSocket

なぜExpressを使うのか

シンプルで柔軟

Expressは、シンプルで柔軟なWebフレームワークです。必要な機能のみを選択して使用することができるため、軽量で高速なWebアプリケーションを作成することができます。

ルーティング

Expressは、ルーティング機能を提供しています。これにより、HTTPリクエストを処理するために、URLに基づいて適切な処理を実行することができます。

ミドルウェア

Expressは、ミドルウェアを使用することで、HTTPリクエストとレスポンスの処理を拡張することができます。これにより、エラーハンドリング、認証、セッション管理などの機能を簡単に実装することができます。

テンプレートエンジン

Expressは、テンプレートエンジンを使用することで、動的なWebページを簡単に作成することができます。テンプレートエンジンを使用することで、HTMLコードを静的なファイルに保存することなく、動的に生成することができます。

静的ファイルの配信

Expressは、静的なファイル(CSS、JavaScript、画像など)を配信するための機能を提供しています。これにより、Webアプリケーションのパフォーマンスを向上させることができます。

データベース

Expressは、MongoDBなどのデータベースを使用するためのパッケージを提供しています。これにより、データベースとの連携が容易になります。

WebSocket

Expressは、WebSocketを使用するためのパッケージを提供しています。WebSocketを使用することで、リアルタイムWebアプリケーションを作成することができます。

以上のように、Expressは、シンプルで柔軟なWebフレームワークであり、多くの便利な機能を提供しているため、Webアプリケーション開発に適しています。

ルーティング

Expressのルーティング機能を使用すると、HTTPリクエストを受信し、それに応じた処理を実行することができます。ルーティングは、Expressアプリケーションに対して、URLとそのURLに対応する処理を関連付けます。

ルーティングは、HTTPリクエストメソッドとURLパスを指定して定義されます。Expressでは、主要なHTTPリクエストメソッド(GET、POST、PUT、DELETEなど)に対応した関数(ルートハンドラ)を定義することができます。ルートハンドラは、リクエストを処理し、レスポンスを返します。

以下は、ルーティングの例です。

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

// GETメソッドに対応するルートハンドラを定義する
app.get('/', function (req, res) {
  res.send('Hello World!');
});

// POSTメソッドに対応するルートハンドラを定義する
app.post('/users', function (req, res) {
  res.send('Creating a new user...');
});

// PUTメソッドに対応するルートハンドラを定義する
app.put('/users/:id', function (req, res) {
  res.send(`Updating user with ID ${req.params.id}`);
});

// DELETEメソッドに対応するルートハンドラを定義する
app.delete('/users/:id', function (req, res) {
  res.send(`Deleting user with ID ${req.params.id}`);
});

// すべてのHTTPリクエストに対してのルートハンドラを定義する
app.all('*', function (req, res) {
  res.send('404 Not Found');
});

// サーバーを起動する
app.listen(3000, function () {
  console.log('Example app listening on port 3000!');
});

上記の例では、4つのHTTPリクエストメソッドに対応したルートハンドラを定義しています。例えば、GETメソッドに対応するルートハンドラは、ルートパス('/')に対応するもので、リクエストがあった場合には「Hello World!」というレスポンスを返します。また、PUTメソッドに対応するルートハンドラは、URLパスに含まれる:idパラメータを使用して、ユーザー情報の更新を実行します。

また、上記の例では、すべてのHTTPリクエストに対してのルートハンドラも定義しています。このルートハンドラは、上記の定義よりも下に記述する必要があります。これは、他の定義にマッチしないリ
クエストがあった場合に実行されるデフォルトの処理です。上記の例では、存在しないURLに対してリクエストがあった場合には、「404 Not Found」というレスポンスを返します。

ルーティング機能を使用することで、複雑なアプリケーションでも効率的にHTTPリクエストを処理することができます。また、ルートハンドラを分割して定義することで、コードの再利用性やメンテナンス性を高めることができます。

ミドルウェア

Expressでのミドルウェアは、HTTPリクエストの前処理、後処理、ログ出力、認証、エラーハンドリングなどの機能を提供する関数です。Expressのアプリケーションは、リクエストが到着したときに、ミドルウェアのチェーンを実行していきます。ミドルウェア関数は、リクエストとレスポンスのオブジェクトを受け取り、通常、次のミドルウェア関数を呼び出すために next 関数を呼び出します。

ミドルウェア関数は、 app.use() または特定の HTTP メソッドに対応するメソッド( app.get() 、 app.post() など)によって、Expressアプリケーションに登録されます。以下は、ミドルウェアの例です。

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

// ログ出力用のミドルウェア関数
const logger = (req, res, next) => {
  console.log(`${req.method} ${req.path}`);
  next();
};

// リクエストの処理
app.get('/', logger, (req, res) => {
  res.send('Hello World!');
});

// エラーハンドリング用のミドルウェア関数
const errorHandler = (err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('Something broke!');
};

// エラーハンドリングの例外を投げるルート
app.get('/error', (req, res, next) => {
  throw new Error('oops');
});

// エラーハンドリングのミドルウェア関数を登録する
app.use(errorHandler);

app.listen(3000, () => {
  console.log('Example app listening on port 3000!');
});

上記の例では、 logger という名前のミドルウェア関数を定義しています。この関数は、リクエストが到着するたびにログを出力します。次に、 / ルートで、この logger 関数を使用してリクエストを処理するルートハンドラを定義しています。

また、 errorHandler という名前のミドルウェア関数を定義しています。この関数は、エラーが発生した場合にログを出力し、クライアントに 500 Internal Server Error のレスポンスを返します。そして、 /error ルートでは、意図的に例外を投げています。この例外は、errorHandler 関数に渡されます。

最後に、 app.use() メソッドを使用して、 errorHandler 関数をExpressアプリケーションに登
録しています。これにより、アプリケーション内で発生するすべてのエラーが、この関数に渡され、適切に処理されます。

ミドルウェア関数は、必要に応じて複数の引数を受け取ることができます。例えば、エラーハンドリング用のミドルウェア関数は、エラーオブジェクトを受け取る必要があります。また、ミドルウェア関数は、実行後に次のミドルウェア関数を呼び出すために next() 関数を呼び出す必要があります。これにより、次のミドルウェア関数が実行されます。

ミドルウェア関数は、Expressアプリケーションの機能を拡張するために非常に重要です。ログ出力、認証、セッション管理、キャッシュ制御、エラーハンドリングなどの機能を提供することができます。また、ミドルウェア関数は、Expressアプリケーションの構成を柔軟に行うことができるため、アプリケーションの保守性を向上させることができます。

テンプレートエンジン

Expressでは、多数のテンプレートエンジンをサポートしています。テンプレートエンジンを使用することで、サーバーサイドで動的なHTMLを生成することができます。例えば、データベースから取得したデータをHTMLに埋め込む場合などに使用されます。

テンプレートエンジンを使用するには、まずテンプレートエンジンのインストールが必要です。多くのテンプレートエンジンはnpmパッケージで提供されているため、npmコマンドを使用してインストールすることができます。

以下は、Pug(旧Jade)を使用したテンプレートエンジンの例です。

まず、Pugをインストールします。

npm install pug

次に、以下のようなPugテンプレートを作成します。

html
  head
    title= pageTitle
  body
    h1= pageTitle
    ul
      each fruit in fruits
        li= fruit

このPugテンプレートでは、pageTitleとfruitsという2つの変数を使用しています。これらの変数は、Expressアプリケーションから渡されます。

次に、以下のようなExpressアプリケーションを作成します。

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

app.set('view engine', 'pug');
app.set('views', './views');

app.get('/', function (req, res) {
  res.render('index', { pageTitle: 'My awesome page', fruits: ['apple', 'banana', 'orange'] });
});

app.listen(3000, function () {
  console.log('Example app listening on port 3000!');
});

これにより、Expressアプリケーションは、ルートパスにリクエストがあった場合、index.pugファイルを使用してHTMLを生成し、クライアントに返します。

この例では、app.setメソッドを使用して、テンプレートエンジンとしてPugを使用するようにExpressに設定しています。次に、app.setメソッドを使用して、テンプレートファイルが格納されているディレクトリを指定しています。

app.getメソッドでは、renderメソッドを使用して、Pugテンプレートに変数を渡し、HTMLを生成しています。

このように、テンプレートエンジンを使用することで、サーバーサイドで動的なHTMLを生成することができます。Expressは、多くのテンプレートエンジンをサポートしているため、好みのテンプレートエンジンを使用することができます。

静的ファイルの配信

Expressでは、静的ファイルを配信するために express.static ミドルウェアを使用することができます。これにより、静的ファイルを配信するためのルーティング設定が簡単に行えます。

例えば、public ディレクトリ内に styles.css ファイルがある場合、以下のように express.static ミドルウェアを使用して、そのファイルを配信することができます。

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

app.use(express.static('public'));

app.listen(3000, () => {
  console.log('Example app listening on port 3000!');
});

この例では、app.use メソッドに express.static('public') を渡しています。これにより、public ディレクトリ内の静的ファイルを配信するためのルーティングが設定されます。

上記の設定を行った場合、http://localhost:3000/styles.css で public/styles.css ファイルが配信されます。

また、express.static ミドルウェアには、第二引数としてオプションを渡すことができます。以下は、express.static ミドルウェアで配信するファイルのキャッシュ期間を1時間に設定する例です。

app.use(express.static('public', { maxAge: '1h' }));

maxAge オプションには、ミリ秒単位でキャッシュ期間を指定します。

なお、express.static ミドルウェアは、指定されたファイルが存在しない場合には自動的に次のミドルウェアに処理が移ります。つまり、404エラーを返すような処理が自動的に行われます。

データベース

Expressでは、MongoDBやMySQLなどのデータベースを使用するためのパッケージを提供しています。これらのパッケージを使用することで、データベースへの接続、クエリの実行、データの取得や更新などが簡単に行えます。

以下は、MongoDBを使用するための例です。まず、MongoDBに接続するためのパッケージをインストールします。

npm install mongoose

次に、接続するためのコードを記述します。

const mongoose = require('mongoose');

mongoose.connect('mongodb://localhost/my_database', {
  useNewUrlParser: true,
  useUnifiedTopology: true,
})
  .then(() => console.log('MongoDB connected'))
  .catch(err => console.log(err));

これにより、mongodb://localhost/my_databaseというURLでMongoDBに接続し、接続が成功した場合はMongoDB connectedというメッセージが表示されます。また、useNewUrlParserとuseUnifiedTopologyは、MongoDBの最新の仕様に従って接続するために必要なオプションです。

次に、モデルを定義してデータベースにアクセスするためのコードを記述します。以下は、ユーザー情報を扱うモデルの例です。

const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  name: String,
  email: String,
  password: String,
});

const User = mongoose.model('User', userSchema);

module.exports = User;

mongoose.Schemaを使用して、データモデルのスキーマを定義します。ここでは、名前、メールアドレス、パスワードの3つのフィールドを定義しています。mongoose.modelを使用して、スキーマをもとにモデルを定義します。そして、module.exportsを使用して、このモデルを外部ファイルから読み込めるようにします。

最後に、実際にデータベースにアクセスするコードを記述します。以下は、ユーザー情報を取得するための例です。

const express = require('express');
const User = require('./models/user');

const app = express();

app.get('/users', async (req, res) => {
  const users = await User.find();
  res.json(users);
});

app.listen(3000, () => {
  console.log('Server started');
});

/usersエンドポイントにアクセスすると、User.find()を使用してすべてのユーザー情報を取得し、JSON形式でレスポンスを返します。

以上のように、Expressでは、データベースを簡単に扱うためのパッケージが提供されています。これを活用することで、効率的なWebアプリケーションの開発が可能になります。ただし、データベースの操作には慎重さが求められます。例えば、入力値のバリデーションやセキュリティー対策をしっかりと行うことが必要です。また、データベースのパフォーマンスにも配慮する必要があります。大量のデータを扱う場合には、適切なインデックスを作成するなど、データベースの最適化にも注意が必要です。

セッション管理

Expressでは、セッション管理のためのパッケージとしてexpress-sessionが提供されています。これを使用することで、ユーザーのログイン状態を維持したり、カートの情報などのセッションに関連する情報を保持することができます。

まず、express-sessionパッケージをインストールします。

npm install express-session

次に、セッションを使用するための初期設定を行います。以下は、セッションを暗号化するためのキーを生成する例です。

const express = require('express');
const session = require('express-session');

const app = express();

app.use(session({
  secret: 'mysecretkey',
  resave: false,
  saveUninitialized: false
}));

app.use()を使用して、セッションの設定を行います。secretは、セッションの暗号化に使用されるキーを指定します。resaveとsaveUninitializedは、それぞれ、セッションの再保存と初期化を行うかどうかを指定するオプションです。

次に、セッションを使用してデータを保持する例を示します。以下は、ログイン情報を保持するための例です。

app.post('/login', (req, res) => {
  const { username, password } = req.body;
  
  // ユーザー名とパスワードを検証する処理
  
  if (username === 'admin' && password === 'password') {
    req.session.isLoggedIn = true;
    req.session.username = username;
    res.redirect('/dashboard');
  } else {
    res.send('Invalid username or password');
  }
});

app.get('/dashboard', (req, res) => {
  if (req.session.isLoggedIn) {
    res.send(`Welcome ${req.session.username}!`);
  } else {
    res.redirect('/login');
  }
});

req.sessionオブジェクトを使用して、セッションにデータを保持することができます。ログイン成功時に、req.session.isLoggedInとreq.session.usernameにそれぞれtrueとユーザー名を設定し、ダッシュボードページにリダイレクトします。ダッシュボードページでは、req.session.isLoggedInがtrueであれば、req.session.usernameを使用して、ユーザー名を表示します。

以上のように、Expressでは、express-sessionパッケージを使用することで、セッション管理が簡単になります。セッションを使用することで、Webアプリケーションのユーザー体験を向上させることができます。

CSRF対策

CSRF(Cross-Site Request Forgery)は、攻撃者が別のWebサイトにアクセスして、そのWebサイトのユーザーが持っている認証情報を使用して、不正なリクエストを送信することによって、攻撃を行う手法です。例えば、攻撃者が正当なユーザーに代わって、意図しないトランザクションを実行させることができます。

Expressでは、CSRF対策のために、csurfというパッケージを提供しています。このパッケージを使用すると、CSRFトークンを生成して、リクエストに含めることができます。これにより、攻撃者が不正なリクエストを送信することが困難になります。

以下は、csurfを使用して、CSRF対策を実装する方法の例です。

csurfパッケージをインストールします。

npm install csurf

Expressアプリケーションで、csurfパッケージを使用します。

const express = require('express');
const csurf = require('csurf');

const app = express();

// CSRF対策のミドルウェアを使用
app.use(csurf());

// ルートハンドラー
app.get('/', (req, res) => {
// CSRFトークンを含んだHTMLをレスポンスとして返す
res.send( <form action="/process" method="POST"> <input type="hidden" name="_csrf" value="${req.csrfToken()}"> <button type="submit">Submit</button> </form> );
});

// フォームのPOSTリクエストを処理する
app.post('/process', (req, res) => {
res.send('Processed!');
});

app.listen(3000, () => {
console.log('Server started');
});

フォームのHTMLに、CSRFトークンを含めます。これにより、フォームがPOSTされた際に、CSRFトークンもサーバーに送信されます。

フォームのPOSTリクエストを処理する際に、csurfパッケージが提供するcsrf()ミドルウェアを使用します。これにより、送信されたCSRFトークンが正当かどうかを検証し、正当でない場合はエラーを返します。

以上のように、csurfパッケージを使用することで、Expressアプリケーションで簡単にCSRF対策を実装することができます。

WebSocket

WebSocketは、リアルタイム通信を実現するためのプロトコルであり、サーバーとクライアントの間で双方向の通信を確立することができます。これにより、サーバー側での状態の変化をリアルタイムにクライアント側に反映させたり、逆にクライアントからのイベントをサーバー側で処理することができます。

Expressでは、WebSocketを使用するためのパッケージとして、socket.ioが提供されています。まず、socket.ioをインストールします。

npm install socket.io

次に、サーバー側で以下のようにコードを記述します。

const express = require('express');
const http = require('http');
const socketio = require('socket.io');

const app = express();
const server = http.createServer(app);
const io = socketio(server);

io.on('connection', socket => {
  console.log('a user connected');
  socket.on('chat message', msg => {
    console.log('message: ' + msg);
    io.emit('chat message', msg);
  });
});

server.listen(3000, () => {
  console.log('Server started');
});

上記のコードでは、サーバーに新しいクライアントが接続されると、'connection'イベントが発生し、コールバック関数内で処理を行います。クライアントから'message'イベントが送信された場合は、受信したメッセージをコンソールに表示し、全てのクライアントに'message'イベントとともにメッセージを送信します。

クライアント側のコードは、以下のようになります。

<!DOCTYPE html>
<html>
<head>
  <title>Chat</title>
</head>
<body>
  <ul id="messages"></ul>
  <form id="form">
    <input id="input" autocomplete="off" />
    <button>Send</button>
  </form>
  <script src="/socket.io/socket.io.js"></script>
  <script>
    const socket = io();
    const form = document.getElementById('form');
    const input = document.getElementById('input');
    const messages = document.getElementById('messages');

    form.addEventListener('submit', e => {
      e.preventDefault();
      if (input.value) {
        socket.emit('chat message', input.value);
        input.value = '';
      }
    });

    socket.on('chat message', msg => {
      const li = document.createElement('li');
      li.textContent = msg;
      messages.appendChild(li);
    });
  </script>
</body>
</html>

上記のコードでは、socket.ioクライアントを読み込み、サーバーに接続します。フォームが送信された場合、クライアントから'send message'イベントを送信し、メッセージを送信します。また、サーバーから'message'イベントを受信した場合は、受信したメッセージをクライアント側のHTMLに表示するようにしています。

このように、WebSocketを使用することで、サーバーとクライアント間で双方向のリアルタイム通信が可能になります。また、socket.ioを使用することで、WebSocketの実装を簡単に行うことができます。

Discussion