Skip to content

Instantly share code, notes, and snippets.

@SuaYoo
Last active March 11, 2024 16:43
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save SuaYoo/c6b7d0e17da9915cbdc171ab58152275 to your computer and use it in GitHub Desktop.
Save SuaYoo/c6b7d0e17da9915cbdc171ab58152275 to your computer and use it in GitHub Desktop.
Uploading an image with Next.js and Blazeback B2 (AWS S3 alternative)
BACKBLAZE_BUCKET_ID='YOUR_BACKBLAZE_BUCKET_ID'
BACKBLAZE_KEY_ID='YOUR_BACKBLAZE_KEY_ID'
BACKBLAZE_APP_KEY='YOUR_BACKBLAZE_APP_KEY'
import axios from 'axios';
import { useState } from 'react';
export default function ImageUpload() {
const [previewFile, setPreviewFile] = useState();
const handleChange = (e) => {
const files = e.target.files;
const file = files[files.length - 1];
if (file) {
// construct data uri for previewing the image
// before sending it to the server
const fileReader = new FileReader();
fileReader.readAsDataURL(file);
fileReader.addEventListener('load', () => {
setPreviewFile({
file,
base64: fileReader.result,
});
});
} else {
setPreviewFile(null);
}
};
const upload = async () => {
const { file } = previewFile;
// rename the file before sending if you want
const fileExt = file.name.substring(file.name.lastIndexOf('.') + 1);
const fileName = `image.${fileExt}`;
try {
// pass the instance of File, not the base64 string, to the server
const { data } = await axios.post(`/api/media/upload`, file, {
headers: {
'content-type': file.type,
// TODO figure out how to parse file name from form data on the server
'x-filename': fileName,
},
});
// do something with the returned data, like store
// the URL returned from the server
} catch (err) {
console.error(err);
}
setPreviewFile();
};
return (
<div>
<div>
{previewFile && <img src={previewFile.base64} />}
</div>
<input
type="file"
id="my-image-id"
name="my-image-id"
onChange={handleChange}
accept="image/png, image/jpeg"
/>
<button onClick={upload}>Submit</button>
</div>
);
}
import B2 from 'backblaze-b2';
const uploadHandler = async (req, res) => {
// reconstruct file buffer from stream
const file = await new Promise((resolve) => {
const chunks = [];
req.on('readable', () => {
let chunk;
while (null !== (chunk = req.read())) {
chunks.push(chunk);
}
});
req.on('end', () => {
resolve(Buffer.concat(chunks));
});
});
const b2 = new B2({
applicationKeyId: process.env.BACKBLAZE_KEY_ID,
applicationKey: process.env.BACKBLAZE_APP_KEY,
});
// b2 auth tokens are valid for 24 hours
// .authorize returns the download url,
// .getUploadUrl returns the upload url and auth token
const { data: authData } = await b2.authorize();
const { data: uploadData } = await b2.getUploadUrl({
bucketId: process.env.BACKBLAZE_BUCKET_ID,
});
// TODO figure out how to parse file name from form data
const reqFileName = req.headers['x-filename'];
const { data } = await b2.uploadFile({
uploadUrl: uploadData.uploadUrl,
uploadAuthToken: uploadData.authorizationToken,
data: file,
// there are no real directories in b2, if you want to place
// your file in a folder structure, do so with slashes. ex:
// fileName: `/my-subfolder/uploads/${fileName}`
fileName,
// info: {}, // store optional info, like original file name
});
// construct friendly url to return in the response
const bucketName = authData.allowed.bucketName;
const downloadURL = authData.downloadUrl;
res.status(200).json({
// add timestamp to url to force re-fetching images with the same src
url: `${downloadURL}/file/${bucketName}/${data.fileName}?timestamp=${data.uploadTimestamp}`,
});
};
// tell next.js to disable body parsing and handle as a stream
export const config = {
api: {
bodyParser: false,
},
};
export default uploadHandler;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment