Next.js と Node.js で学ぶ、ファイルの読み書きを可能にするfs モジュール

TwitterFacebookHatena

TL;DR

このページでは、Next.js と TypeScript のコンテクストで Node.js の fs(ファイルシステム)モジュールの実装方法について詳しく解説します。一言でいうと、fs モジュールとはファイルの読み書きを可能にする Node.js の組み込みモジュールです。

開発環境 バージョン
Next.js 13.4.4
TypeScript 5.0.4
Emotion 11.11.0
React 18.2.0

「fs モジュール」 とは?

「fs」とは、Node.js に内蔵されている「File System」の略称で、ファイルの読み書きを行う機能を提供しています。ファイルの操作は Web アプリケーションの開発においてよく遭遇する課題であり、それらを効率的に処理するための重要なツールです。

基本的な使い方は以下の通りです。まず、fs モジュールを import します。

import fs from 'fs'

次に、fs モジュールの readFile 関数を使って、ファイルを読み込みます。ここでは非同期でのファイル読み込みを行います。

fs.readFile('path/to/your/file', 'utf-8', (err, data) => {
  if (err) {
    console.error(err)
    return
  }
  console.log(data)
})

上記のコードは、指定したパスのファイルを読み込み、その内容を表示します。エンコーディングを 'utf-8' に指定することで、読み込んだファイルの内容を文字列として扱うことができます。また、エラーハンドリングも行い、エラーが発生した場合にはエラーメッセージを表示します。

fs.readFile メソッド

fs.readFile メソッドは、指定されたファイルの内容を非同期で読み込むためのメソッドです。以下のように使用します。

fs.readFile(path, [options], callback)

fs.readFile メソッドの引数は以下の通りです:

  1. path(必須):ファイルへのパス。文字列、バッファ、または URL を指定できます。

  2. options(オプション):エンコーディング、フラグ、またはモードを指定するオブジェクト。または、エンコーディングを示す文字列。デフォルトは null です。

  3. callback(必須):ファイルの内容が読み込まれた後に呼び出される関数。この関数は、最初の引数にエラーオブジェクト、二番目の引数にファイルの内容を取ります。

以下は fs.readFile メソッドを使ったコード例です:

import fs from 'fs'
import path from 'path'

const filePath = path.join(process.cwd(), 'data', 'content.txt')

fs.readFile(filePath, 'utf-8', (err, data) => {
  if (err) {
    console.error('Error reading file:', err)
    return
  }

  console.log('File contents:', data)
})

このコードでは、まず filePath に読み込むファイルの絶対パスを設定します。そして、fs.readFile を使ってそのファイルを読み込みます。第二引数に 'utf-8' を指定することで、ファイルの内容を文字列として読み込みます。読み込みが完了したら、コールバック関数が呼び出され、エラーがあればそれを表示し、なければファイルの内容を表示します。

Markdown ファイルを読み込む

Markdown ファイルを読み込むためには、ファイルを読み込むための fs モジュールに加え、Markdown を HTML に変換するためのパッケージ(例えば marked)も必要となります。

まず、必要なパッケージをインストールします。ターミナルで次のコマンドを実行してください。

npm install marked

インストールが完了したら、/api/content というエンドポイントを作成します。このエンドポイントでは、fs モジュールを使って Markdown ファイルを読み込み、marked パッケージを使ってその内容を HTML に変換します。

ファイル名:pages/api/content.ts

import { NextApiRequest, NextApiResponse } from 'next'
import fs from 'fs'
import path from 'path'
import marked from 'marked'

const handler = async (req: NextApiRequest, res: NextApiResponse) => {
  const filePath = path.join(process.cwd(), 'data', 'content.md')
  fs.readFile(filePath, 'utf-8', (err, data) => {
    if (err) {
      res.status(500).json({ message: 'Error reading file' })
      return
    }

    const htmlContent = marked(data)
    res.status(200).json({ data: htmlContent })
  })
}

export default handler

このコードでは、fs.readFile を使って指定の Markdown ファイルを読み込み、その内容を marked を使って HTML に変換します。そして、その HTML 文字列をレスポンスとして返します。

最後に、フロントエンドからこの API を呼び出す方法を見てみましょう。

ファイル名:pages/index.tsx

import { useEffect, useState } from 'react'
import axios from 'axios'

const Home = () => {
  const [content, setContent] = useState('')

  useEffect(() => {
    const fetchContent = async () => {
      const res = await axios.get('/api/content')
      setContent(res.data.data)
    }
    fetchContent()
  }, [])

  return <div dangerouslySetInnerHTML={{ __html: content }}></div>
}

export default Home

ここでは dangerouslySetInnerHTML を使って API から取得した HTML 文字列を表示しています。ただし、dangerouslySetInnerHTML は XSS(クロスサイトスクリプティング)攻撃のリスクがありますので、信頼できないソースからの HTML をそのまま表示するのは避けるべきです。今回の例では、自分で管理している Markdown ファイルの内容を表示しているので、問題ないと思われます。

Next.js で、fs モジュール を実装

ここからは、Next.js と TypeScript を使って、fs モジュールを使用する方法を見ていきましょう。

サーバーサイドのコードに fs モジュールを使用するため、Next.js の API ルートを使います。今回は /api/content というエンドポイントを作成し、その中で fs モジュールを使ってファイルの読み込みを行います。

ファイル名:pages/api/content.ts

import { NextApiRequest, NextApiResponse } from 'next'
import fs from 'fs'
import path from 'path'

const handler = async (req: NextApiRequest, res: NextApiResponse) => {
  const filePath = path.join(process.cwd(), 'data', 'content.txt')
  fs.readFile(filePath, 'utf-8', (err, data) => {
    if (err) {
      res.status(500).json({ message: 'Error reading file' })
      return
    }
    res.status(200).json({ data })
  })
}

export default handler

このコードではまず、API ルート(handler 関数)を定義します。その中で、fs モジュールを使って data/content.txt ファイルを読み込みます。ファイルのパスは path.join 関数を使って生成しています。エラーが発生した場合には、ステータスコード 500 を返し、エラーメッセージを含む JSON をレスポンスとして返します。ファイルが正常に読み込まれた場合には、その内容を JSON の形で返します。

fs モジュールによるファイル書き込み

fs モジュールはファイルの読み込みだけでなく、ファイルの書き込みも行うことができます。このセクションでは、fs モジュールを使ってファイルにデータを書き込む方法を見ていきましょう。

ファイル名:pages/api/updateContent.ts

import { NextApiRequest, NextApiResponse } from 'next'
import fs from 'fs'
import path from 'path'

const handler = (req: NextApiRequest, res: NextApiResponse) => {
  if (req.method === 'POST') {
    const { content } = req.body
    const filePath = path.join(process.cwd(), 'data', 'content.txt')
    fs.writeFile(filePath, content, 'utf-8', (err) => {
      if (err) {
        res.status(500).json({ message: 'Error writing file' })
        return
      }
      res.status(200).json({ message: 'File updated successfully' })
    })
  } else {
    res.status(405).json({ message: 'Method not allowed' })
  }
}

export default handler

このコードでは、POST リクエストが送られた時、リクエストボディに含まれる contentdata/content.txt ファイルに書き込みます。書き込みには fs モジュールの writeFile 関数を使用します。この関数は第一引数にファイルパス、第二引数に書き込むデータ、第三引数にエンコーディング、第四引数にコールバック関数を取ります。

エラーが発生した場合には、ステータスコード 500 を返し、エラーメッセージを含む JSON をレスポンスとして返します。ファイルへの書き込みが成功した場合には、成功メッセージを含む JSON を返します。

ファイルを削除する

ファイルの削除には、fs モジュールの fs.unlink メソッドを使用します。これは非同期的な操作で、コールバック関数を必要とします。以下に、その使用例を示します。

import fs from 'fs'
import path from 'path'

const filePath = path.join(process.cwd(), 'data', 'file-to-delete.txt')

fs.unlink(filePath, (err) => {
  if (err) {
    console.error('Error deleting file:', err)
    return
  }

  console.log('File deleted successfully')
})

このコードでは、まず filePath に削除するファイルの絶対パスを設定します。そして、fs.unlink メソッドを使ってそのファイルを削除します。削除が完了したら、コールバック関数が呼び出され、エラーがあればそれを表示し、なければ成功メッセージを表示します。

なお、このメソッドは非同期であるため、削除処理が終了するまでプログラムの実行はブロックされません。同期的にファイルを削除したい場合は、fs.unlinkSync メソッドを使用できます。ただし、同期的なメソッドはプログラムの実行をブロックするため、パフォーマンスに影響を与える可能性があることに注意してください。

フロントエンドでの API の利用

最後に、フロントエンドのコンポーネントから上記で作成した API を呼び出す方法を見ていきましょう。

ファイル名:pages/index.tsx

import { useEffect, useState } from 'react'
import axios from 'axios'

const Home = () => {
  const [content, setContent] = useState('')

  useEffect(() => {
    const fetchContent = async () => {
      const res = await axios.get('/api/content')
      setContent(res.data.data)
    }
    fetchContent()
  }, [])

  return (
    <div>
      <p>{content}</p>
    </div>
  )
}

export default Home

このコードでは、useStateuseEffect フックを使って、コンポーネントのマウント時に API を呼び出し、その結果を状態として保持します。その状態を元に、API の結果を表示します。このように Next.js で fs モジュールを使った API を作成し、それをフロントエンドから利用することで、サーバーサイドでのファイル操作を行うことができます。

今回紹介した例は、最も基本的なものですが、fs モジュールはこれだけでなく、ファイルやディレクトリの作成、リネーム、削除など、様々なファイル操作を行うことができます。それらの機能を活用することで、より複雑な処理を行うことができます。

Next.js と Node.js で学ぶ、ファイルの読み書きを可能にするfs モジュール