Express(+TypeScript)入門ガイド: Webアプリケーションを素早く構築する方法

2024/12/07に公開

はじめに

Node.jsは、高速でスケーラブルなサーバーサイド開発を可能にする人気のプラットフォームです。そして、そのNode.jsをさらに活用できる強力なWebフレームワークが「Express」です。本記事では、Expressを使ってWebアプリケーションを開発する方法を初心者向けに解説します。これから始める方や、まだExpressに触れたことがない方でも安心して学べる内容となっています。まずはExpressのセットアップから、ルーティング、API作成の基本的な部分までを順を追って解説し、実践的な知識を身につけていきましょう。

Expressとは

Node.jsで使用される軽量で柔軟なWebアプリケーションフレームワークです。主にサーバーサイドのアプリケーション開発を簡単にするためのツールセットを提供します。Expressは、HTTPリクエストの処理、ルーティング、ミドルウェア(リクエストとレスポンスを操作する関数)の管理、テンプレートエンジンのサポートなど、Webサーバーに必要な基本的な機能を簡単に実装できます。

Expressの特徴

  1. シンプルで軽量
    Expressは非常にシンプルで、必要最低限の機能だけを提供しています。そのため、プロジェクトに合わせて自由に拡張しやすく、学習コストが低いです。

  2. ルーティング機能
    Expressは、特定のURLに対するリクエスト(GET, POST, PUT, DELETEなど)に対応するルート(エンドポイント)を簡単に定義できます。

  3. ミドルウェアの活用
    ミドルウェアはリクエストを処理する前に実行される関数で、認証、ロギング、エラーハンドリング、データのパースなど、リクエストに対する共通の処理を一元化できます。

  4. テンプレートエンジンのサポート
    Expressは、HTMLの動的生成をサポートするテンプレートエンジン(例えば、EJSやPugなど)と統合しやすく、HTMLページを動的に生成できます。

  5. 非同期処理対応
    ExpressはNode.js上で動作しており、非同期I/Oに対応しています。そのため、パフォーマンスが高く、同時に多くのリクエストを処理できます。

Expressを使う利点

  1. 素早くアプリケーションを立ち上げられる
    Expressを使うことで、設定や準備にかかる時間を短縮し、すぐにサーバーやAPIの開発に取り掛かれます。

  2. 大規模アプリケーションにも対応
    Expressは小規模なアプリケーションから大規模なアプリケーションまで対応でき、拡張性に優れています。多くのプラグインやミドルウェアが公式・コミュニティによって提供されています。

  3. 広く使われている
    ExpressはNode.jsのエコシステムの中でも非常に人気があり、多くの開発者によって使われています。そのため、リソースやドキュメントが豊富で学習しやすいです。

Express公式サイト

https://expressjs.com/

Expressの導入

まずはpackage.jsonを作成します。

mkdir express-basics-guide
cd express-basics-guide
npm init -y

Image from Gyazo

次にExpressをインストールします。

npm i express

Expressの導入は以上となります。

サンプルコードの作成

簡単なコードを書いてExpressが正しく動作するか確認します。

プロジェクト直下にserver.jsというファイルを作成します。

server.js
const express = require('express');
const app = express();

app.get('/', (req, res) => {
  res.send('Hello, Express');
});

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

コードの解説をします。

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

Expressが提供している関数を実行しサーバー用のインスタンスを生成します。

app.get('/', (req, res) => {
  res.send('Hello, Express');
});

GETメソッドで/にアクセスされた際にHello, Expressという文字列を返します。

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

3000番で起動し起動後にコンソールログを出力し起動できていることを確認できるようにしておきます。

動作確認

Expressを起動します。

node server.js

どうやら起動できたようです。

Image from Gyazo

ブラウザでhttp://localhost:3000/にアクセスすると先ほど設定した文字列が表示されていることが確認できます。

Image from Gyazo

都度ブラウザで確認するのが面倒という方はcurlでもいいかと思います。

curl http://localhost:3000/

TypeScriptで開発

Expressが非常に簡単だというイメージができてきたところで少しずつ内容を深掘りしていきたいと思います。

まずはTypeScriptでコードを書くよう設定していきます。

必要なパッケージのインストール

TypeScriptで動かすために必要なパッケージをインストールします。

npm i -D typescript @types/node @types/express ts-node
  1. typescript
    TypeScriptのコンパイラです。TypeScriptコードをJavaScriptにトランスパイルするために必要です。プロジェクトでTypeScriptを使用する場合に必須のパッケージです。

  2. @types/node
    Node.jsのための型定義パッケージです。Node.jsの標準ライブラリ(fs, http, path など)をTypeScriptで使用する際に、型チェックや補完機能を提供します。

  3. @types/express
    Expressの型定義パッケージです。ExpressフレームワークをTypeScriptで使う際に、ルートやミドルウェアの型情報を提供し、補完や型チェックをサポートします。

  4. ts-node
    TypeScriptのコードを直接実行するためのツールです。通常はTypeScriptコードをコンパイルしてから実行しますが、ts-nodeを使うとコンパイルを事前に行わず、直接実行することができます。開発中のスクリプト実行に便利です。

tsconfigの作成

TypeScript プロジェクトのコンパイラ設定を管理するためのtsconfig.jsonファイルを作成します。

npx tsc --init

js -> tsに変更

拡張子を.jsから.tsに変更しつつコードを修正します。

server.ts
- const express = require('express');
+ import express from 'express'
const app = express();

app.get('/', (_req, res) => {
  res.send('Hello, Express');
});

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

エディターで確認すると型が定義されていることがわかります。

Image from Gyazo

動作確認

ts-nodeでExpressを起動します。

npx ts-node server.ts

curlでアクセスし設定した文字列が返ってくればOKです。

curl http://localhost:3000/

変更を監視

変更の都度コマンドを実行してExpressを起動するのは開発時に面倒なのでファイル監視ツールを使います。

npm i -D nodemon

起動コマンドが長くなるのでpackage.jsonで定義します。

  "scripts": {
+     "dev": "nodemon --watch '*.ts' --exec 'ts-node' server.ts",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

下記コマンドで実行できるようになります。

npm run dev

server.tsでレスポンス内容を変更するなど更新しファイルを保存すると自動でExpressの再起動がかかります。

Image from Gyazo

これでExpressの開発が行いやすくなりました。

ルーティング

Expressの機能の一つであるルーティングについて幾つかご紹介します。

server.tsに追記していく形を想定しています。

GETメソッド

/userにGETメソッドでアクセス可能となります。

app.get('/user', (req, res) => {
  res.send('Hello, User');
});
curl http://localhost:3000/user

Hello, User

POSTメソッド

app.post()でPOSTメソッドが作成できます。

app.post('/submit', (req, res) => {
  res.send('Form submitted!');
});
url -X POST http://localhost:3000/submit

Form submitted!

パラメータ付きルーティング

URLに動的なパラメータを含めることができます。

パラメータはreq.paramsでアクセス可能ですのでリクエストに応じたレスポンスを返すことが可能です。

app.get('/users/:id', (req, res) => {
  const userId = req.params.id;
  res.send(`User ID: ${userId}`);
});
curl http://localhost:3000/users/123

User ID: 123

クエリパラメータ

URLにクエリ文字列を含める場合の処理です。
パラメータはreq.paramsでアクセス可能ですのでクエリパラメータに応じたレスポンスを返すことが可能です。

app.get('/search', (req, res) => {
  const query = req.query.q;
  res.send(`Search query: ${query}`);
});
curl "http://localhost:3000/search?q=express"

Search query: express

ルーティングのグループ化

いくつもルートを定義していくと複雑になり保守性が下がります。

可能であればグループ化することで保守性を向上できます。

const router = express.Router();

router.get('/profile', (req, res) => {
  res.send('User profile');
});

router.get('/settings', (req, res) => {
  res.send('User settings');
});

app.use('/user', router);
curl http://localhost:3000/user/profile

User profile
curl http://localhost:3000/user/settings

User settings%
app.get('/test', (req, res) => {
  res.send('Hello from Handler 1');
});

app.get('/test', (req, res) => {
  res.send('Hello from Handler 2');
});

curl http://localhost:3000/test

Hello from Handler 1

ミドルウェア

Expressのミドルウェアは、リクエストとレスポンスの間で追加の処理を行う関数です。

ミドルウェアを使うことで、リクエストのログ記録、認証、データの加工、エラーハンドリングなど、アプリケーションの機能を拡張できます。

ミドルウェアの特徴

  1. リクエストとレスポンスを操作可能
    リクエスト (req) オブジェクトを変更したり、レスポンス (res) オブジェクトに追加の処理を行うことができます。

  2. チェーン形式で処理を流す
    next() を呼ぶことで次のミドルウェアやルートに処理を渡します。呼び出さないと処理が止まります。

  3. 順番が重要
    ミドルウェアは登録された順に実行されます。順序を間違えると動作がおかしくなることがあります。

アプリケーションレベルのミドルウェア

全ての処理共通で設定したい場合。
ここではサーバー側でリクエストのメソッドとURLを出力し簡易的なログ記録を行っています。

// ログ記録用ミドルウェア
app.use((req, res, next) => {
  console.log(`${req.method} ${req.url}`);
  next();
});

リクエストに応じてサーバー側にログが出力されることが確認できます。

curl http://localhost:3000/

curl http://localhost:3000/test

curl -X POST http://localhost:3000/submit

Image from Gyazo

ルーター(ルート)レベルのミドルウェア

特定のルートにのみ適用されるミドルウェアです。

app.get('/user/:id', (req, res, next) => {
  console.log(`Request for user ID: ${req.params.id}`);
  next();
}, (req, res) => {
  res.send(`User ID: ${req.params.id}`);
});
curl http://localhost:3000/user/123

User ID: 123

起動したサーバー側には下記のログが出力されます。

Request for user ID: 123

ビルトインミドルウェア

Expressに組み込まれている便利なミドルウェアが存在します。

express.json()はJSON形式のリクエストボディを解析します。

// JSON解析ミドルウェアを設定
app.use(express.json());


app.post('/api/data', (req, res) => {
  console.log(req.body); // パースされたJSONデータ
  res.send(`Received: ${JSON.stringify(req.body)}`);
});
curl -X POST http://localhost:3000/api/data \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "age": 25}'

Received: {"name":"Alice","age":25}

誤った形式のJSONを送信するとエラーになります。

curl -X POST http://localhost:3000/api/data \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "age": 25,'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>SyntaxError: Unexpected end of JSON input<br> &nbsp; &nbsp;at JSON.parse (&lt;anonymous&gt;)<br> &nbsp; &nbsp;at parse (/Users/test/Documents/workspace/express-basics-guide/node_modules/body-parser/lib/types/json.js:92:19)<br> &nbsp; &nbsp;at /Users/test/Documents/workspace/express-basics-guide/node_modules/body-parser/lib/read.js:128:18<br> &nbsp; &nbsp;at AsyncResource.runInAsyncScope (node:async_hooks:203:9)<br> &nbsp; &nbsp;at invokeCallback (/Users/test/Documents/workspace/express-basics-guide/node_modules/raw-body/index.js:238:16)<br> &nbsp; &nbsp;at done (/Users/test/Documents/workspace/express-basics-guide/node_modules/raw-body/index.js:227:7)<br> &nbsp; &nbsp;at IncomingMessage.onEnd (/Users/test/Documents/workspace/express-basics-guide/node_modules/raw-body/index.js:287:7)<br> &nbsp; &nbsp;at IncomingMessage.emit (node:events:517:28)<br> &nbsp; &nbsp;at IncomingMessage.emit (node:domain:489:12)<br> &nbsp; &nbsp;at endReadableNT (node:internal/streams/readable:1400:12)</pre>
</body>
</html>

注意点

エラーハンドリング

先ほどの章でJSONの形式が不正な場合にエラーメッセージが返却されていました。

こちらでもエラー内容を確認できますが一般的にはエラーハンドリングを定義しておきます。

import express, { ErrorRequestHandler } from 'express'

// カスタムエラーハンドリング
const errorHandler: ErrorRequestHandler = (err, req, res, next) => {
  if (err instanceof SyntaxError) {
    res.status(400).send({ error: 'Invalid JSON format' });
    return
  }
  next(err);
};

app.use(errorHandler);

SyntaxErrorが発生した場合のエラーメッセージを定義しました。

curl -X POST http://localhost:3000/api/data -H "Content-Type: application/json" -d '{"name": "Alice", "age": 25,'

{"error":"Invalid JSON format"}

構文エラーに関してはエラーハンドリングできましたが実際のアプリケーションでは様々な理由でエラーが発生しレスポンスを正しく返せないケースがあります。

わかりやすい例

app.get('/error', (req, res) => {
  throw new Error('Something went wrong!');
});
curl http://localhost:3000/error

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Error: Something went wrong!<br> &nbsp; &nbsp;at /Users/test/Documents/workspace/express-basics-guide/server.ts:74:9<br> &nbsp; &nbsp;at Layer.handle [as handle_request] (/Users/test/Documents/workspace/express-basics-guide/node_modules/express/lib/router/layer.js:95:5)<br> &nbsp; &nbsp;at next (/Users/test/Documents/workspace/express-basics-guide/node_modules/express/lib/router/route.js:149:13)<br> &nbsp; &nbsp;at Route.dispatch (/Users/test/Documents/workspace/express-basics-guide/node_modules/express/lib/router/route.js:119:3)<br> &nbsp; &nbsp;at Layer.handle [as handle_request] (/Users/test/Documents/workspace/express-basics-guide/node_modules/express/lib/router/layer.js:95:5)<br> &nbsp; &nbsp;at /Users/test/Documents/workspace/express-basics-guide/node_modules/express/lib/router/index.js:284:15<br> &nbsp; &nbsp;at Function.process_params (/Users/test/Documents/workspace/express-basics-guide/node_modules/express/lib/router/index.js:346:12)<br> &nbsp; &nbsp;at next (/Users/test/Documents/workspace/express-basics-guide/node_modules/express/lib/router/index.js:280:10)<br> &nbsp; &nbsp;at Layer.handle [as handle_request] (/Users/test/Documents/workspace/express-basics-guide/node_modules/express/lib/router/layer.js:91:12)<br> &nbsp; &nbsp;at trim_prefix (/Users/test/Documents/workspace/express-basics-guide/node_modules/express/lib/router/index.js:328:13)</pre>
</body>
</html>

このようなケースのために汎用的なエラーハンドリングを用意しておきます。

// カスタムエラーハンドリング
const errorHandler: ErrorRequestHandler = (err, req, res, next) => {
  if (err instanceof SyntaxError) {
    res.status(400).send({ error: 'Invalid JSON format' });
    return
  }
-   next(err);
+   console.error(err);  // エラーログを記録(開発中に役立つ)
+   res.status(500).send('Internal Server Error')
+   return
};

app.use(errorHandler);

このエラーハンドリングミドルウェアはapp.getなどのルーティングの後に記述します。また、app.useの中でも最後によばれるよう定義する必要があります。

curl http://localhost:3000/error

{"error":"Internal Server Error"}

ここまで書いてきたserver.tsです。

server.ts
server.ts
import express, { ErrorRequestHandler } from 'express'
const app = express();
const router = express.Router();

// ログ記録用ミドルウェア
app.use((req, res, next) => {
  console.log(`${req.method} ${req.url}`);
  next();
});

// JSON解析ミドルウェアを設定
app.use(express.json());

app.get('/user/:id', (req, res, next) => {
  console.log(`Request for user ID: ${req.params.id}`);
  next();
}, (req, res) => {
  res.send(`User ID: ${req.params.id}`);
});

app.get('/', (req, res) => {
  res.send('Hello, Express');
});

app.get('/users/:id', (req, res) => {
  const userId = req.params.id;
  res.send(`User ID: ${userId}`);
});

app.get('/search', (req, res) => {
  const query = req.query.q;
  res.send(`Search query: ${query}`);
});

app.post('/submit', (req, res) => {
  res.send('Form submitted!');
});

app.post('/api/data', (req, res) => {
  console.log(req.body);
  res.send(`Received: ${JSON.stringify(req.body)}`);
});

router.get('/profile', (req, res) => {
  res.send('User profile');
});

router.get('/settings', (req, res) => {
  res.send('User settings');
});

app.use('/user', router);

app.get('/test', (req, res) => {
  res.send('Hello from Handler 1');
});

app.get('/test', (req, res) => {
  res.send('Hello from Handler 2');
});

app.get('/error', (req, res) => {
  throw new Error('Something went wrong!');
});

// カスタムエラーハンドリング
const errorHandler: ErrorRequestHandler = (err, req, res, next) => {
  if (err instanceof SyntaxError) {
    res.status(400).send({ error: 'Invalid JSON format' });
    return
  }
  console.error(err);  // エラーログを記録(開発中に役立つ)
  res.status(500).send({ error: 'Internal Server Error' })
  return
};

app.use(errorHandler);

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

おすすめ記事

さいごに

この記事では、Node.jsのExpressフレームワークを使ったWebアプリケーション開発の基本的な部分について学んできました。Expressの魅力は、シンプルでありながら非常に強力な機能を持っているところです。この記事を参考に、実際に自分のプロジェクトにExpressを導入してみてください。実際の開発で使いこなすためには、いくつかの実践的なプロジェクトに取り組むことが重要です。これからもNode.jsとExpressの学習を続けて、効率的なWeb開発スキルを磨いていきましょう。

記事に関するお問い合わせ📝

記事の内容に関するご質問、ご意見などは、下記よりお気軽にお問い合わせください。
ご質問フォームへ

技術支援などお仕事に関するお問い合わせ📄

技術支援やお仕事のご依頼に関するお問い合わせは、下記よりお気軽にお問い合わせください。
お問い合わせフォームへ