【Next.js】DaisyUiのCardにDBに保存した画像のパスを渡してブラウザに表示させる方法

# ローカルデータベースに保存した画像のパスをブラウザに表示したい

こんな思いを抱いている方向けに記事を書いていきたいと思います。
意外と情報が無かったので少しでもお役立てればうれしいです。

# 完成イメージ
画像ファイルの上部の３つのCardは、画像が存在しない場合のイメージです。
画面ファイルの左端はローカルデータベースから画像パスを取得してブラウザ表示された時のイメージです。

![画像2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/589f373d-5f20-cc42-c884-3d59650ff6c8.png)

# 画像のパス保存からブラウザ表示までの流れ
コードのロジックは下記のとおりです。
<b>【手順】</b>
<b>①</b>ブラウザで画像をアップロードして<b>pulicフォルダ</b>配下に新規フォルダ「uploads」を作成します。このフォルダの中にアップロードした画像ファイルを格納します。

:::note info
<b>【これだけは覚えておいてほしい】</b>
Next.jsでは、プロジェクト作成時に<b>publicフォルダ</b>が作成されます。
このpublicフォルダはブラウザからアクセスできるフォルダとなります。
なので、画像を表示したい場合は<b>必ずpublicフォルダ内に配置</b>しましょう。
:::

<b>②</b>publicフォルダ配下のuploadsフォルダに画像を配置したら、その絶対パスをメモリに一時的に保存します。
```
(メモリの保存イメージ)
C:\Users\[ユーザー名]\Desktop\webapp\public\uploads\apple.png
```

<b>③</b>画像の絶対パスをローカルデータベースに保存する前に加工します。
どのように加工するかというと、Webサーバから画像が見えるようにパスを加工します。
今回はローカルサーバから画像が見えるようにしたいので、下記のURLをパスに付けます。
```
(ローカルURL)
http://localhost:3000/
```
Next.jsのポート番号は3000番なので↑のように記載しましょう。

:::note info
コンソールコマンドで「npm run dev」すると「`http://localhost:3000/`」が表示されるので、このURLをコピー&ペーストすると良いでしょう。
:::


プログラムでは下記のように①加工前から加工後に変換していきます。
```
(画像の絶対パスの加工前)
C:\Users\[ユーザー名]\Desktop\webapp\public\uploads\apple.png
```
<b>publicフォルダは書かないの？</b>と思った人もいるでしょう。
そうです、<b>publicフォルダは、デフォルトでWebサーバから見えているので書きません。</b>
<b>publicフォルダ配下の指定したフォルダ名をURLに追加してください。</b>
```
(画像の絶対パスの加工後)
http://localhost:3000/uploads/apple.png
```

# 実際のプログラムのコードを見ながら解説する
上記の<b>「画像のパス保存からブラウザ表示までの流れ」</b>での説明をプログラムを指し示しながら解説します。

①ブラウザで画像をアップロードしてpulicフォルダ配下に新規フォルダ「uploads」を作成します。このフォルダの中にアップロードした画像ファイルを格納します。
☞この説明を表したコードは下記となります。
変数filePathでブラウザからアクセスするためのフォルダパス<b>「./public/uploads」</b>を指定します。
そのあと、指定先のフォルダに画像をアップロードします。

```app/pages/api/upload.tsx
    if(file && file.size > 0){
        const data = await file.arrayBuffer();
        const buffer = Buffer.from(data);
        filePath = resolve(
            process.cwd(),
            "./public/uploads", // "./uploads"
            `${file.name}` //`${crypto.randomUUID()}.${file.name.split(".").pop()}`
        );
        await fs.writeFile(filePath,buffer);

        //以降の処理は省略...
    }
```

②publicフォルダ配下のuploadsフォルダに画像を配置したら、その絶対パスをメモリに一時的に保存します。
☞こちらは、変数filePathに保持した馬場です。

③画像の絶対パスをローカルデータベースに保存する前に加工します。
どのように加工するかというと、Webサーバから画像が見えるようにパスを加工します。
☞変数filePathをWebサーバーが画像を認識できるようにローカルURLを上書きしておきます。
それが下記のプログラムコードです。

```app/pages/api/upload.tsx
 filePath = `http://localhost:3000/uploads/` +`${file.name}`;
```

変数filePathをWebサーバからアクセスできるURLに加工したら、そのURLパスなどをデータベースに登録します。
そのプログラムコードが下記です。

```app/pages/api/upload.tsx
//データベースへ登録する処理
export async function RegisterEventInfo(array:any){        
    //データベース登録完了判定フラグ（完了：、true失敗：false）
    let completeFlag = false;

    let eventInfoArray = array;
    console.log("渡された配列は、" + eventInfoArray);
    
    //データベースへ接続する
    const connection = await getDB();

    try{
        //登録する情報は１．ユーザID、２．イベントタイトル、３，イベント詳細、４，イベントのサムネイルパス
        //イベントIDは自動連番のためクエリには記載しない
        await connection.execute(`INSERT INTO eventInfo (userid,eventtitle,eventdetail,eventthumbnailpath) VALUES (?,?,?,?)`,
            [eventInfoArray[0],eventInfoArray[1],eventInfoArray[2],eventInfoArray[3]]
        )
        console.log("イベント情報を登録しました。");
        
        //データベース登録完了判定フラグを更新する
        completeFlag = true;
        //EvaluationFlagForEventRegister(completeFlag);                 
    }catch(error){
        console.log("イベント登録のエラーが発生しました。");
        
    }finally{
        connection.end();              
    }
    //データベース登録完了判定フラグを更新する 
    //EvaluationFlagForEventRegister(completeFlag);
    return  completeFlag;  
}
```

全体を踏まえたコードは下記となります。

```app/pages/api/upload.ts
'use server'

import { promises as fs } from "node:fs"
import { resolve } from "node:path"
import { revalidatePath } from "next/cache"
import { array, boolean } from "zod";
import { list } from "postcss";
import { getDB } from "@/app/lib/db";

//返却する値をタプル型配列に格納する(ユーザーID、イベントタイトル、イベント詳細、サムネイルパス)の順番
let eventInfoArray:[String,String,String,String];

export async function UploadFile(formData:FormData){
        
    const file = formData.get("file") as File;
    const userId = formData.get("userId");
    const eventTitle = formData.get("eventTitle");
    const eventDetail = formData.get("eventDetail");
    //アップロードファイル情報を取得する
    console.log(file);
    //ユーザーIDを取得する
    console.log(userId);
    //イベントタイトルを取得する
    console.log(eventTitle);
    //イベント内容を取得する
    console.log(eventDetail);
    //ファイルのパス情報
    let filePath = '';
    if(file && file.size > 0){
        const data = await file.arrayBuffer();
        const buffer = Buffer.from(data);
        filePath = resolve(
            process.cwd(),
            "./public/uploads", // "./uploads"
            `${file.name}` //`${crypto.randomUUID()}.${file.name.split(".").pop()}`
        );
        await fs.writeFile(filePath,buffer);
        //ファイル名を取得する
        console.log("アップロードファイル名は、" + file.name);
        //ファイルパスを取得する
        console.log("修正後のファイルパスは：" + filePath);
        //Next.jsプロジェクトの公開パス(public)配下に上書きする
        //filePath = '/uploads'+"\\"+`${crypto.randomUUID()}.${file.name.split(".").pop()}`;
        filePath = `http://localhost:3000/uploads/` +`${file.name}`;
        console.log("修正後のこれの上書き後のファイルパスは：" + filePath);
    }
    revalidatePath("/file");

    if(userId !=null && eventTitle != null && eventDetail !=null && file != null){
        //返却値を配列に格納する
        eventInfoArray = [userId?.toString(),eventTitle?.toString(),eventDetail?.toString(),filePath?.toString()];
        console.log("配列情報は：" + eventInfoArray);
        console.log("配列の最後尾は：" + eventInfoArray[3]);
        
        //データベースへ登録する処理を実行
        const registerationSuccess = await RegisterEventInfo(eventInfoArray);
        console.log("イベントの登録に成功しました。");
        return registerationSuccess;
    }else{
        console.log("イベントの登録に失敗しました。");
    }    
    return false;
}

//データベースへ登録する処理
export async function RegisterEventInfo(array:any){        
    //データベース登録完了判定フラグ（完了：、true失敗：false）
    let completeFlag = false;

    let eventInfoArray = array;
    console.log("渡された配列は、" + eventInfoArray);
    
    //データベースへ接続する
    const connection = await getDB();

    try{
        //登録する情報は１．ユーザID、２．イベントタイトル、３，イベント詳細、４，イベントのサムネイルパス
        //イベントIDは自動連番のためクエリには記載しない
        await connection.execute(`INSERT INTO eventInfo (userid,eventtitle,eventdetail,eventthumbnailpath) VALUES (?,?,?,?)`,
            [eventInfoArray[0],eventInfoArray[1],eventInfoArray[2],eventInfoArray[3]]
        )
        console.log("イベント情報を登録しました。");
        
        //データベース登録完了判定フラグを更新する
        completeFlag = true;
        //EvaluationFlagForEventRegister(completeFlag);                 
    }catch(error){
        console.log("イベント登録のエラーが発生しました。");
        
    }finally{
        connection.end();              
    }
    //データベース登録完了判定フラグを更新する 
    //EvaluationFlagForEventRegister(completeFlag);
    return  completeFlag;  
}

//データベース登録完了判定フラグを返却する処理
export async function EvaluationFlagForEventRegister(flag:boolean){
    return flag;
}
     

```

こんな感じでブロブラウザに表示することができました。

参考までにChatGPTからの回答内容もの載せておきます。

## (ChatGPTからの回答)

Next.jsでデータベースに保存されているローカルの絶対パスをブラウザに表示し、画像を表示できるようにするためには、いくつかの考慮すべきポイントがあります。一般的に、ブラウザ側ではローカルファイルシステムのパスを直接参照することはできません。そのため、画像を表示するためには、以下の方法を試してください。

<b>解決方法：</b>
画像をpublicディレクトリに格納する

Next.jsでは、publicディレクトリ内のファイルは静的なアセットとして提供されます。データベースに保存されているローカルの絶対パスが実際にサーバー上の画像を指しているのであれば、その画像ファイルをpublicフォルダに移動し、相対パスでブラウザにアクセスできるようにします。
例えば、データベースに保存されているパスが /images/sample.jpg の場合、その画像をプロジェクトの public/images/ フォルダに配置します。次に、ブラウザでその画像を表示するために、次のようにHTMLやNext.jsのImageコンポーネントを使って画像を表示できます。

```page.jsx
import Image from 'next/image';

const MyPage = ({ imagePath }) => {
  return (
    <div>
      <h1>画像の表示</h1>
      <Image src={imagePath} alt="画像" width={500} height={300} />
    </div>
  );
};

export async function getServerSideProps() {
  const imagePath = '/images/sample.jpg'; // DBから取得したパス
  return { props: { imagePath } };
}

export default MyPage;

```

<b>注意点：</b>

publicフォルダ内に配置された画像は、/images/sample.jpg のように、ルートパスを基準にアクセスできます。
Imageコンポーネントを使うことで、画像の最適化が行われます。
画像を静的ファイルサーバーに配置してURLで参照する

もし画像がローカルサーバーや外部サーバーに保存されている場合、画像をそのサーバーに置いて、URLをデータベースに保存します。ブラウザでは絶対URLを使って画像にアクセスできるようにします。 例えば、画像がhttp://localhost:3000/images/sample.jpgというURLでアクセスできる場合、そのURLをデータベースに保存し、ブラウザ側でそのURLを使用して画像を表示します。

```page.jsx
const MyPage = ({ imageUrl }) => {
  return (
    <div>
      <h1>画像の表示</h1>
      <img src={imageUrl} alt="画像" />
    </div>
  );
};

export async function getServerSideProps() {
  const imageUrl = 'http://localhost:3000/images/sample.jpg'; // DBから取得したURL
  return { props: { imageUrl } };
}

export default MyPage;

```

<b>画像をbase64エンコードして保存</b>

画像をデータベースに直接保存する代わりに、画像をbase64エンコードして文字列として保存する方法もあります。この方法では、画像のデータを直接HTMLに埋め込んで表示できます。
```page.jsx
const MyPage = ({ base64Image }) => {
  return (
    <div>
      <h1>画像の表示</h1>
      <img src={`data:image/jpeg;base64,${base64Image}`} alt="画像" />
    </div>
  );
};

export async function getServerSideProps() {
  const base64Image = '...'; // DBから取得したbase64エンコードされた画像データ
  return { props: { base64Image } };
}

export default MyPage;

```

<b>まとめ</b>
Next.jsのpublicディレクトリを使って、静的な画像ファイルを管理する方法が一般的で、推奨されます。
画像の絶対パスをブラウザ側で表示するためには、ローカルのファイルパスではなく、静的なURLやbase64形式で表示する必要があります。