5.

Express + EJS で作る簡単な Web アプリ(views / ルーティング / フォーム / 本番デプロイ)

編集
この記事の要点
  • Express = Node.js の最有力 Web フレームワーク、EJS = HTML テンプレートエンジン
  • 初期化: npm init -ynpm install express ejs
  • app.set("view engine", "ejs") + views/ ディレクトリで自動解決
  • res.render("index", { name: "Taro" }) でテンプレートに変数を渡す
  • 本番は PM2 でデーモン化、Nginx をリバプロに置く構成が定番

Express + EJS とは

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

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

プロジェクト作成

# 1. ディレクトリ作成
mkdir myapp && cd myapp

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

# 3. 依存追加
npm install express ejs

# 4. 開発用にホットリロード(任意)
npm install --save-dev nodemon

ファイル構成

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

最小の app.js

// app.js
const express = require('express');
const path = require('path');
const app = express();

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

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

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

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

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

// 起動
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`http://localhost:${PORT} で待機中`);
});

EJS テンプレート例

<%# views/index.ejs %>
<%- include('partials/header', { title: title }) %>

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

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

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

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

<%- include('partials/footer') %>
タグ用途
<%= value %>変数をエスケープして出力(XSS 防止)
<%- value %>生のまま HTML 出力(信頼できる値のみ)
<% if () { %> ... <% } %>JS 制御構文(出力しない)
<%# コメント %>テンプレート内コメント
<%- include('partial') %>他テンプレート読込

パーシャル(共通ヘッダ・フッタ)

<%# 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>© 2026 MyApp</footer>
</body>
</html>

フォーム POST の処理

// app.js に追加
app.get('/contact', (req, res) => {
  res.render('contact', { title: 'Contact', message: null });
});

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

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

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

  // PRG パターン: リダイレクト
  res.redirect('/contact?ok=1');
});
<%# views/contact.ejs %>
<%- include('partials/header', { title: title }) %>

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

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

<% if (typeof query !== 'undefined' && 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('partials/footer') %>

ルーティングを別ファイルに分割

// routes/users.js
const express = require('express');
const router = express.Router();

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

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

module.exports = router;

// app.js
const usersRouter = require('./routes/users');
app.use('/users', usersRouter);
// → /users → list, /users/123 → show

開発: ホットリロード

// package.json
{
  "scripts": {
    "start": "node app.js",
    "dev": "nodemon app.js"
  }
}
npm run dev
# → ファイル変更で自動再起動

本番デプロイ

PM2 でデーモン化

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

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 'upgrade';
    proxy_set_header Host $host;
    proxy_cache_bypass $http_upgrade;
  }

  # 静的ファイルは Nginx 直配信(高速化)
  location /public/ {
    alias /var/www/myapp/public/;
    expires 30d;
  }
}

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"]

セキュリティのチェックポイント

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

FAQ

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

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

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

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. インストール方法(Windows編)
  2. インストール方法(CentOS編)
  3. クイックスタート
  4. 簡単なサーバー構築と起動方法
  5. ExpressとEJSを使用した簡単なアプリの作成

最近更新/作成されたページ