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?

Saas『Cloudinary』を使って画像を更新と削除処理をNext.jsで制御する方法

Posted at

『Cloudinary』とは?

Cloudinaryは、Webアプリやクラウドメディア向けに画像などのファイル情報を提供するためのサービスです。
アメリカのカリフォルニアに本社のあるSaas企業で、無料と有料でサービスをりよす売ることができます。

高速に加増を表示させることや画像処理最適化もできるため人気が高まっているので今回は、Next.jsを使って画像をアップロードと削除の処理を実装していきたいと思います。

事前準備

モジュールのインストール

next-cloudinary

■@types/sha1 (SHA-1(Secure Hash Algorithm 1))※

※任意の長さの原文から160ビットのハッシュ値を生成する暗号学的ハッシュ関数のこと。
⇒画像をサーバ側(route.ts)で制御するためにこのモジュールは必要です。

下記のコマンドを入力してインストールする。

npm install --save @types/sha1

環境変数(.envファイルの作成と編集)の設定

Next.jsのルートプロジェクトに.envファイルを作成して、シークレットキーなどの情報を入力しておきます。

.env
NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME='your cloud name'
NEXT_PUBLIC_CLOUDINARY_API_KEY=='your api key'
CLOUDINARY_API_SECRET='your api scecret key'
NEXT_PUBLIC_CLOUDINARY_UPLOAD_PRESET='your cloudinary upload preset'

ディレクトリの構成

Cloudinary APIとのやり取りはサーバ側(route.ts)で行いので、/app/api/直下のフォルダを作成しています。

.
└── Procject/
    └── app/
        ├── api/
        │   ├── filesDelete/
        │   │   └── route.ts
        │   └── filesUploads/
        │       └── route.ts
        └── components/
            └── thumbnails/
                └── page.tsx

画面の実装

実装の流れは、下記の通りにコーディングしていきます。

CloudinaryのUpload Presetを定義

page.tsx
const uploadPreset = process.env.NEXT_PUBLIC_CLOUDINARY_UPLOAD_PRESET;

画像のアップーロードすためのサーバエンドポイントを定義

さいしょに、画像をCloudinaryへアップロードするためのエンドポイントを定義していきます。

page.tsx
const uploadSignatureEndpoint = '/api/filesUploads';

Formタグを設定

注意するポイント

UploadDeleteともに画像のこうする宇するだけなので、

タグには、handleSubmit不要です。
そのため、のtype属性もbutton属性にします。
page.tsx
<form>
    <button type="button" onClick={handleUpload} className="bg bg-blue-500 px-4 py-4">
        upload
    </button>
</form>
page.tsx
(前略)
  return (
    <div>
      <form>
        <input
          type="file"
          onChange={(e)=>setFile(e.target.files?.[0] || null)}
        />
        <button type="button" onClick={handleUpload} className="bg bg-blue-500 px-4 py-4">
          upload
        </button>
        
      </form>
      <div>
        {uploadFilePath &&(
          <form>
            <div>
              <input
                  type="hidden"
                  value={publicId}
                  id="publicId"
              />
            </div>
            <div>
              <input
                type="hidden"
                value={signature}
                id="signature"
              />
            </div>
              <div>
                <img src={uploadFilePath}  alt="Uploaded image" width={300} height={300}/>
              </div>
              <div>
                <button
                  type="button"
                  className="bg bg-rose-500 text-white rounded-md px-4 py-4"
                  onClick={handleDelete}
                >
                  Delete
                </button>
              </div>
          </form>          
        )}
      </div>
    </div>
  );

アップロードした画像を画面に表示する

状態管理した変数uploadFilePath の値を判定して、画像がアップロードされたら画面表示させます。

page.tsx
      <div>
        {uploadFilePath &&(
          <form>
            <div>
              <input
                  type="hidden"
                  value={publicId}
                  id="publicId"
              />
            </div>
            <div>
              <input
                type="hidden"
                value={signature}
                id="signature"
              />
            </div>
              <div>
                <img src={uploadFilePath}  alt="Uploaded image" width={300} height={300}/>
              </div>
              <div>
                <button
                  type="button"
                  className="bg bg-rose-500 text-white rounded-md px-4 py-4"
                  onClick={handleDelete}
                >
                  Delete
                </button>
              </div>
          </form>          
        )}
      </div>

アップーロードした画像を削除するためのサーバエンドポイントを定義

つづいて、アップロードした画像をサーバから削除するためのエンドポイントを定義していきます。

page.tsx
const deleteSignatureEndPoint = '/api/filesDelete';

全体のコードはこちら↓

Projcet/app/components/thumbnails/page.tsx
"use client";

import { CldUploadButton, type CldUploadButtonProps } from "next-cloudinary";
import { useState,useEffect } from "react";
//import axios from "axios";

export default function UploadButton(props: CldUploadButtonProps) {
  const uploadPreset = process.env.NEXT_PUBLIC_CLOUDINARY_UPLOAD_PRESET;
  const uploadSignatureEndpoint = '/api/filesUploads';
  const deleteSignatureEndPoint = '/api/filesDelete';

  if (!uploadPreset) {
    throw new Error("Cloudinary upload preset is not defined in environment variables.");
  }

  const [file,setFile] = useState<File | null>(null);
  const [uploadFilePath,setUploadFilePath] = useState('');
  const [publicId,setPublicId] = useState('');
  const [signature,setSignature] = useState('');

  const handleUpload = async(result: any) => {
    const formData = new FormData();
    if(!file){
      return;
    }
    formData.append('file',file);
    formData.append('upload_preset',uploadPreset);
    try{
       await fetch(uploadSignatureEndpoint,{
        method:'POST',
        body:formData,
      }).then(async(response)=>{
        const json = await response.json();
        console.log('レスポンスデータは、',json);
        console.log('Public_Idは、',json.publicId);
        console.log('Signatureは、',json.signature);
        console.log('Secure_URLは、',json.secureUrl);
        setPublicId(json.publicId);
        setSignature(json.signature);
        setUploadFilePath(json.secureUrl);
        alert('Success to upload.');
        
      }).catch((error)=>{
        console.log(error);
        alert(error);
      });
           
    }catch(error){
      alert(error);
      console.log(error);
    }
  };

  const handleDelete = async()=>{
    const formData = new FormData();
    formData.append('publicId',publicId);
    formData.append('signature',signature);
    formData.append('uploadFilePath',uploadFilePath);
    formData.append('upload_preset',uploadPreset);
    
    try{
      const response = await fetch(deleteSignatureEndPoint,{
        method:'POST',
        body:formData
      });
      if(response.ok){
        alert('Success to delete.');
        console.log(response);
        setUploadFilePath("");
      }
      
    }catch(error){
      console.log(error);
    }
    
  }

  return (
    <div>
      <form>
        <input
          type="file"
          onChange={(e)=>setFile(e.target.files?.[0] || null)}
        />
        <button type="button" onClick={handleUpload} className="bg bg-blue-500 px-4 py-4">
          upload
        </button>
        
      </form>
      <div>
        {uploadFilePath &&(
          <form>
            <div>
              <input
                  type="hidden"
                  value={publicId}
                  id="publicId"
              />
            </div>
            <div>
              <input
                type="hidden"
                value={signature}
                id="signature"
              />
            </div>
              <div>
                <img src={uploadFilePath}  alt="Uploaded image" width={300} height={300}/>
              </div>
              <div>
                <button
                  type="button"
                  className="bg bg-rose-500 text-white rounded-md px-4 py-4"
                  onClick={handleDelete}
                >
                  Delete
                </button>
              </div>
          </form>          
        )}
      </div>
    </div>
  );
}

サーバサイドの実装

画像のアップロード

Cloudinaryのシークレットキーとの設定

Cloudinaryのシークレットキーとの設定をします。

page.tsx
// Cloudinaryの設定
cloudinary.config({
  cloud_name: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME,
  api_key: process.env.NEXT_PUBLIC_CLOUDINARY_API_KEY,
  api_secret: process.env.CLOUDINARY_API_SECRET,
});

画面から渡ってきたFormDataから値を取得

画面から渡ってきたFormDataを取得します。

page.tsx
const formData =await request.formData();

FormDataの中身をより詳しく知りたいときは下記を実装します。

page.tsx
const image = formData.get('file');
const uploadPreset = formData.get('upload_preset') as string;

Cloudinaryへアップするための情報をFormDataに格納

page.tsx
const cloudinaryForm = new FormData();
cloudinaryForm.append('file', image);
cloudinaryForm.append("upload_preset", uploadPreset);

非同期通信でCloudinaryへアップロード

Cloudinaryへ画像をアップ路度する際は、下記のURLを指定する。

`https://api.cloudinary.com/v1_1/${process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME}/image/upload/`,//`https://api.cloudinary.com/v1_1/${process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME}/image/upload/`

レスポンスデータをJSON変換

画面にへんきゃする前に、Cloudinaryから返却されたデータをJSONへ変換する。

page.tsx
const uploadedImageData = await uploadResponse.json();
console.log('uploadedImageDataは、', uploadedImageData);
console.log('uploadedImageData.secure_urlは、', uploadedImageData.secure_url);
page.tsx
    // Cloudinaryに画像をアップロード
    const uploadResponse = await fetch(
      `https://api.cloudinary.com/v1_1/${process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME}/image/upload/`,//`https://api.cloudinary.com/v1_1/${process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME}/image/upload/`,
      {
        method: "POST",
        body: cloudinaryForm,
      }
    );

NextResponseで返却

NextRequestの戻り値は、NextResponseオブジェクトなのでNextResponseオブジェクトを使って返却する。

page.tsx
    return NextResponse.json({
      secureUrl:uploadedImageData.secure_url,
      publicId:uploadedImageData.public_id,
      signature:uploadedImageData.signature,
      message: "Success",
    },{status:200});

全体のコードはこちら↓

Project/app/api/filesUploads/route.ts
import { v2 as cloudinary } from "cloudinary";
import { NextRequest, NextResponse } from "next/server";

// Cloudinaryの設定
cloudinary.config({
  cloud_name: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME,
  api_key: process.env.NEXT_PUBLIC_CLOUDINARY_API_KEY,
  api_secret: process.env.CLOUDINARY_API_SECRET,
});

export async function POST(request: NextRequest) {

  try {
    // フォームデータの取得
    const formData =await request.formData();//await
    const image = formData.get('file');//as Blob
    const uploadPreset = formData.get('upload_preset') as string;

    const cloudinaryForm = new FormData();
    cloudinaryForm.append('file', image);
    cloudinaryForm.append("upload_preset", uploadPreset);
    console.log('cloudinaryForm',cloudinaryForm);
    
    // Cloudinaryに画像をアップロード
    const uploadResponse = await fetch(
      `https://api.cloudinary.com/v1_1/${process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME}/image/upload/`,//`https://api.cloudinary.com/v1_1/${process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME}/image/upload/`,
      {
        method: "POST",
        body: cloudinaryForm,
      }
    );

    // レスポンスが成功していない場合はエラーメッセージを表示
    if (!uploadResponse.ok) {
      const errorText = await uploadResponse.text();
      console.error('Cloudinary upload failed:', errorText);
      return NextResponse.json({ error: 'Cloudinary upload failed', details: errorText }, { status: 500 });
    }

    // JSONレスポンスを取得
    const uploadedImageData = await uploadResponse.json();
    console.log('uploadedImageDataは、', uploadedImageData);
    console.log('uploadedImageData.secure_urlは、', uploadedImageData.secure_url);
    // 成功した場合、secure_urlを返す
    return NextResponse.json({
      secureUrl:uploadedImageData.secure_url,
      publicId:uploadedImageData.public_id,
      signature:uploadedImageData.signature,
      message: "Success",
    },{status:200});
    

    /*
    return new NextResponse(
      JSON.stringify({
        secure_url: uploadedImageData.secure_url,
        message: "Success",
      }),
      {
        status: 200,//201
        headers: {
          "Content-Type": "application/json",
          "Access-Control-Allow-Origin": "*", // CORS設定
        },
      
      },
    );
    */
  } catch (error) {
    console.error("Error during Cloudinary upload:", error);
    return NextResponse.json({ error: "Internal Server Error", details: error.message }, { status: 500 });
  }
}

Cloudinary上の画像を削除

SHA1をインポート

page.tsx
import sha1 from "sha1";

上記にも書いたが、TypeScriptのモジュールは下記をインストールする。

【重要】秒単位のTimeStampを設定

timestamp は「UNIX秒(例: 1689438743)」であるべきなので、下記の通り設定する。

page.tsx
const timestamp = Math.floor(Date.now() / 1000); // 秒単位

【重要】シグネチャ(書名)をハッシュ化する

page.tsx
const stringToSign = `public_id=${publicId}&timestamp=${timestamp}${process.env.CLOUDINARY_API_SECRET}`;
const signature = sha1(stringToSign);

Formdataを作成

page.tsx
        const cloudinaryForm = new FormData();
        cloudinaryForm.append('api_key',process.env.NEXT_PUBLIC_CLOUDINARY_API_KEY);
        cloudinaryForm.append('public_id',publicId);
        cloudinaryForm.append('signature',signature);
        cloudinaryForm.append('uploadFilePath',uploadFilePath);
        cloudinaryForm.append('uploadPreset',uploadPreset);
        cloudinaryForm.append("timestamp", timestamp);

全体のコードはこちら↓

Project/app/api/filesDelete/route.ts
import { v2 as cloudinary } from "cloudinary";
import { NextRequest, NextResponse } from "next/server";
import sha1 from "sha1";

// Cloudinaryの設定
cloudinary.config({
  cloud_name: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME,
  api_key: process.env.NEXT_PUBLIC_CLOUDINARY_API_KEY,
  api_secret: process.env.CLOUDINARY_API_SECRET,
});

export async function POST(request:NextRequest){
    try{
        const formData = await request.formData();
        const publicId = formData.get('publicId') as string;
        //const signature = formData.get('signature') as string;
        const uploadFilePath = formData.get('uploadFilePath') as string;
        const uploadPreset = formData.get('upload_preset') as string;
        const timestamp = Math.floor(Date.now() / 1000); // 秒単位
        const stringToSign = `public_id=${publicId}&timestamp=${timestamp}${process.env.CLOUDINARY_API_SECRET}`;
        const signature = sha1(stringToSign);

        const cloudinaryForm = new FormData();
        cloudinaryForm.append('api_key',process.env.NEXT_PUBLIC_CLOUDINARY_API_KEY);
        cloudinaryForm.append('public_id',publicId);
        cloudinaryForm.append('signature',signature);
        cloudinaryForm.append('uploadFilePath',uploadFilePath);
        cloudinaryForm.append('uploadPreset',uploadPreset);
        cloudinaryForm.append("timestamp", timestamp);

        const deleteResponse = await fetch(`https://api.cloudinary.com/v1_1/${process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME}/image/destroy`,{
            method:'DELETE',
            body:cloudinaryForm
        });

        const responsedata = await deleteResponse.json();
        console.log('削除したデータのレスポンスは、',responsedata);
        return NextResponse.json({
            message:"Succes to delete image."
        },{status:200});

    }catch(error){
        return NextResponse.json({
            message:'Failed to delete image.'
        },{status:500});
    }
}

参考サイト

■ LinkedIN:How to Upload and Delete Images Using Cloudinary API with Nextjs @Rishabh Tak

■ Cloudinary へ画像アップロード

■ [Next.js]Cloudinaryを使ってみた

MINIO関連

■ 【Windows】MinIO Server インストール手順

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?