この内容は古いバージョンです。最新バージョンを表示するには、戻るボタンを押してください。
バージョン:4
ページ更新者:guest
更新日時:2026-06-11 07:07:02

タイトル: ExpressとEJSを使用した簡単なアプリの作成
SEOタイトル: Express + EJS で作る簡単な Web アプリ(views / ルーティング / フォーム / 本番デプロイ)

この本文には実行可能なタグ等が含まれるため、安全のためHTMLソースとして表示しています。本文データは変更していません。

<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;background:#fffbea;border:1px solid #f4c430;margin:1em 0;width:100%"><tr><td style="background:#f4c430;color:#000;font-weight:bold;padding:8px">この記事の要点</td></tr><tr><td style="padding:10px"><ul><li><strong>Express</strong> = Node.js の最有力 Web フレームワーク、<strong>EJS</strong> = HTML テンプレートエンジン</li><li>初期化: <code>npm init -y</code> → <code>npm install express ejs</code></li><li><code>app.set("view engine", "ejs")</code> + <code>views/</code> ディレクトリで自動解決</li><li><code>res.render("index", { name: "Taro" })</code> でテンプレートに変数を渡す</li><li>本番は <code>PM2</code> でデーモン化、Nginx をリバプロに置く構成が定番</li></ul></td></tr></table>
<h2>Express + EJS とは</h2>

<p><strong>Express</strong> は Node.js 上で動く最も普及した Web アプリケーションフレームワークです。HTTP ルーティング・ミドルウェア・テンプレート連携などの最小機能だけを提供する「薄い」フレームワークで、フロント主流時代でも API サーバや SSR の土台として現役です。</p>

<p><strong>EJS(Embedded JavaScript Templates)</strong>は、HTML に JavaScript を埋め込めるシンプルなテンプレートエンジン。<code><%= name %></code> のように Erb/JSP 系の構文で、サーバ側でレンダリングします。学習コストが低く、Express の標準的なお供として広く使われています。</p>

<h2>プロジェクト作成</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-bash"># 1. ディレクトリ作成
mkdir myapp && cd myapp

# 2. package.json 作成
npm init -y

# 3. 依存追加
npm install express ejs

# 4. 開発用にホットリロード(任意)
npm install --save-dev nodemon</code></pre>
<h3>ファイル構成</h3>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-text">myapp/
├── package.json
├── app.js              ← エントリポイント
├── public/             ← 静的ファイル (CSS, JS, 画像)
│   └── css/style.css
├── views/              ← EJS テンプレート
│   ├── index.ejs
│   ├── about.ejs
│   └── partials/
│       ├── header.ejs
│       └── footer.ejs
└── routes/             ← ルート定義 (規模が大きくなったら分割)
    └── users.js</code></pre>
<h2>最小の app.js</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-javascript">// app.js
const express = require(&apos;express&apos;);
const path = require(&apos;path&apos;);
const app = express();

// テンプレートエンジン: EJS
app.set(&apos;view engine&apos;, &apos;ejs&apos;);
app.set(&apos;views&apos;, path.join(__dirname, &apos;views&apos;));

// 静的ファイル
app.use(express.static(path.join(__dirname, &apos;public&apos;)));

// フォーム解析ミドルウェア
app.use(express.urlencoded({ extended: true }));
app.use(express.json());

// ルーティング
app.get(&apos;/&apos;, (req, res) => {
  res.render(&apos;index&apos;, { title: &apos;Home&apos;, name: &apos;Taro&apos; });
});

app.get(&apos;/about&apos;, (req, res) => {
  res.render(&apos;about&apos;, { title: &apos;About&apos; });
});

// 起動
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`http://localhost:${PORT} で待機中`);
});</code></pre>
<h2>EJS テンプレート例</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><%# views/index.ejs %>
<%- include(&apos;partials/header&apos;, { title: title }) %>

<main>
  <h1>こんにちは、<%= name %> さん</h1>

  <% if (name === &apos;Taro&apos;) { %>
    <p>管理者です。</p>
  <% } else { %>
    <p>一般ユーザです。</p>
  <% } %>

  <ul>
  <% [&apos;apple&apos;, &apos;banana&apos;, &apos;cherry&apos;].forEach(fruit => { %>
    <li><%= fruit %></li>
  <% }) %>
  </ul>

  <p>生 HTML をそのまま出す(XSS 注意): <%- &apos;<strong>太字</strong>&apos; %></p>
</main>

<%- include(&apos;partials/footer&apos;) %></code></pre>
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%">
<thead><tr style="background:#e3f2fd"><th>タグ</th><th>用途</th></tr></thead>
<tbody>
<tr><td><code><%= value %></code></td><td>変数を<strong>エスケープして</strong>出力(XSS 防止)</td></tr>
<tr><td><code><%- value %></code></td><td>生のまま HTML 出力(信頼できる値のみ)</td></tr>
<tr><td><code><% if () { %> ... <% } %></code></td><td>JS 制御構文(出力しない)</td></tr>
<tr><td><code><%# コメント %></code></td><td>テンプレート内コメント</td></tr>
<tr><td><code><%- include('partial') %></code></td><td>他テンプレート読込</td></tr>
</tbody>
</table>

<h2>パーシャル(共通ヘッダ・フッタ)</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><%# views/partials/header.ejs %>
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title><%= title %></title>
  <link rel="stylesheet" href="/css/style.css">
</head>
<body>
  <nav>
    <a href="/">Home</a> | <a href="/about">About</a>
  </nav>

<%# views/partials/footer.ejs %>
  <footer>&copy; 2026 MyApp</footer>
</body>
</html></code></pre>
<h2>フォーム POST の処理</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-javascript">// app.js に追加
app.get(&apos;/contact&apos;, (req, res) => {
  res.render(&apos;contact&apos;, { title: &apos;Contact&apos;, message: null });
});

app.post(&apos;/contact&apos;, (req, res) => {
  const { name, email, body } = req.body;

  // 簡易バリデーション
  if (!name || !email) {
    return res.render(&apos;contact&apos;, {
      title: &apos;Contact&apos;,
      message: { type: &apos;error&apos;, text: &apos;名前とメールは必須です&apos; },
    });
  }

  // DB 保存などの処理
  console.log(&apos;Received&apos;, { name, email, body });

  // PRG パターン: リダイレクト
  res.redirect(&apos;/contact?ok=1&apos;);
});</code></pre><pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><%# views/contact.ejs %>
<%- include(&apos;partials/header&apos;, { title: title }) %>

<h1>お問い合わせ</h1>

<% if (message) { %>
  <p class="<%= message.type %>"><%= message.text %></p>
<% } %>

<% if (typeof query !== &apos;undefined&apos; && query.ok) { %>
  <p class="success">送信しました。</p>
<% } %>

<form method="POST" action="/contact">
  <label>名前 <input name="name" required></label>
  <label>メール <input name="email" type="email" required></label>
  <label>本文 <textarea name="body"></textarea></label>
  <button type="submit">送信</button>
</form>

<%- include(&apos;partials/footer&apos;) %></code></pre>
<h2>ルーティングを別ファイルに分割</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-javascript">// routes/users.js
const express = require(&apos;express&apos;);
const router = express.Router();

router.get(&apos;/&apos;, (req, res) => {
  res.render(&apos;users/list&apos;, { users: [&apos;Alice&apos;, &apos;Bob&apos;] });
});

router.get(&apos;/:id&apos;, (req, res) => {
  res.render(&apos;users/show&apos;, { id: req.params.id });
});

module.exports = router;

// app.js
const usersRouter = require(&apos;./routes/users&apos;);
app.use(&apos;/users&apos;, usersRouter);
// → /users → list, /users/123 → show</code></pre>
<h2>開発: ホットリロード</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-json">// package.json
{
  "scripts": {
    "start": "node app.js",
    "dev": "nodemon app.js"
  }
}</code></pre><pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-bash">npm run dev
# → ファイル変更で自動再起動</code></pre>
<h2>本番デプロイ</h2>

<h3>PM2 でデーモン化</h3>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-bash">npm install -g pm2

# 起動
pm2 start app.js --name myapp -i max     # CPU コア数だけクラスタ起動

# 再起動・停止
pm2 restart myapp
pm2 stop myapp
pm2 logs myapp

# 起動時自動起動
pm2 startup
pm2 save</code></pre>
<h3>Nginx リバースプロキシ</h3>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-nginx"># /etc/nginx/sites-available/myapp
server {
  listen 80;
  server_name example.com;

  location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection &apos;upgrade&apos;;
    proxy_set_header Host $host;
    proxy_cache_bypass $http_upgrade;
  }

  # 静的ファイルは Nginx 直配信(高速化)
  location /public/ {
    alias /var/www/myapp/public/;
    expires 30d;
  }
}</code></pre>
<h3>Dockerfile</h3>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-dockerfile">FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "app.js"]</code></pre>
<h2>セキュリティのチェックポイント</h2>

<ul>
<li><code>helmet</code> ミドルウェアでセキュリティ HTTP ヘッダ自動付与 (<code>npm install helmet</code> → <code>app.use(helmet())</code>)</li>
<li><code>express-rate-limit</code> でブルートフォース防止</li>
<li>セッションは <code>express-session</code> + Redis Store(本番)</li>
<li>CSRF は <code>csurf</code>(非推奨化済→<code>csrf-csrf</code> 等)で対策</li>
<li>EJS は <code><%= %></code>(エスケープ)を基本とし、<code><%- %></code> は信頼データのみ</li>
</ul>

<h2>FAQ</h2>

<p><strong>Q: EJS と Pug、どちらを選ぶべき?</strong><br />
A: HTML をそのまま書きたいなら EJS、インデントベースの簡潔さなら Pug。チーム経験で選んで OK。新規は EJS が無難。</p>

<p><strong>Q: SSR と SPA、どちらに向く?</strong><br />
A: Express + EJS は SSR 寄り。SPA なら Next.js / Nuxt / SvelteKit を直接使う方が現代的です。Express は API サーバとして残せます。</p>

<p><strong>Q: 大規模化したらどうする?</strong><br />
A: <code>routes/</code> 分割、<code>controllers/</code> 切り出し、ORM (Prisma / Sequelize) 採用、TypeScript 化、を順番に。さらに大規模なら NestJS への移行も検討。</p>