【JavaScript入門】ローカルサーバーの構築

 ここでは、シングルページアプリケーション「みんなのつぶやき」を動かすためのローカルサーバーを Node.js と Express、Multer で構築する手順を解説します。
 Node.js のインストールは事前に完了しているものとし(「Node.jsのインストール」を参照)、以降は以下の流れで進めます。

【JavaScript入門】開発環境の準備:Node.jsのインストール

このページで解説している内容は、以下の YouTube 動画の解説で見ることができます。 【JavaScript入門】開発環境の準備:Node.jsのインストール 開発環境の準備:Node.js…

  1. プロジェクトディレクトリの準備
  2. 必要ライブラリ(Multer)のインストール
  3. index.js の作成と解説
  4. サーバー起動と動作確認

ディレクトリ構成

プロジェクトルートを node-js とした場合の最終的な構成は以下の通りです。

プロジェクトディレクトリ直下に、以下のディレクトリとテキストファイルを作成します。

  • upload_images:アップロード画像格納ディレクトリ
  • post-dat.txt:投稿情報保存用テキストファイル(空ファイル)
node-js/app
   │   ├─ index.html
   │   ├─ style.css
   │   └─ js/
   │        ├─ common.js
   │        ├─ storage.js
   │        ├─ imageArea.js
   │        ├─ imageEffects.js
   │        ├─ controlArea.js
   │        ├─ commentArea.js
   │        ├─ sendArea.js
   │        ├─ dateFormat.js
   │        └─ postListArea.js
   ├─ index.js      ※サーバー起動スクリプト
   ├─ upload_images ※アップロード画像格納ディレクトリ
   └─ post-dat.txt  ※投稿情報保存用テキストファイル(空ファイル)

upload_images と post-dat.txt の役割

  • upload_images/
    Multer を使ってクライアントから受け取った画像ファイルを保存するディレクトリ。ブラウザから投稿されたバイナリデータ(画像)はここに格納されます。
  • post-dat.txt
    (あるいは後述のサンプルでは JSON ファイル posts.json
    投稿されたコメントと画像ファイル名、タイムスタンプ、いいね数などを保持するテキストデータ。サーバー起動時に読み込み、更新を行って永続化します。

プロジェクトディレクトリへの移動

Windows 環境でデスクトップ上に node-js を作成した場合、以下のコマンドで移動します。

cd C:\Users\<ユーザー名>\Desktop\node-js

Multer のインストール

Multer とは?

 Multer は Express 向けのミドルウェアで、multipart/form-data(ファイルアップロード)を扱いやすくするためのライブラリです。

  • アップロードされたファイルを一時的に保存
  • req.filesreq.file でファイル情報にアクセス

インストール手順

 PowerShell の実行ポリシーを一時的に緩和し(スコープはプロセスのみ)、Multer をプロジェクトに追加します。

# スクリプト実行ポリシーの設定
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process

# Multer のインストール
npm install multer

index.js の作成

 サーバー起動用のエントリポイント index.js を以下のように作成します。コメントを付加して可読性を高め、主要な処理の流れを追いやすくしています。

/**
 * index.js
 *
 * Express と Multer を用いて、
 * 1) コメント&画像の投稿機能
 * 2) 投稿一覧取得 (/get)
 * 3) 「いいね!」機能 (/like/:id)
 * を実装したサーバーです。
 */

const express = require('express');           // Web フレームワーク
const fs      = require('fs');                // ファイル操作
const path    = require('path');              // パス操作ユーティリティ
const multer  = require('multer');            // ファイルアップロード

const app = express();

// ========== 設定値 ==========
const PORT        = 3000;
const UPLOAD_DIR  = path.resolve(__dirname, 'upload_images');
const DATA_FILE   = path.resolve(__dirname, 'posts.json');

// upload_images ディレクトリがなければ作成
if (!fs.existsSync(UPLOAD_DIR)) {
  fs.mkdirSync(UPLOAD_DIR, { recursive: true });
}

// posts.json がなければ空配列で初期化
if (!fs.existsSync(DATA_FILE)) {
  fs.writeFileSync(DATA_FILE, JSON.stringify([], null, 2), 'utf8');
}

// ========== ミドルウェア ==========
app.use(express.json());                      // JSON ボディのパース
app.use(express.urlencoded({ extended: true })); // URL エンコードボディのパース
app.use('/app', express.static(path.resolve(__dirname, 'app'))); // 静的ファイル
app.use('/upload_images', express.static(UPLOAD_DIR));

// ========== Multer 初期化 ==========
const upload = multer({ dest: UPLOAD_DIR });

// ========== ヘルスチェック ==========
app.get('/', (req, res) => {
  res.send('Hello World');
});

// ========== 全投稿取得 ==========
app.get('/get', (req, res) => {
  try {
    const posts = JSON.parse(fs.readFileSync(DATA_FILE, 'utf8'));
    posts.sort((a, b) => b.date - a.date);    // 新しい順
    res.json({ list: posts });
  } catch (err) {
    console.error('GET /get error:', err);
    res.status(500).json({ error: '内部サーバーエラー' });
  }
});

// ========== 投稿受付 ==========
app.post('/post', upload.any(), (req, res) => {
  console.log('--- POST /post ---', req.body, req.files);

  const newPost = {
    id:      Date.now().toString(),           // 一意 ID
    comment: req.body.comment || '',
    dir:     'upload_images/',
    image:   req.files[0]?.filename || '',
    date:    Date.now(),
    likes:   0
  };

  try {
    const posts = JSON.parse(fs.readFileSync(DATA_FILE, 'utf8'));
    posts.push(newPost);
    fs.writeFileSync(DATA_FILE, JSON.stringify(posts, null, 2), 'utf8');
    console.log('Saved new post:', newPost);
    res.json({ status: 'success', post: newPost });
  } catch (err) {
    console.error('POST /post error:', err);
    res.status(500).json({ error: '投稿の保存に失敗しました' });
  }
});

// ========== いいね!API ==========
app.post('/like/:id', (req, res) => {
  const id = req.params.id;
  try {
    const posts = JSON.parse(fs.readFileSync(DATA_FILE, 'utf8'));
    const post  = posts.find(p => p.id === id);
    if (!post) {
      return res.status(404).json({ error: '投稿が見つかりません' });
    }
    post.likes = (parseInt(post.likes, 10) || 0) + 1;
    fs.writeFileSync(DATA_FILE, JSON.stringify(posts, null, 2), 'utf8');
    res.json({ likes: post.likes });
  } catch (err) {
    console.error('POST /like error:', err);
    res.status(500).json({ error: 'いいね!の更新に失敗しました' });
  }
});

// ========== サーバ起動 ==========
app.listen(PORT, () => {
  console.log(`Server started at http://localhost:${PORT}`);
});

プログラム全体の流れ

  1. 設定値読み込み
    保存先ディレクトリ・データファイルのパスを定義し、存在がなければ初期化。
  2. ミドルウェア登録
    JSON/URL エンコードデータのパース、静的ファイルの配信パスを設定。
  3. Multer 初期化
    ファイルアップロード先として upload_images を指定。
  4. 各種エンドポイント実装
    /:ヘルスチェック
    /get:全投稿データの取得
    /post:投稿受付(画像+コメント)
    /like/:id:いいね数カウントアップ
  5. サーバ起動
    指定ポートでリクエストを待ち受け。

主要命令の解説

命令・関数説明
require('express')Express モジュールの読み込み
express.json()JSON ボディを req.body にパース
express.urlencoded({extended:true})フォーム送信データをパース(ネストされたデータ対応)
express.static()静的ファイル配信用ミドルウェア
multer({ dest: ... })アップロード先を設定した Multer のインスタンス生成
fs.existsSync(path)指定パスの存在確認
fs.mkdirSync(path, {recursive})ディレクトリ作成(recursive で親ディレクトリも同時に作成可能)
fs.readFileSync(path, 'utf8')ファイルを同期的に読み込み
fs.writeFileSync(path, data, 'utf8')ファイルを同期的に書き込み
app.get(path, handler)HTTP GET リクエストのルーティング
app.post(path, handler)HTTP POST リクエストのルーティング
app.listen(PORT, callback)指定ポートでサーバ起動、起動完了後にコールバック実行

サーバーの起動と動作確認

1.ターミナルでプロジェクトルート(node-js)に移動

2.以下コマンドでサーバーを起動

node .

3.ブラウザで http://localhost:3000 にアクセスし、Hello World と表示されれば正常に起動完了

http://localhost:3000 

4.サーバーを停止するにはターミナルで Ctrl + C

 次回は、index.htmlstyle.css を詳しく見ながら、画面レイアウトの実装内容を解説していきます。