// server.js
const express = require('express');
const multer = require('multer');
const { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectCommand } = require('@aws-sdk/client-s3');
const { getSignedUrl } = require('@aws-sdk/s3-request-presigner');
const crypto = require('crypto');
const path = require('path');
const fs = require('fs');
require('dotenv').config();
const app = express();
const port = process.env.PORT || 3000;
// Cloudflare R2 configuration
const r2Client = new S3Client({
region: 'auto',
endpoint: `https://${process.env.CLOUDFLARE_ACCOUNT_ID}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
},
});
const BUCKET_NAME = process.env.R2_BUCKET_NAME;
// Configure multer for file uploads
const upload = multer({
dest: 'uploads/',
limits: {
fileSize: 100 * 1024 * 1024, // 100MB limit
},
});
// Middleware
app.use(express.json());
app.use(express.static('public'));
// Helper function to generate unique filename
const generateFileName = (originalName) => {
const ext = path.extname(originalName);
const name = path.basename(originalName, ext);
const timestamp = Date.now();
const random = crypto.randomBytes(8).toString('hex');
return `${name}-${timestamp}-${random}${ext}`;
};
// Upload file to R2
app.post('/upload', upload.single('file'), async (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ error: 'No file uploaded' });
}
const fileName = generateFileName(req.file.originalname);
const fileBuffer = fs.readFileSync(req.file.path);
const uploadParams = {
Bucket: BUCKET_NAME,
Key: fileName,
Body: fileBuffer,
ContentType: req.file.mimetype,
Metadata: {
originalName: req.file.originalname,
uploadedAt: new Date().toISOString(),
}
};
// Upload to R2
const command = new PutObjectCommand(uploadParams);
await r2Client.send(command);
// Clean up temporary file
fs.unlinkSync(req.file.path);
// Generate public URL (if bucket is configured for public access)
const publicUrl = `https://${process.env.R2_PUBLIC_DOMAIN}/${fileName}`;
res.json({
success: true,
message: 'File uploaded successfully',
data: {
fileName,
originalName: req.file.originalname,
size: req.file.size,
mimetype: req.file.mimetype,
url: publicUrl,
key: fileName
}
});
} catch (error) {
console.error('Upload error:', error);
// Clean up temporary file if it exists
if (req.file && fs.existsSync(req.file.path)) {
fs.unlinkSync(req.file.path);
}
res.status(500).json({
error: 'Failed to upload file',
details: error.message
});
}
});
// Upload multiple files
app.post('/upload/multiple', upload.array('files', 10), async (req, res) => {
try {
if (!req.files || req.files.length === 0) {
return res.status(400).json({ error: 'No files uploaded' });
}
const uploadPromises = req.files.map(async (file) => {
const fileName = generateFileName(file.originalname);
const fileBuffer = fs.readFileSync(file.path);
const uploadParams = {
Bucket: BUCKET_NAME,
Key: fileName,
Body: fileBuffer,
ContentType: file.mimetype,
Metadata: {
originalName: file.originalname,
uploadedAt: new Date().toISOString(),
}
};
const command = new PutObjectCommand(uploadParams);
await r2Client.send(command);
// Clean up temporary file
fs.unlinkSync(file.path);
return {
fileName,
originalName: file.originalname,
size: file.size,
mimetype: file.mimetype,
url: `https://${process.env.R2_PUBLIC_DOMAIN}/${fileName}`,
key: fileName
};
});
const uploadedFiles = await Promise.all(uploadPromises);
res.json({
success: true,
message: `${uploadedFiles.length} files uploaded successfully`,
data: uploadedFiles
});
} catch (error) {
console.error('Multiple upload error:', error);
// Clean up temporary files
if (req.files) {
req.files.forEach(file => {
if (fs.existsSync(file.path)) {
fs.unlinkSync(file.path);
}
});
}
res.status(500).json({
error: 'Failed to upload files',
details: error.message
});
}
});
// Generate presigned URL for direct upload
app.post('/upload/presigned', async (req, res) => {
try {
const { fileName, contentType } = req.body;
if (!fileName || !contentType) {
return res.status(400).json({
error: 'fileName and contentType are required'
});
}
const key = generateFileName(fileName);
const command = new PutObjectCommand({
Bucket: BUCKET_NAME,
Key: key,
ContentType: contentType,
});
const signedUrl = await getSignedUrl(r2Client, command, {
expiresIn: 3600 // 1 hour
});
res.json({
success: true,
data: {
uploadUrl: signedUrl,
key,
expiresIn: 3600
}
});
} catch (error) {
console.error('Presigned URL error:', error);
res.status(500).json({
error: 'Failed to generate presigned URL',
details: error.message
});
}
});
// Get file info
app.get('/file/:key', async (req, res) => {
try {
const { key } = req.params;
const command = new GetObjectCommand({
Bucket: BUCKET_NAME,
Key: key,
});
const response = await r2Client.send(command);
res.json({
success: true,
data: {
key,
contentType: response.ContentType,
contentLength: response.ContentLength,
lastModified: response.LastModified,
metadata: response.Metadata,
url: `https://${process.env.R2_PUBLIC_DOMAIN}/${key}`
}
});
} catch (error) {
if (error.name === 'NoSuchKey') {
return res.status(404).json({ error: 'File not found' });
}
console.error('Get file error:', error);
res.status(500).json({
error: 'Failed to get file info',
details: error.message
});
}
});
// Delete file
app.delete('/file/:key', async (req, res) => {
try {
const { key } = req.params;
const command = new DeleteObjectCommand({
Bucket: BUCKET_NAME,
Key: key,
});
await r2Client.send(command);
res.json({
success: true,
message: 'File deleted successfully'
});
} catch (error) {
console.error('Delete file error:', error);
res.status(500).json({
error: 'Failed to delete file',
details: error.message
});
}
});
// Error handling middleware
app.use((error, req, res, next) => {
if (error instanceof multer.MulterError) {
if (error.code === 'LIMIT_FILE_SIZE') {
return res.status(400).json({ error: 'File too large' });
}
if (error.code === 'LIMIT_FILE_COUNT') {
return res.status(400).json({ error: 'Too many files' });
}
}
console.error('Server error:', error);
res.status(500).json({ error: 'Internal server error' });
});
// Health check
app.get('/health', (req, res) => {
res.json({ status: 'OK', timestamp: new Date().toISOString() });
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
console.log(`R2 Bucket: ${BUCKET_NAME}`);
});
// Ensure uploads directory exists
if (!fs.existsSync('uploads')) {
fs.mkdirSync('uploads');
}
Created
September 1, 2025 17:10
-
-
Save siumhossain/c766b67b035daf142e1200490263745f to your computer and use it in GitHub Desktop.
cloudflare r2 upload with express js
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment