# LaravelとNext.jsでBlobファイルの作成と取扱い
LaravelとNext.jsを使用したアプリケーションで、Blobファイルの作成と取扱いについて解説します。サーバー側(Laravel)とクライアント側(Next.js)でのBlobの生成、処理、連携方法を実装例とともに紹介します。
# Blobとは
Blob(Binary Large Object)は、JavaScriptでバイナリデータを扱うためのオブジェクトです。画像、動画、PDFなど、あらゆる種類のバイナリデータを格納できます。特に大きなファイルを扱う際に威力を発揮します。
# Blobの特徴
- バイナリデータの扱い: 画像や動画などのバイナリデータを扱える
- メモリ効率: 大きなファイルも効率的に処理できる
- 型指定: MIMEタイプを指定して、ファイルの種類を明確にできる
- URL生成:
URL.createObjectURL()で一時的なURLを生成できる - ストリーミング対応: 大きなファイルを分割して処理できる
# LaravelでのBlob作成と取扱い
# LaravelでBlobデータを生成
Laravelでバイナリデータを生成し、Blobとして扱う方法です。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;
class FileController extends Controller
{
/**
* 動的にBlobデータを生成
*/
public function generateBlob()
{
// バイナリデータを生成(例:画像の生成など)
$imageData = $this->generateImageData();
return response($imageData, 200)
->header('Content-Type', 'image/png')
->header('Content-Disposition', 'inline; filename="generated.png"');
}
/**
* データベースからBlobデータを取得
*/
public function getBlobFromDatabase($id)
{
$file = File::findOrFail($id);
// BLOBカラムからデータを取得
$blobData = $file->data;
return response($blobData, 200)
->header('Content-Type', $file->mime_type)
->header('Content-Length', strlen($blobData));
}
/**
* ストレージからBlobデータを取得
*/
public function getBlobFromStorage($path)
{
if (!Storage::exists($path)) {
abort(404, 'ファイルが見つかりません');
}
$fileContent = Storage::get($path);
$mimeType = Storage::mimeType($path);
return response($fileContent, 200)
->header('Content-Type', $mimeType)
->header('Content-Length', strlen($fileContent));
}
}
# LaravelでBlobデータをストリーミング
大きなファイルをストリーミングで配信する方法です。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;
class FileController extends Controller
{
/**
* 大きなファイルをストリーミングで配信
*/
public function streamBlob($id)
{
$file = File::findOrFail($id);
$filePath = storage_path('app/files/' . $file->path);
if (!file_exists($filePath)) {
abort(404, 'ファイルが見つかりません');
}
return response()->streamDownload(function () use ($filePath) {
$handle = fopen($filePath, 'rb');
$chunkSize = 1024 * 1024; // 1MB
while (!feof($handle)) {
echo fread($handle, $chunkSize);
flush();
}
fclose($handle);
}, $file->filename, [
'Content-Type' => $file->mime_type,
]);
}
}
# LaravelでBlobデータを保存
アップロードされたBlobデータを保存する方法です。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use App\Models\File;
class FileController extends Controller
{
/**
* Blobデータをアップロードして保存
*/
public function uploadBlob(Request $request)
{
$request->validate([
'file' => 'required|file|max:102400', // 100MBまで
'filename' => 'required|string',
]);
$uploadedFile = $request->file('file');
$filename = $request->input('filename');
// ストレージに保存
$path = $uploadedFile->store('files', 'public');
// データベースに保存
$file = File::create([
'filename' => $filename,
'path' => $path,
'mime_type' => $uploadedFile->getMimeType(),
'size' => $uploadedFile->getSize(),
]);
return response()->json([
'id' => $file->id,
'path' => $path,
'url' => Storage::url($path),
], 201);
}
/**
* バイナリデータを直接保存
*/
public function saveBinaryData(Request $request)
{
$request->validate([
'data' => 'required|string', // base64エンコードされたデータ
'filename' => 'required|string',
'mime_type' => 'required|string',
]);
// base64デコード
$binaryData = base64_decode($request->input('data'));
// ストレージに保存
$path = 'files/' . $request->input('filename');
Storage::put($path, $binaryData);
// データベースに保存
$file = File::create([
'filename' => $request->input('filename'),
'path' => $path,
'mime_type' => $request->input('mime_type'),
'size' => strlen($binaryData),
]);
return response()->json([
'id' => $file->id,
'path' => $path,
], 201);
}
}
# Next.jsでのBlob作成と取扱い
# Next.jsでBlobを作成
Next.js(クライアント側)でBlobを作成する方法です。
// app/components/BlobCreator.tsx
'use client';
import { useState } from 'react';
export default function BlobCreator() {
const [blob, setBlob] = useState<Blob | null>(null);
/**
* テキストからBlobを作成
*/
const createTextBlob = (text: string, mimeType: string = 'text/plain') => {
const blob = new Blob([text], { type: mimeType });
setBlob(blob);
return blob;
};
/**
* ArrayBufferからBlobを作成
*/
const createBlobFromArrayBuffer = (arrayBuffer: ArrayBuffer, mimeType: string) => {
const blob = new Blob([arrayBuffer], { type: mimeType });
setBlob(blob);
return blob;
};
/**
* 複数のチャンクを結合してBlobを作成
*/
const createBlobFromChunks = (chunks: Uint8Array[], mimeType: string) => {
const blob = new Blob(chunks, { type: mimeType });
setBlob(blob);
return blob;
};
/**
* 画像からBlobを作成
*/
const createBlobFromImage = async (imageUrl: string): Promise<Blob> => {
const response = await fetch(imageUrl);
const blob = await response.blob();
setBlob(blob);
return blob;
};
return (
<div>
{/* Blob作成のUI */}
</div>
);
}
# Next.jsでBlobをダウンロード
Blobをローカルにダウンロードする方法です。
// app/utils/blobDownload.ts
export function downloadBlob(blob: Blob, filename: string) {
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
// 使用例
export function useBlobDownload() {
const download = async (url: string, filename: string) => {
try {
const response = await fetch(url);
const blob = await response.blob();
downloadBlob(blob, filename);
} catch (error) {
console.error('ダウンロードに失敗しました:', error);
}
};
return { download };
}
# Next.jsでBlobを表示
Blobを画像や動画として表示する方法です。
// app/components/BlobViewer.tsx
'use client';
import { useState, useEffect } from 'react';
interface BlobViewerProps {
blob: Blob;
mimeType: string;
}
export default function BlobViewer({ blob, mimeType }: BlobViewerProps) {
const [objectUrl, setObjectUrl] = useState<string | null>(null);
useEffect(() => {
const url = URL.createObjectURL(blob);
setObjectUrl(url);
return () => {
URL.revokeObjectURL(url);
};
}, [blob]);
if (!objectUrl) {
return <div>読み込み中...</div>;
}
if (mimeType.startsWith('image/')) {
return <img src={objectUrl} alt="Blob image" />;
}
if (mimeType.startsWith('video/')) {
return <video src={objectUrl} controls />;
}
return <a href={objectUrl} download>ダウンロード</a>;
}
# Next.js API RouteでBlobを処理
Next.jsのAPI RouteでBlobを処理する方法です。
// app/api/files/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const fileId = searchParams.get('id');
if (!fileId) {
return NextResponse.json({ error: 'File ID is required' }, { status: 400 });
}
// Laravel APIからBlobデータを取得
const laravelUrl = `${process.env.LARAVEL_API_URL}/api/files/${fileId}`;
const response = await fetch(laravelUrl, {
headers: {
'Authorization': `Bearer ${process.env.LARAVEL_API_TOKEN}`,
},
});
if (!response.ok) {
return NextResponse.json({ error: 'File not found' }, { status: 404 });
}
const blob = await response.blob();
const arrayBuffer = await blob.arrayBuffer();
return new NextResponse(arrayBuffer, {
headers: {
'Content-Type': blob.type,
'Content-Length': blob.size.toString(),
},
});
}
export async function POST(request: NextRequest) {
const formData = await request.formData();
const file = formData.get('file') as File;
if (!file) {
return NextResponse.json({ error: 'File is required' }, { status: 400 });
}
// Blobデータを取得
const blob = await file.arrayBuffer();
// Laravel APIに送信
const laravelUrl = `${process.env.LARAVEL_API_URL}/api/files`;
const response = await fetch(laravelUrl, {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.LARAVEL_API_TOKEN}`,
'Content-Type': file.type,
},
body: blob,
});
const data = await response.json();
return NextResponse.json(data);
}
# LaravelとNext.jsの連携
# Laravel APIからBlobを取得してNext.jsで処理
// app/components/FileDownloader.tsx
'use client';
import { useState } from 'react';
export default function FileDownloader() {
const [loading, setLoading] = useState(false);
const downloadFile = async (fileId: number) => {
setLoading(true);
try {
// Laravel APIからBlobデータを取得
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/files/${fileId}`, {
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`,
},
});
if (!response.ok) {
throw new Error('ファイルの取得に失敗しました');
}
// Blobに変換
const blob = await response.blob();
// ファイル名を取得(Content-Dispositionヘッダーから)
const contentDisposition = response.headers.get('Content-Disposition');
const filename = contentDisposition
? contentDisposition.split('filename=')[1]?.replace(/"/g, '')
: `file-${fileId}`;
// ダウンロード
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
} catch (error) {
console.error('ダウンロードエラー:', error);
} finally {
setLoading(false);
}
};
return (
<button onClick={() => downloadFile(1)} disabled={loading}>
{loading ? 'ダウンロード中...' : 'ファイルをダウンロード'}
</button>
);
}
# Next.jsからLaravelにBlobをアップロード
// app/components/FileUploader.tsx
'use client';
import { useState } from 'react';
export default function FileUploader() {
const [uploading, setUploading] = useState(false);
const uploadFile = async (file: File) => {
setUploading(true);
try {
// FormDataを作成
const formData = new FormData();
formData.append('file', file);
formData.append('filename', file.name);
// Laravel APIにアップロード
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/files`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`,
},
body: formData,
});
if (!response.ok) {
throw new Error('アップロードに失敗しました');
}
const data = await response.json();
console.log('アップロード成功:', data);
} catch (error) {
console.error('アップロードエラー:', error);
} finally {
setUploading(false);
}
};
return (
<input
type="file"
onChange={(e) => {
const file = e.target.files?.[0];
if (file) {
uploadFile(file);
}
}}
disabled={uploading}
/>
);
}
# ストリーミング処理の連携
大きなファイルをストリーミングで処理する例です。
// app/utils/streamBlob.ts
export async function streamBlobFromLaravel(
fileId: number,
onProgress?: (progress: number) => void
): Promise<Blob> {
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/files/${fileId}/stream`,
{
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`,
},
}
);
if (!response.ok) {
throw new Error('ストリーミングに失敗しました');
}
const reader = response.body?.getReader();
if (!reader) {
throw new Error('リーダーを取得できませんでした');
}
const chunks: Uint8Array[] = [];
const contentLength = parseInt(response.headers.get('Content-Length') || '0', 10);
let received = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
received += value.length;
if (onProgress && contentLength > 0) {
const progress = (received / contentLength) * 100;
onProgress(progress);
}
}
return new Blob(chunks, { type: response.headers.get('Content-Type') || 'application/octet-stream' });
}
# 実用的な例
# 画像編集アプリケーション
// app/components/ImageEditor.tsx
'use client';
import { useState, useRef } from 'react';
export default function ImageEditor() {
const canvasRef = useRef<HTMLCanvasElement>(null);
const [imageBlob, setImageBlob] = useState<Blob | null>(null);
const loadImage = async (file: File) => {
const blob = await file.arrayBuffer();
const imageBlob = new Blob([blob], { type: file.type });
setImageBlob(imageBlob);
const img = new Image();
img.src = URL.createObjectURL(imageBlob);
img.onload = () => {
const canvas = canvasRef.current;
if (canvas) {
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx?.drawImage(img, 0, 0);
}
};
};
const saveImage = async () => {
const canvas = canvasRef.current;
if (!canvas) return;
canvas.toBlob(async (blob) => {
if (!blob) return;
// Laravel APIに保存
const formData = new FormData();
formData.append('file', blob, 'edited-image.png');
await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/files`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`,
},
body: formData,
});
}, 'image/png');
};
return (
<div>
<input type="file" accept="image/*" onChange={(e) => {
const file = e.target.files?.[0];
if (file) loadImage(file);
}} />
<canvas ref={canvasRef} />
<button onClick={saveImage}>保存</button>
</div>
);
}
# 注意点とベストプラクティス
# 1. メモリ管理
Blobオブジェクトはメモリを消費するため、使用後は適切に解放する必要があります。
useEffect(() => {
const url = URL.createObjectURL(blob);
return () => {
URL.revokeObjectURL(url); // クリーンアップ
};
}, [blob]);
# 2. エラーハンドリング
Blobの処理中にエラーが発生する可能性があるため、適切なエラーハンドリングが必要です。
try {
const blob = await response.blob();
// 処理
} catch (error) {
console.error('Blob処理エラー:', error);
// エラー処理
}
# 3. ファイルサイズの制限
大きなファイルを扱う場合は、ストリーミング処理を検討します。
# 4. CORS設定
LaravelとNext.jsが異なるドメインで動作する場合、CORS設定が必要です。
// Laravel: config/cors.php または middleware
header('Access-Control-Allow-Origin: http://localhost:3000');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
# 実装時の注意点
LaravelとNext.jsでBlobファイルを扱う際のポイント:
- Laravel側: バイナリデータの生成、保存、配信を効率的に処理
- Next.js側: クライアント側でBlobを作成、表示、ダウンロード
- 連携: APIを通じてBlobデータを送受信し、ストリーミング処理も可能
- メモリ管理: 適切なクリーンアップでメモリリークを防止
適切に実装することで、効率的でユーザーフレンドリーなファイル処理機能を提供できる。