Expressとは、Node.jsでWebサーバーやAPIを短いコードで作れる軽量なフレームワークです。 2026年10月時点の最新版は Express 5.2.1(npm の latest)で、Node.js 18 以上が必要です。4系は latest-4 タグで 4.22.3 が引き続き配布されています。

この記事では、Express 5 と ESM(import 構文)で動くタスク管理の CRUD API 一式を作り、Node.js 22.22.2 上で起動して curl の実行結果をすべて掲載します。あわせて、Express 4 から 5 への変更点を比較表にし、実際に出るエラー文を再現しました。入門記事でよく省かれる「async 関数のエラー処理」「ミドルウェアの順序」まで、コピペで確かめられる形でまとめています。

Express 5 入門の早見表

やりたいこと Express 5 の書き方 ポイント
インストール npm i express(5.2.1 が入る) Node 18 以上。4系は npm i express@4
ESM で読み込む import express from 'express' package.json に "type": "module"
アプリ作成と起動 const app = express(); app.listen(7000) ポートは環境変数で切り替えると便利
GET ルート app.get('/health', (req, res) => res.json({...})) res.json が Content-Type も設定
JSON ボディを受け取る app.use(express.json()) → req.body 解析されないと req.body は undefined
パスパラメータ /tasks/:id → req.params.id 値は常に文字列。数値化して検証
クエリ文字列 /tasks?done=true → req.query.done 値は文字列。req.query は読み取り専用
ステータスコード res.status(201).json(obj) 本文なしは res.sendStatus(204)
ルートをファイル分割 const router = express.Router() → app.use('/tasks', router) router.get('/:id') は /tasks/:id になる
404 全ルートの後に app.use((req, res) => ...) 一致しなかったリクエストがここに来る
エラーハンドラ app.use((err, req, res, next) => ...) 引数4つで、最後に置く
async 関数のエラー throw するだけでエラーハンドラへ Express 5 から自動。4 では next(err) が必要
ワイルドカード /files/*splat → req.params.splat(配列) 5 では /* だけは書けない
省略可能な部分 /img/:file{.:ext} 5 では ? を使わず {} で囲む
静的ファイル app.use(express.static('public')) ドットファイルは既定で配信しない

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Node.js Express入門|Express5対応の最小APIをコピペで作る」

Express 5 の版と動作環境を確認する

まず npm で最新版と必要な Node のバージョンを確認しておくと、記事や手順書との差に迷いません。

$ node -v
v22.22.2
$ npm view express version engines dist-tags --json
{
  "version": "5.2.1",
  "engines": {
    "node": ">= 18"
  },
  "dist-tags": {
    "latest": "5.2.1",
    "latest-4": "4.22.3"
  }
}
項目 Express 5 Express 4
npm のタグ latest(5.2.1) latest-4(4.22.3)
必要な Node.js 18 以上 0.10 以上
npm i express で入る版 こちら npm i express@4 と明示

検索上位の入門記事の多くはバージョンが書かれておらず、4系の書き方が混ざっています。この記事のコードはすべて Express 5.2.1 / Node 22.22.2 で実行しています。

最小の Express アプリを作る手順

1. プロジェクトを作ってインストールする

mkdir tasks-api && cd tasks-api
npm init -y
npm pkg set type=module   # ESM(import 構文)を使う設定
npm i express@5
mkdir -p src/routes public
echo '<h1>Tasks API</h1>' > public/index.html

"type": "module" を入れると .js ファイルが ESM として扱われ、import / export がそのまま使えます。 CommonJS(require)で書きたい場合はこの行を省きます。

2. 完成形のフォルダ構成

tasks-api/
├── package.json        "type": "module"
├── public/
│   └── index.html      静的ファイル
└── src/
    ├── server.js       起動(listen)だけ
    ├── app.js          ミドルウェアとルートの組み立て
    ├── store.js        メモリ上のデータ保存
    └── routes/
        └── tasks.js    /tasks 以下のルート(Router)

app.js(組み立て)と server.js(起動)を分けておくと、テストで app だけを読み込めるので後から便利です。

タスク管理 CRUD API の完成コード

src/store.js(メモリ保存)

// メモリ上のタスク保存(サーバーを止めると消えます)
const tasks = new Map();
let nextId = 1;

export function list({ done } = {}) {
  const all = [...tasks.values()];
  if (done === undefined) return all;
  return all.filter((t) => t.done === done);
}
export function find(id) {
  return tasks.get(id);
}
export function create(title) {
  const task = { id: nextId++, title, done: false };
  tasks.set(task.id, task);
  return task;
}
export function update(id, fields) {
  const task = tasks.get(id);
  if (!task) return undefined;
  Object.assign(task, fields);
  return task;
}
export function remove(id) {
  return tasks.delete(id);
}
// async の例として使う(DB 呼び出しの代わり)
export async function findAsync(id) {
  await new Promise((r) => setTimeout(r, 10));
  const task = tasks.get(id);
  if (!task) {
    const err = new Error(`task ${id} not found`);
    err.status = 404;
    throw err;
  }
  return task;
}

findAsync は、データベースへの問い合わせのような非同期処理の代わりです。見つからないときに status: 404 を付けた Error を投げ、エラーハンドラで使います。

src/routes/tasks.js(Router でルートを分割)

import { Router } from 'express';
import * as store from '../store.js';

const router = Router();

// パスパラメータ :id を数値に変換して検証(router.param でも可)
function parseId(req, res, next) {
  const id = Number(req.params.id);
  if (!Number.isInteger(id) || id < 1) {
    return res.status(400).json({ error: 'id must be a positive integer' });
  }
  req.taskId = id;
  next();
}

// GET /tasks?done=true  一覧(クエリで絞り込み)
router.get('/', (req, res) => {
  const { done } = req.query; // 文字列 "true" / "false" / undefined
  const filter = done === undefined ? {} : { done: done === 'true' };
  res.json(store.list(filter));
});

// GET /tasks/:id  1件取得(async 関数。throw は自動でエラーハンドラへ)
router.get('/:id', parseId, async (req, res) => {
  const task = await store.findAsync(req.taskId);
  res.json(task);
});

// POST /tasks  作成
router.post('/', (req, res) => {
  const title = req.body?.title;
  if (typeof title !== 'string' || title.trim() === '') {
    return res.status(400).json({ error: 'title is required' });
  }
  const task = store.create(title.trim());
  res.status(201).location(`/tasks/${task.id}`).json(task);
});

// PATCH /tasks/:id  一部更新
router.patch('/:id', parseId, (req, res) => {
  const { title, done } = req.body ?? {};
  const fields = {};
  if (title !== undefined) fields.title = String(title);
  if (done !== undefined) fields.done = Boolean(done);
  const task = store.update(req.taskId, fields);
  if (!task) return res.status(404).json({ error: 'task not found' });
  res.json(task);
});

// DELETE /tasks/:id  削除
router.delete('/:id', parseId, (req, res) => {
  if (!store.remove(req.taskId)) {
    return res.status(404).json({ error: 'task not found' });
  }
  res.sendStatus(204);
});

export default router;

ポイントは3つです。

  • parseId のように、ルートの途中に関数を挟めるのがミドルウェアの基本形です。next() を呼ぶと次の関数へ進み、res.status(400).json(...) で返すとそこで終わります。
  • req.params と req.query の値は常に文字列です。Number() や === 'true' で型を変換します。
  • req.body?.title と ?. を付けているのは、Express 5 では JSON として解析されなかったとき req.body が undefined になるためです。

src/app.js(ミドルウェアとルートの組み立て)

import express from 'express';
import tasksRouter from './routes/tasks.js';

const app = express();

// 1. ログ(自作の最小ミドルウェア)
app.use((req, res, next) => {
  const start = Date.now();
  res.on('finish', () => {
    console.log(`${req.method} ${req.originalUrl} ${res.statusCode} ${Date.now() - start}ms`);
  });
  next();
});

// 2. 静的ファイル(public/ 以下をそのまま配信)
app.use(express.static('public'));

// 3. JSON ボディの解析(Content-Type: application/json のときだけ req.body が入る)
app.use(express.json({ limit: '100kb' }));

// 4. ルーティング
app.get('/health', (req, res) => {
  res.json({ status: 'ok' });
});
app.use('/tasks', tasksRouter);

// 5. 404(どのルートにも一致しなかったとき)
app.use((req, res) => {
  res.status(404).json({ error: `Not Found: ${req.method} ${req.originalUrl}` });
});

// 6. エラーハンドラ(引数が4つの関数。必ず最後に置く)
app.use((err, req, res, next) => {
  const status = err.status ?? err.statusCode ?? 500;
  if (status >= 500) console.error(err);
  res.status(status).json({ error: status >= 500 ? 'Internal Server Error' : err.message });
});

export default app;

エラーハンドラでは、500 系のときは詳細をログにだけ出し、レスポンスには固定文言を返しています。 内部のエラー文をそのまま利用者に見せない形にしておくと安心です。エラーレスポンスの JSON 形式の決め方は「REST APIの設計」「APIエラーレスポンス設計」の記事で詳しく扱っています。

src/server.js(起動)

import app from './app.js';

const PORT = process.env.PORT ?? 7000;
app.listen(PORT, () => {
  console.log(`listening on http://localhost:${PORT}`);
});
$ PORT=7000 node src/server.js
listening on http://localhost:7000

curl で全ルートを動かした実行結果

別のターミナルから curl で呼び出した結果を、そのまま載せます(Express 5.2.1 / Node 22.22.2)。

作成(POST)と一覧(GET)

$ curl -s http://localhost:7000/health
{"status":"ok"}

$ curl -s -X POST http://localhost:7000/tasks -H 'Content-Type: application/json' -d '{"title":"備品の発注"}'
{"id":1,"title":"備品の発注","done":false}

$ curl -s -X POST http://localhost:7000/tasks -H 'Content-Type: application/json' -d '{"title":"会議室の予約"}'
{"id":2,"title":"会議室の予約","done":false}

$ curl -s -i -X POST http://localhost:7000/tasks -H 'Content-Type: application/json' -d '{"title":"在庫の棚卸し"}'
HTTP/1.1 201 Created
X-Powered-By: Express
Location: /tasks/3
Content-Type: application/json; charset=utf-8
Content-Length: 50
ETag: W/"32-yFYQA1Hy7VoE5lTAZqQhxrz/IoI"
Date: Thu, 01 Oct 2026 14:33:43 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"id":3,"title":"在庫の棚卸し","done":false}

$ curl -s http://localhost:7000/tasks
[{"id":1,"title":"備品の発注","done":false},{"id":2,"title":"会議室の予約","done":false},{"id":3,"title":"在庫の棚卸し","done":false}]

res.status(201).location(...) によって、201 Created と Location ヘッダーが返っていることが確認できます。

更新(PATCH)とクエリでの絞り込み

$ curl -s -X PATCH http://localhost:7000/tasks/1 -H 'Content-Type: application/json' -d '{"done":true}'
{"id":1,"title":"備品の発注","done":true}

$ curl -s 'http://localhost:7000/tasks?done=true'
[{"id":1,"title":"備品の発注","done":true}]

$ curl -s 'http://localhost:7000/tasks?done=false'
[{"id":2,"title":"会議室の予約","done":false},{"id":3,"title":"在庫の棚卸し","done":false}]

1件取得・削除・削除後の取得

$ curl -s http://localhost:7000/tasks/2
{"id":2,"title":"会議室の予約","done":false}

$ curl -s -i -X DELETE http://localhost:7000/tasks/2
HTTP/1.1 204 No Content
X-Powered-By: Express
ETag: W/"a-bAsFyilMr4Ra1hIU5PyoyFRunpI"
Date: Thu, 01 Oct 2026 14:33:43 GMT
Connection: keep-alive
Keep-Alive: timeout=5

$ curl -s -i http://localhost:7000/tasks/2
HTTP/1.1 404 Not Found
Content-Type: application/json; charset=utf-8
(ヘッダー一部省略)

{"error":"task 2 not found"}

削除後の GET /tasks/2 は、async 関数内の throw がエラーハンドラに届いて 404 になっています。 try/catch も next(err) も書いていない点に注目してください。

入力エラーと 404

$ curl -s -i http://localhost:7000/tasks/abc
HTTP/1.1 400 Bad Request
(ヘッダー省略)
{"error":"id must be a positive integer"}

$ curl -s -X POST http://localhost:7000/tasks -H 'Content-Type: application/json' -d '{}'
{"error":"title is required"}          ← 400

$ curl -s -X POST http://localhost:7000/tasks -d 'title=x'
{"error":"title is required"}          ← 400(Content-Type が JSON でないので req.body は undefined)

$ curl -s -X POST http://localhost:7000/tasks -H 'Content-Type: application/json' -d '{"title":'
{"error":"Unexpected end of JSON input"}   ← 400(express.json が status 400 のエラーを渡す)

$ curl -s -i http://localhost:7000/nothing
HTTP/1.1 404 Not Found
(ヘッダー省略)
{"error":"Not Found: GET /nothing"}

$ curl -s http://localhost:7000/
<h1>Tasks API</h1>

壊れた JSON を送ると、express.json() が status = 400 付きのエラーを作り、自作のエラーハンドラに渡します。 そのため 500 ではなく 400 で返せています。

サーバー側のログ

listening on http://localhost:7000
GET /health 200 19ms
POST /tasks 201 239ms
POST /tasks 201 2ms
POST /tasks 201 5ms
GET /tasks 200 1ms
PATCH /tasks/1 200 2ms
GET /tasks?done=true 200 1ms
GET /tasks?done=false 200 4ms
GET /tasks/2 200 14ms
DELETE /tasks/2 204 1ms
GET /tasks/2 404 12ms
GET /tasks/abc 400 6ms
POST /tasks 400 4ms
POST /tasks 400 1ms
POST /tasks 400 5ms
GET /nothing 404 1ms
GET / 200 25ms

res.on('finish') を使うと、レスポンスを返し終えた時点のステータスコードと処理時間を記録できます。本格的なログには morgan や pino-http などのパッケージがよく使われます。

ミドルウェアの順序を図と早見表で理解する

Express は app.use / app.get を書いた順に上から試し、next() で次へ進みます。 順序を入れ替えるだけで結果が変わるので、図で流れを押さえておきましょう。

flowchart TD
    A[リクエスト] --> B[1. ログ]
    B --> C{2. express.static<br>public にファイルがある?}
    C -- ある --> R1[ファイルを返す]
    C -- ない --> D[3. express.json<br>req.body を作る]
    D --> E{4. ルート<br>/health, /tasks}
    E -- 一致 --> F[ハンドラ]
    E -- 不一致 --> G[5. 404 ハンドラ]
    F -- 正常 --> R2[res.json で返す]
    F -- throw / next err --> H[6. エラーハンドラ<br>引数4つ]
    D -- JSON が壊れている --> H
    H --> R3[エラーを JSON で返す]
順番 置くもの なぜこの位置か
1 ログ すべてのリクエストを記録するため最初に置く
2 CORS・セキュリティヘッダー 404 やエラーのときにもヘッダーを付けたいので早めに置く
3 express.static ファイルがあれば即返し、以降の処理を省ける
4 express.json / express.urlencoded ルートより前に置かないと req.body が入らない
5 ルート(app.get、app.use('/x', router)) 本体の処理
6 404 ハンドラ どのルートにも一致しなかったときだけ届く
7 エラーハンドラ(引数4つ) 最後。これより後のルートで起きたエラーは拾えない

CORS は cors パッケージを app.use(cors({ origin: 'http://localhost:5173' })) のように2番目の位置に置くのが基本です。 許可するオリジンの考え方は CORS の解説記事で詳しく扱っているため、ここでは位置だけ押さえておけば十分です。

順序を入れ替えたときの実行結果

express.json() の前後にルートを置き、エラーハンドラの後ろにもルートを置いて試しました。

import express from 'express';
const app = express();
app.post('/before', (req, res) => res.json({ body: req.body ?? 'undefined' }));
app.use(express.json());
app.post('/after', (req, res) => res.json({ body: req.body }));
app.use((err, req, res, next) => res.status(500).json({ error: err.message }));
app.get('/late', (req, res) => { throw new Error('route after error handler'); });
app.listen(7004);
POST /before 200 {"body":"undefined"}
POST /after 200 {"body":{"title":"x"}}
GET /late 500 <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Error</title> </head> <body> <pre>Internal Server Error</pre> </body> </html>
症状 原因 直し方
req.body が undefined ルートが express.json() より前にある/Content-Type が JSON でない app.use(express.json()) をルートより前へ。クライアントは Content-Type: application/json を付ける
自作エラーハンドラが呼ばれず HTML のエラーが返る エラーハンドラより後ろにルートがある エラーハンドラを一番最後に移す
自作エラーハンドラが呼ばれない(引数3つ) (err, req, res) のように引数が3つ 必ず4つ書く。next を使わなくても省略しない
404 ハンドラが全リクエストに反応する 404 ハンドラがルートより前にある ルートの後ろ、エラーハンドラの前へ

なお /late の HTML は NODE_ENV=production で実行した結果です。開発時(NODE_ENV 未設定)は、Express 既定のハンドラがスタックトレースを含む HTML を返します。

Express 4 と 5 の違い(比較表と実際のエラー)

Express 5 の大きな変更は「async 関数のエラーが自動でエラーハンドラに届く」「パスの書き方が厳密になった」「古い書き方のメソッドが削除された」の3点です。 公式の移行ガイドの内容を、両方の版を入れて実行した結果とあわせて表にしました。

項目 Express 4(4.22.3) Express 5(5.2.1)
async 関数で throw / reject エラーハンドラに届かない(Node 22 ではプロセスが終了) 自動でエラーハンドラへ(500 などで返る)
ワイルドカード /* 書ける。値は req.params[0](文字列) PathError で起動時に失敗。/*splat と名前を付け、値は配列
省略可能 :ext? 書ける PathError。{.:ext} のように波かっこで囲む
req.query への代入 代入できる getter のみ。代入で TypeError
JSON 以外の本文での req.body {} undefined
express.urlencoded の extended 既定 true false
express.static のドットファイル 配信する 既定で ignore
app.del() 非推奨の警告つきで動く 削除(app.delete() を使う)
res.json(obj, 201) 警告つきで 201 第2引数は無視され 200
res.send(200) 警告つきで OK を返す 数値 200 を本文として返す
res.redirect('back') 使える 削除。res.redirect(req.get('Referrer') || '/')
req.param(name) 使える 削除。req.params / req.body / req.query を使う
res.sendfile() 使える 削除。res.sendFile()
Node.js 0.10 以上 18 以上

実行して確認した Express 5 のエラー

両方の版をインストールし、同じコードを実行しました(ルート登録時の例外を try/catch で表示)。

express 5.2.1
app.get('/*') -> PathError: Missing parameter name at index 2: /*; visit https://git.new/pathToRegexpError for info
app.get('/:file.:ext?') -> PathError: Unexpected ? at index 11: /:file.:ext?; visit https://git.new/pathToRegexpError for info
app.get('/files/*splat') OK
app.get('/img/:file{.:ext}') OK
app.del('/x') -> TypeError: app.del is not a function
GET /files/a/b/c.txt 200 {"splat":["a","b","c.txt"]}
GET /img/logo.png 200 {"file":"logo","ext":"png"}
GET /img/logo 200 {"file":"logo"}
GET /async 500 {"handled":"boom from async"}
GET /query?x=1 200 {"error":"Cannot set property query of #<IncomingMessage> which has only a getter"}
POST /body 200 {"body":{"a":1}}
POST /body (text/plain) 200 {"body":"undefined"}
GET /jsonstatus 200 {"ok":1}
GET /sendnum 200 200

同じコードを Express 4.22.3 で動かした結果です(日時とファイルパスは省略)。

express 4.22.3
app.get('/*') OK
app.get('/:file.:ext?') OK
express deprecated app.del: Use app.delete instead
app.del('/x') OK
unhandledRejection: boom from async
GET /async no response ( AbortError )
GET /query?x=1 200 {"ok":true,"query":{"a":1}}
POST /body 200 {"body":{"a":1}}
POST /body (text/plain) 200 {"body":{}}
express deprecated res.json(obj, status): Use res.status(status).json(obj) instead
GET /jsonstatus 201 {"ok":1}
express deprecated res.send(status): Use res.sendStatus(status) instead
GET /sendnum 200 OK

Express 4 では async 関数の reject がどこにも届かず、レスポンスが返らないまま待ち続けました(テストでは 1.5 秒で打ち切り)。unhandledRejection のハンドラを置かない場合は、次のようにプロセスが終了します。

$ node crash4.mjs   # Express 4.22.3 + Node 22.22.2
{"0":"a/b/c.txt"}
./crash4.mjs:4
app.get('/tasks/:id', async (req, res) => { throw new Error('task not found'); });
                                                  ^

Error: task not found
    at ./crash4.mjs:4:51
    at Layer.handle [as handle_request] (.../express/lib/router/layer.js:95:5)
    ...
Node.js v22.22.2

1行目の {"0":"a/b/c.txt"} は、4 の /files/* で取れるワイルドカードの値です。5 では {"splat":["a","b","c.txt"]} と名前付きの配列になるので、パスとして使うときは req.params.splat.join('/') とします。

4 から 5 へ書き換える早見表

Express 4 の書き方 Express 5 の書き方
app.get('*', ...) / app.get('/*', ...) app.get('/*splat', ...)(ルートも含めるなら '/{*splat}')
req.params[0] req.params.splat.join('/')
'/:file.:ext?' '/:file{.:ext}'
promise.then(...).catch(next) async 関数で await するだけ
req.query.page = 1 const page = Number(req.query.page ?? 1) と別の変数に
app.del(...) app.delete(...)
res.json(obj, 201) res.status(201).json(obj)
res.send(204) res.sendStatus(204)
res.redirect(url, 301) res.redirect(301, url)
req.acceptsLanguage() など単数形 req.acceptsLanguages() など複数形

公式は自動書き換えツールも用意しています(npx codemod@latest @expressjs/v5-migration-recipe)。本記事ではこのツールは実行していません。 置き換え後は、上の比較表の項目を中心に動作を確かめると安心です。

静的ファイルとよく使う設定

express.static はフォルダをそのまま公開する組み込みミドルウェアです。 よく使うオプションをまとめます。

import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

// 起動場所に関係なく src/../public を指す
app.use(express.static(path.join(__dirname, '..', 'public'), {
  maxAge: '1h',          // Cache-Control: max-age=3600
  index: 'index.html',   // ディレクトリへのアクセス時に返すファイル
  dotfiles: 'ignore',    // Express 5 の既定。.well-known を出すなら 'allow'
}));

// URL の前に /static を付けて公開する
app.use('/static', express.static(path.join(__dirname, '..', 'assets')));
よくある疑問 答え
express.static('public') の基準はどこ? node を実行したディレクトリ。ずれるなら上のように絶対パスで指定
ESM で __dirname が使えない fileURLToPath(import.meta.url) で作る(Node 20.11 以降は import.meta.dirname も可)
.well-known/ が 404 になる Express 5 は既定でドットファイルを配信しない。dotfiles: 'allow' を指定
X-Powered-By: Express を消したい app.disable('x-powered-by')

つまずきやすいポイントのチェックリスト

チェック 確認方法
"type": "module" があるか import で SyntaxError: Cannot use import statement outside a module が出たらここ
相対 import に拡張子 .js を付けたか ESM では './app.js' と拡張子まで書く
express.json() がルートより前か req.body が undefined なら順序と Content-Type を確認
req.params.id を数値化したか '2' === 2 は false。Number() で変換
1つのリクエストで2回返していないか return res.status(400)... と return を付ける
エラーハンドラは引数4つ・最後か 呼ばれないときはまずこの2点
パスに /* や ? を使っていないか 5 では PathError。/*splat、{} に書き換え

return を付け忘れると、続く res.json が実行されて Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client が出ます。レスポンスを返す行には return を付ける習慣にすると防げます。

よくある質問

Q. これから学ぶなら Express 4 と 5 のどちらがよいですか? A. Express 5 がおすすめです。 npm i express で入るのは 5 系で、async 関数のエラーを自動で扱えるため、書くコードが少なくなります。4 系の記事を読むときは、本記事の「4 から 5 へ書き換える早見表」で読み替えてください。

Q. TypeScript で書けますか? A. 書けます。npm i -D typescript @types/express @types/node を入れ、Express 5 に対応した @types/express(5 系)を使います。本記事のコードは JavaScript(ESM)のみで実行しています。

Q. メモリ保存をデータベースに置き換えるには? A. store.js の関数を、同じ名前の async 関数に差し替えるだけで済むように作っています。ルート側は await store.list() のように await を付ければ、Express 5 では DB のエラーもそのままエラーハンドラに届きます。

Q. ポート番号を変えたいときは? A. PORT=3000 node src/server.js のように環境変数で渡します。server.js が process.env.PORT ?? 7000 を読む作りです。Node 20.6 以降なら node --env-file=.env src/server.js で .env ファイルからも読めます。

まとめ

  • Express の最新版は 5.2.1 で、Node.js 18 以上が必要。npm i express で 5 系が入る
  • ESM なら "type": "module" と、相対 import の拡張子 .js を忘れずに
  • CRUD API は express.json・Router・404・引数4つのエラーハンドラの組み合わせで作れる
  • ミドルウェアは書いた順に実行される。express.json はルートの前、エラーハンドラは最後
  • Express 5 では async 関数の throw が自動でエラーハンドラに届く(4 ではプロセスが終了した)
  • パスは /*splat と {.:ext}、req.query は読み取り専用、app.del などは削除済み

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Node.js Express入門|Express5対応の最小APIをコピペで作る」

株式会社RJCでは、Node.js と Express を使った API づくりを仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?