المستوى المطلوب: متوسط — محتاج تكون مرتاح مع Node.js وفكرة الـ HTTP requests، ومعندكش مشكلة تفتح حساب S3 أو R2.
بعد المقال ده سيرفرك مش هيشيل وزن رفع الملفات خالص. المتصفح هيرفع الصورة أو الفيديو على S3 مباشرة، وسيرفرك دوره هيبقى بس إنه يوقّع تذكرة دخول مؤقتة. النتيجة: استهلاك أقل، رفع أسرع، وفاتورة bandwidth أنضف.
المشكلة باختصار
الطريقة الشائعة إن الملف يترفع على سيرفرك الأول، وبعدين سيرفرك يرفعه على S3. الطريقة دي بتفشل لمّا الملفات تكبر أو العدد يزيد.
تخيّل متجر بيرفع 2000 صورة منتج في اليوم، كل واحدة 5 ميجا. ده معناه 10 جيجا في اليوم بتعدّي على سيرفرك من غير أي داعي. كل request بيقفل جزء من الـ RAM وطول الـ bandwidth، والـ worker بيفضل مشغول ثواني لحد ما الملف يخلّص. على VPS صغير، ده بيخنق باقي الطلبات العادية.
إزاي الـ Presigned URL بيشتغل
قبل التعريف العلمي، خد المثال ده. تخيّل إنك صاحب مخزن مقفول. صديقك عايز يدخل يحط صندوق جوه. انت مش محتاج تروح معاه وتحمل الصندوق بنفسك. انت بتديله مفتاح مؤقت يفتح باب واحد بس، صالح دقيقة واحدة، وبيقفل لوحده بعدها. هو يدخل يحط الصندوق ويمشي، وانت مقعدتش من مكانك.
الـ Presigned URL بالظبط كده. هو رابط مؤقت بيوقّعه سيرفرك بمفتاح S3 السري. الرابط بيقول لـ S3: "اسمح للي معاه الرابط ده إنه يرفع ملف في المكان الفلاني، بشرط معيّن، ولفترة محدودة". المتصفح بيستخدم الرابط ده ويرفع الملف على S3 مباشرة. مفتاحك السري عمره ما بيوصل للمتصفح، وسيرفرك مبيلمسش بايت واحد من الملف.
الخطوات
- الباك إند: endpoint بيستقبل اسم الملف ونوعه، ويرجّع Presigned URL صالح لثواني معدودة.
- الفرونت إند: يطلب الرابط الموقّع، وبعدين يعمل
PUTبالملف على الرابط ده مباشرة. - إعداد CORS على الـ bucket علشان المتصفح يُسمح له يرفع من دومينك.
- حفظ الـ key في قاعدة بياناتك بعد نجاح الرفع.
1) الباك إند (Node.js + AWS SDK v3)
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import crypto from "node:crypto";
const s3 = new S3Client({ region: "eu-central-1" });
// POST /uploads/presign { filename, contentType }
app.post("/uploads/presign", async (req, res) => {
const { filename, contentType } = req.body;
// اسم فريد علشان الملفات ماتدوسش على بعض
const key = `uploads/${Date.now()}-${crypto.randomUUID()}-${filename}`;
const command = new PutObjectCommand({
Bucket: "my-app-uploads",
Key: key,
ContentType: contentType,
});
// الرابط صالح 60 ثانية بس
const url = await getSignedUrl(s3, command, { expiresIn: 60 });
res.json({ url, key });
});