0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Vue3・Nuxt4のためにNode.jsの基本を整理してみる

0
Posted at

Vue3やNuxt4で画面を作る学習を進めていると、npm installnpm run devserver/apiなどでNode.jsが出てきます。
最初は「JavaScriptはブラウザで動くものでは?」と思っていましたが、Nuxt4ではAPIやサーバー側の処理にもJavaScript・Node.jsが関わりそうなので、基本を整理してみます。

今回は、画像解析結果をブラウザへ表示するためのAPIや、解析結果ファイルを扱う場面をイメージしながら、Node.jsの基本をまとめます。

まだ途中段階なので、勘違いなどあれば教えていただけるとうれしいです。

今日やったこと

Node.jsの役割、モジュール、ファイル操作、非同期処理、Nuxt4のサーバー側をざっくり整理しました。

Node.jsは、JavaScriptをブラウザの外でも実行できるランタイム環境です。サーバー、Webアプリ、コマンドラインツール、スクリプトなどを作れるため、画像解析システムでは「解析結果のJSONを読む」「ブラウザからの要求に応じて結果を返す」といった用途につながりそうです。(Node.js公式:ファイルの読み込み) nodejs

image.avif 「フロントエンドのために始めたJavaScriptが、裏側のデータを返すところにもつながるのは少し面白い。」

Nuxt4ではserver/ディレクトリにサーバー側のコードを置けます。server/api/にはAPIルート、server/routes/にはサーバールート、server/utils/にはサーバー側で使う関数を置く構成になっています。(Nuxt4公式:Directory Structure) nuxt

Node.jsとは

Node.jsは、JavaScriptをブラウザではなくPCやサーバー上で動かすための環境です。

ブラウザ側のJavaScriptでは、ボタンのクリック、入力フォーム、画面の表示切り替えなどを扱います。一方でNode.jsでは、ファイルの読み書き、データベースとの接続、APIの作成、コマンドの実行など、サーバー側の処理を扱えそうです。

場所 主な役割 画像解析結果の利用イメージ
ブラウザ・Vue3 画面表示とユーザー操作 一覧表示、検索、詳細表示
Node.js・Nuxt4サーバー データや処理をブラウザへ渡す JSONを読み、解析結果APIとして返す
画像解析処理側 画像を解析して結果を作る 画像・判定・検出件数を出力する

今の理解では、Node.jsは画像解析そのものを必ず担当するものではなく、解析済みのデータとブラウザ画面をつなぐ役割として使えそうです。

npmとpackage.json

Node.jsのプロジェクトでは、パッケージのインストールやコマンド実行にnpmを使います。

package.jsonは、プロジェクトで使うパッケージや実行コマンドの情報を持つ設定ファイルです。Nuxt4プロジェクトを作ると最初から存在するため、今は自分で最初から書くより、何が書かれているか読むところから始めたいです。

{
  "scripts": {
    "dev": "nuxt dev",
    "build": "nuxt build",
    "preview": "nuxt preview"
  }
}

この例では、scriptsにコマンドの別名を登録しています。

# 開発用サーバーを起動する。
# Nuxtの画面をブラウザで確認しながら開発するときに使う。
npm run dev

# 本番用のビルドを作成する。
# 実際に公開できる形へ変換できるかを確認するときに使う。
npm run build

# ビルドした内容をローカル環境で確認する。
npm run preview

npm installpackage.jsonに書かれたパッケージをインストールし、npm install パッケージ名は新しいパッケージを追加するために使います。パッケージの役割をよく分からないまま増やすと追いにくくなりそうなので、今はNuxt4で必要になったものを1つずつ確認したいです。

モジュールとimport

Node.jsでは、Node.js本体が持つ機能や、自分で作った関数をimportで読み込んで使います。

たとえば、ファイルを操作するnode:fs/promisesはNode.jsの標準モジュールです。node:という接頭辞があると、npmで追加した外部パッケージではなく、Node.jsに用意された機能だと判断しやすそうです。

// Node.js標準のファイル操作モジュールを読み込む。
// promises版を使うと、awaitと組み合わせて非同期でファイルを扱える。
import { readFile } from 'node:fs/promises'

// パスを安全に組み立てるNode.js標準モジュール。
import path from 'node:path'

// このファイルが置かれている場所を基準に、JSONファイルの場所を作る。
// 実際の配置はプロジェクト構成に合わせて確認したい。
const filePath = path.join(process.cwd(), 'data', 'analysis-results.json')

// ファイルを文字列として読み込む関数。
const loadResultsFile = async () => {
  // utf8を指定すると、JSONファイルの内容を文字列として受け取れる。
  const text = await readFile(filePath, 'utf8')

  // JSON文字列をJavaScriptの配列やオブジェクトへ変換して返す。
  return JSON.parse(text)
}

Node.jsではfs.readFile()、同期版のfs.readFileSync()、Promise版のfsPromises.readFile()などでファイルを読み込めます。fs/promisesを使うとawaitと組み合わせられます。(Node.js公式:ファイルの読み込み) nodejs

非同期処理

ファイルの読み込み、データベースへの問い合わせ、外部API通信は、すぐに終わるとは限りません。Node.jsでは、その完了を待つためにPromiseasyncawaitがよく出てきそうです。

import { readFile } from 'node:fs/promises'

// async を付けると、関数の中で await を使える。
const loadResultsFile = async () => {
  try {
    // ファイルの読み込みが終わるまで待つ。
    const text = await readFile('./data/analysis-results.json', 'utf8')

    // 文字列のJSONを、JavaScriptで扱える値へ変換する。
    const results = JSON.parse(text)

    return results
  } catch (error) {
    // ファイルがない、JSON形式が正しくない、といった失敗時にここへ入る。
    // 今後はエラー内容をAPIの応答へどう返すかも確認したい。
    console.error('解析結果を読み込めませんでした', error)

    // 呼び出し元が失敗を扱えるように、エラーを投げ直す。
    throw error
  }
}

Node.jsは、デフォルトでは単一のJavaScriptスレッドを使いながら、イベントループによってノンブロッキングI/Oを実行できます。今は「ファイル待ちや通信待ちの間に、処理全体を止めにくくする仕組みがある」程度に理解しておきたいです。(Node.js公式:Event Loop) nodejs

image.avif awaitは“待つ”と書いてあるのに、サーバー全体を止めるわけではないところが、まだ少し不思議。」

process.envと環境変数

パスワード、APIキー、接続先URLのような情報をコードへ直接書くと、Gitへ誤って登録してしまう可能性があります。Node.jsでは環境変数をprocess.envから読み取れます。

// 環境変数からAPIの接続先を取得する。
// 値が設定されていない場合は、ローカル開発用のURLを使う例。
const apiBaseUrl = process.env.API_BASE_URL || 'http://localhost:3000'

console.log(apiBaseUrl)

ただし、Nuxt4ではprocess.envを画面側へそのまま使うより、runtimeConfigを使う場面がありそうです。秘密の値をクライアント側へ渡さない設定を含め、実際に外部サービスやデータベースへ接続するときに確認したいです。

// nuxt.config.ts

export default defineNuxtConfig({
  runtimeConfig: {
    // サーバー側だけで使う値。
    // 実際の値は環境変数から設定する想定。
    analysisApiToken: '',

    public: {
      // ブラウザ側へ公開してもよい値だけを置く。
      // ここへ秘密のトークンは置かない。
      analysisApiBase: ''
    }
  }
})

クラスも触ってみる

Node.jsでもJavaScriptのクラスを使えます。クラスは、関連するデータと処理をまとめる書き方です。

たとえば、解析結果ファイルを読む処理を1つにまとめるなら、次のような書き方ができます。

import { readFile } from 'node:fs/promises'

// 解析結果を読むためのデータと処理をまとめるクラス。
// ファイル操作を1か所へ集める場合に使えそう。
class AnalysisResultRepository {
  constructor(filePath) {
    // 読み込むJSONファイルの場所を、インスタンスごとに持つ。
    this.filePath = filePath
  }

  // ファイルからJSONを読み、JavaScriptの値として返すメソッド。
  async findAll() {
    // ファイル読み込みが終わるまで待つ。
    const text = await readFile(this.filePath, 'utf8')

    // JSON文字列を配列やオブジェクトに変換して返す。
    return JSON.parse(text)
  }
}

// new で、特定のファイルを読むためのインスタンスを作る。
const repository = new AnalysisResultRepository(
  './data/analysis-results.json'
)

// 非同期の読み込み結果を受け取る。
const results = await repository.findAll()

console.log(results)

ただ、Nuxt4で小さなAPIを作る最初の段階では、関数として分けるだけでも十分そうです。クラスは「データと処理をまとめられる書き方」と知りつつ、実際に同じ種類の処理が増えてきたときに検討したいです。

import { readFile } from 'node:fs/promises'

// ファイルを受け取り、解析結果を返す関数。
// 小さな処理なら、まずは関数のほうが読みやすい場合もありそう。
export const loadAnalysisResults = async (filePath) => {
  const text = await readFile(filePath, 'utf8')

  return JSON.parse(text)
}

つまずいたところ

ブラウザで動くJavaScriptと、Node.jsで動くJavaScriptでは、同じ書き方でも使える機能が違うところが混乱しそうです。

ブラウザとNode.jsの違い

ブラウザではwindowdocumentを使って画面を操作できますが、Node.jsには通常のブラウザ画面がありません。そのため、Node.jsではHTML要素を直接操作する代わりに、ファイル、通信、データベースなどを扱うことになりそうです。

image.avif 「どちらもJavaScriptなのに、画面を触る側とファイルを読む側で、使える道具が違うんだね。」
項目 ブラウザ側 Node.js側
主な役割 画面表示とユーザー操作 ファイル・API・データ処理
代表的な機能 document、イベント処理 fsprocess、HTTP処理
Vue・Nuxtでの場所 .vueapp/ server/、APIルート
画像解析結果の例 一覧、検索、詳細画面 結果JSONの読み込み、API応答

Nuxt4ではserver/がサーバー側コードの置き場であり、APIはserver/api/へ置けます。(Nuxt4公式:server directory) nuxt

同期処理と非同期処理

readFileSyncのような同期処理は、読み込みが完了するまで次の処理へ進みません。小さなスクリプトでは扱いやすい場合もありそうですが、Webサーバーで大きな処理を同期的に行うと、ほかのリクエストへの対応を待たせる可能性があります。

今はNuxt4のサーバーAPIでは、fs/promisesasyncawaitを使う書き方から始めたいです。ファイル全体を一度にメモリへ読み込むreadFileは、大きいファイルではストリームのほうがメモリ効率がよい場合もあるため、大量データを扱う段階で確認したいです。(Node.js公式:ファイルの読み込み) nodejs

Nuxt4で使う入口

Nuxt4では、server/api/へファイルを置くとAPIルートとして使えます。ここでは、解析結果の仮データを返す最小の例だけを確認します。

// server/api/analysis-results.get.ts

// GET /api/analysis-results へのアクセス時に実行される想定。
// 今はファイル読込ではなく、APIの形を確認するために仮データを返す。
export default defineEventHandler(() => {
  return [
    {
      id: 1,
      imageName: '画像001',
      detectionCount: 3,
      status: '確認が必要'
    },
    {
      id: 2,
      imageName: '画像002',
      detectionCount: 0,
      status: '問題なし'
    }
  ]
})

このファイルを置くと、今の理解ではブラウザ側から/api/analysis-resultsへアクセスして、解析結果の一覧を受け取る流れを作れそうです。Nuxtのserver/ディレクトリはアプリケーションへAPIやサーバーハンドラーを登録するために使われます。(Nuxt4公式:server directory) nuxt

次に、Vue3・Nuxt4のページ側でデータを表示するイメージです。

<script setup>
// Nuxtの useFetch で、サーバーAPIから解析結果を取得する。
// 実際に先ほどのAPIを作った場合に試したい。
const { data: results, pending, error } = await useFetch(
  '/api/analysis-results'
)
</script>

<template>
  <!-- 読み込み中は待機メッセージを表示する -->
  <p v-if="pending">解析結果を読み込み中です。</p>

  <!-- 取得エラー時には、画面上で失敗を伝える -->
  <p v-else-if="error">解析結果を取得できませんでした。</p>

  <!-- 成功時には、結果を一覧表示する -->
  <ul v-else>
    <!-- :key には各結果を識別できるIDを使う -->
    <li v-for="result in results" :key="result.id">
      {{ result.imageName }}:{{ result.detectionCount }}件
    </li>
  </ul>
</template>

NuxtのuseFetchは、APIエンドポイントからデータを取得するSSR対応のcomposableです。実際にはAPIの返却形式やエラー処理も確認しながら使いたいです。(Nuxt4公式:useFetch) nuxt

今の理解(メモ)

まだふわっとしていますが、Node.jsはブラウザ画面とデータ・ファイル・APIをつなぐための土台に見えています。

  • Node.jsは、ブラウザの外でJavaScriptを動かす環境
  • ブラウザ側では画面操作、Node.js側ではファイルやAPIなどを扱う
  • npmはパッケージのインストールやプロジェクトのコマンド実行に使う
  • package.jsonには使うパッケージやnpm run devなどのスクリプトが書かれている
  • importでNode.js標準モジュールや自作関数を読み込む
  • node:fs/promisesでは、ファイルを非同期で読み書きできる
  • asyncawaitは、ファイルや通信の完了を待つ処理で使う
  • trycatchで、ファイルがない・JSONが壊れているといった失敗を扱う
  • process.envは環境変数を読むためのもの。秘密情報をコードへ直接書かないために使う
  • クラスはNode.jsでも使えるが、小さな処理は関数で始めるほうが読みやすそう
  • Nuxt4ではserver/api/にAPIルートを置き、Vue側のuseFetchから取得できそう

次にやりたいこと

Node.jsとNuxt4のサーバー側を、画像解析結果の小さなAPIで試してみたいです。

  • node:fs/promisesでJSONファイルを読み込み、console.logで配列として扱えるか確認する
  • trycatchを付け、ファイルが存在しない場合のエラーを確認する
  • server/api/analysis-results.get.tsを作り、仮の解析結果をJSONとして返す
  • useFetchでAPIを呼び出し、Nuxt4の一覧画面へ解析結果を表示する
  • 検出件数がある結果だけを、サーバー側または画面側でfilterして表示する
  • 環境変数とruntimeConfigを使い、接続先URLやトークンをコードに直接書かない形を試す

今日の整理

Node.jsとNuxt4で出てきそうなものを、後から見返せるようにまとめます。

分類 書き方・場所 役割 利用イメージ
実行環境 Node.js ブラウザ外でJavaScriptを実行する API、ファイル操作、スクリプト
パッケージ npm パッケージとコマンドを管理する npm installnpm run dev
設定 package.json パッケージ・実行コマンドを定義する Nuxt開発・ビルド設定
モジュール import 外部の機能を読み込む node:fs/promises
ファイル操作 readFile ファイルを読み込む 解析結果JSONを読む
非同期処理 asyncawait 完了を待つ処理を書く ファイル、通信、DBアクセス
例外処理 trycatch 失敗時の処理を書く ファイルなし、JSONエラー
環境変数 process.env 実行環境ごとの値を読む 接続先URL、秘密情報
クラス classnew データと処理をまとめる ファイル読込処理の整理
Nuxt4サーバー server/api/ APIルートを置く 解析結果をJSONで返す
Nuxt4画面 useFetch APIからデータを取得する 解析結果の一覧表示

おわりに

今回は、Vue3・Nuxt4を触るときに出てきそうなNode.jsの基本を、ファイル操作やAPIのイメージと一緒に整理してみました。まだNode.jsだけでサーバーを作れる段階ではありませんが、ブラウザ側の一覧画面と、解析結果を返す側の処理が少しつながって見えてきました。

画像解析結果を見やすく扱う画面を作るためには、Vue3で一覧を表示するだけでなく、Node.jsやNuxt4のサーバー側でデータを返す部分も必要になりそうです。まずは仮データを返す小さなAPIから作り、次にJSONファイルを読む形へ進めてみたいです。

学習中の内容なので、勘違いや理解不足もあると思います。もし気になる点があれば教えてもらえるとうれしいです。

 注意事項
 本ブログに掲載している内容は、私個人の見解であり、
 所属する組織の立場や戦略、意見を代表するものではありません。
 あくまでエンジニアとしての経験や考えを発信していますのでご了承ください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?