Amazon S3 — fayl saqlash, upload, delete, signed URL
Muammodan boshlaylik
Fayllarni serverda uploads/ papkasiga saqlayapsiz. Hozir ishlaydi. Lekin:
- Server o'chirib yoqilsa — fayllar o'chadi (agar persistent storage yo'q bo'lsa)
- Ikki server ishlatilsa (load balancing) — biri yuklagan fayl ikkinchisida yo'q
- Disk to'lsa — yangi fayllar yuklanmaydi
- Foydalanuvchi Toshkentda, server Germaniyadа — rasm yuklash sekin
Amazon S3 (Simple Storage Service) bu muammolarni hal qiladi: cheksiz joy, global tarqatish, 99.999999999% ishonchlilik.
S3 nima?
S3 — Amazon ning bulut fayl saqlash xizmati. Fayllar bucket (chelak) da saqlanadi. Har bir fayl — object (obyekt), uning key (nomi/yo'li) bor.
Hayotiy o'xshatma
Tasavvur qiling: ulkan pochta ombori. Omborning o'zi — S3. Har bir yachiyka — bucket. Yachiykaдаги har bir paket — object. Paketning manzili — key.
S3
├── bucket: mening-loyiham-rasmlar
│ ├── profil/user-123.webp ← key
│ ├── profil/user-456.webp
│ └── mahsulot/iphone-front.webp
└── bucket: mening-loyiham-hujjatlar
├── shartnomalar/2024-001.pdf
└── hisobotlar/yanvar.xlsx
O'rnatish va sozlash
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
# .env
AWS_REGION=eu-central-1
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
AWS_S3_BUCKET=mening-loyiham-rasmlar
// config/s3.js
const { S3Client } = require('@aws-sdk/client-s3');
const s3 = new S3Client({
region: process.env.AWS_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
},
});
module.exports = s3;
Fayl yuklash (Upload)
const { PutObjectCommand } = require('@aws-sdk/client-s3');
const s3 = require('./config/s3');
const multer = require('multer');
const sharp = require('sharp');
const { v4: uuidv4 } = require('uuid'); // npm install uuid
const upload = multer({
storage: multer.memoryStorage(),
limits: { fileSize: 10 * 1024 * 1024 },
fileFilter: (req, file, cb) => {
const allowed = ['image/jpeg', 'image/png', 'image/webp'];
allowed.includes(file.mimetype) ? cb(null, true) : cb(new Error('Faqat rasm'));
}
});
app.post('/profil/rasm', upload.single('rasm'), async (req, res) => {
if (!req.file) {
return res.status(400).json({ error: 'Rasm yuborilmadi' });
}
// Sharp bilan qayta ishlash
const buffer = await sharp(req.file.buffer)
.resize(300, 300, { fit: 'cover' })
.webp({ quality: 85 })
.toBuffer(); // ← faylga emas, bufferga
const key = `profil/${uuidv4()}.webp`; // ← noyob yo'l
// S3 ga yuklash
await s3.send(new PutObjectCommand({
Bucket: process.env.AWS_S3_BUCKET, // ← bucket nomi
Key: key, // ← fayl yo'li
Body: buffer, // ← fayl ma'lumoti
ContentType: 'image/webp', // ← fayl turi
}));
const url = `https://${process.env.AWS_S3_BUCKET}.s3.${process.env.AWS_REGION}.amazonaws.com/${key}`;
res.json({ message: 'Yuklandi', url, key });
});
key da papka tuzilishi nima uchun? profil/, mahsulot/ kabi prefix lar S3 da "papka" tashkil qiladi. Keyin faqat profil rasmlarini ko'rish, narx hisoblash yoki o'chirish oson bo'ladi.
Fayl o'chirish (Delete)
const { DeleteObjectCommand } = require('@aws-sdk/client-s3');
app.delete('/profil/rasm', async (req, res) => {
const { key } = req.body; // ← masalan: "profil/abc-123.webp"
await s3.send(new DeleteObjectCommand({
Bucket: process.env.AWS_S3_BUCKET,
Key: key,
}));
res.json({ message: 'O\'chirildi' });
});
Fayl o'qish (Get)
Agar bucket public bo'lsa — to'g'ridan-to'g'ri URL orqali kirish mumkin. Lekin ko'plab hollarda bucket private bo'lishi kerak — maxfiy hujjatlar, faqat o'z foydalanuvchisiga ko'rinadigan fayllar.
Private bucket — Signed URL
Signed URL — vaqtincha amal qiladigan maxsus havola. Foydalanuvchi bu havola orqali S3 ga to'g'ridan-to'g'RI kirishi mumkin — serveringizdan o'tmasdan:
const { GetObjectCommand } = require('@aws-sdk/client-s3');
const { getSignedUrl } = require('@aws-sdk/s3-request-presigner');
app.get('/fayl/:key', async (req, res) => {
const command = new GetObjectCommand({
Bucket: process.env.AWS_S3_BUCKET,
Key: req.params.key,
});
// 1 soat amal qiladigan URL
const signedUrl = await getSignedUrl(s3, command, { expiresIn: 3600 });
// ↑ soniyada
res.json({ url: signedUrl });
});
Signed URL qanday ko'rinadi:
https://bucket.s3.eu-central-1.amazonaws.com/hujjat/shartnoma.pdf
?X-Amz-Algorithm=AWS4-HMAC-SHA256
&X-Amz-Credential=AKIAIOSFODNN7...
&X-Amz-Date=20240115T120000Z
&X-Amz-Expires=3600
&X-Amz-Signature=abc123...
Nima uchun Signed URL? Har bir fayl so'rovini serveringiz orqali o'tkazsangiz — server banda bo'ladi, katta fayllar uchun internet chiqimi to'laysiz. Signed URL bilan foydalanuvchi S3 dan to'g'ridan-to'g'RI yuklab oladi — server zimmasidan tushadi.
Upload uchun Signed URL (frontend dan to'g'ridan-to'g'RI S3 ga)
Ba'zan frontend S3 ga to'g'ridan-to'g'RI yuklashi kerak — server orqali o'tmasdan. Masalan, 100 MB video fayllar:
const { PutObjectCommand } = require('@aws-sdk/client-s3');
const { getSignedUrl } = require('@aws-sdk/s3-request-presigner');
// Frontend bu URL ni so'raydi, keyin to'g'ridan-to'g'RI S3 ga yuklaydi
app.get('/upload-url', async (req, res) => {
const { contentType } = req.query; // ← masalan: "image/webp"
const key = `uploads/${uuidv4()}`;
const command = new PutObjectCommand({
Bucket: process.env.AWS_S3_BUCKET,
Key: key,
ContentType: contentType,
});
const uploadUrl = await getSignedUrl(s3, command, { expiresIn: 300 }); // ← 5 daqiqa
res.json({ uploadUrl, key });
});
Frontend da:
// 1. Server dan upload URL olish
const { uploadUrl, key } = await fetch('/upload-url?contentType=image/webp')
.then(r => r.json());
// 2. To'g'ridan-to'g'RI S3 ga yuklash (server orqali o'tmaydi)
await fetch(uploadUrl, {
method: 'PUT',
body: file, // ← File obyekti
headers: { 'Content-Type': 'image/webp' },
});
// 3. Serverga faqat key ni xabar berish
await fetch('/profil/rasm-saqlash', {
method: 'POST',
body: JSON.stringify({ key }),
headers: { 'Content-Type': 'application/json' },
});
To'liq yordamchi modul
Kod takrorlanishini kamaytirish uchun alohida modul:
// utils/s3-helpers.js
const { PutObjectCommand, DeleteObjectCommand, GetObjectCommand } = require('@aws-sdk/client-s3');
const { getSignedUrl } = require('@aws-sdk/s3-request-presigner');
const s3 = require('../config/s3');
const BUCKET = process.env.AWS_S3_BUCKET;
const REGION = process.env.AWS_REGION;
const uploadToS3 = async (buffer, key, contentType) => {
await s3.send(new PutObjectCommand({
Bucket: BUCKET,
Key: key,
Body: buffer,
ContentType: contentType,
}));
return `https://${BUCKET}.s3.${REGION}.amazonaws.com/${key}`;
};
const deleteFromS3 = async (key) => {
await s3.send(new DeleteObjectCommand({ Bucket: BUCKET, Key: key }));
};
const getSignedDownloadUrl = async (key, expiresIn = 3600) => {
const command = new GetObjectCommand({ Bucket: BUCKET, Key: key });
return getSignedUrl(s3, command, { expiresIn });
};
module.exports = { uploadToS3, deleteFromS3, getSignedDownloadUrl };
Ishlatish:
const { uploadToS3, deleteFromS3 } = require('./utils/s3-helpers');
app.post('/rasm', upload.single('rasm'), async (req, res) => {
const buffer = await sharp(req.file.buffer).resize(800).webp({ quality: 85 }).toBuffer();
const key = `rasmlar/${uuidv4()}.webp`;
const url = await uploadToS3(buffer, key, 'image/webp');
res.json({ url, key });
});
app.delete('/rasm/:key', async (req, res) => {
await deleteFromS3(req.params.key);
res.json({ message: 'O\'chirildi' });
});
Tez-tez uchraydigan xatolar
1. AWS kalitlarini kodga yozish
// ❌
const s3 = new S3Client({
credentials: {
accessKeyId: 'AKIAIOSFODNN7EXAMPLE', // ← GitHub da ochiq!
secretAccessKey: 'wJalrXUtnFEMI/K7MDENG...',
}
});
Sababi: GitHub da yuzlab bot repository larni skanerlaydi — kalitlar topilsa darhol AWS hisobingizni ishlatib, ming dollarlik to'lov chiqishi mumkin. Har doim .env dan oling.
2. Bucket ni to'liq public qilish
// ❌ — hamma fayllar internetga ochiq
// Bucket Policy da: "Effect": "Allow", "Principal": "*"
Sababi: maxfiy hujjatlar, foydalanuvchi ma'lumotlari — hammasi ochiq bo'lib qoladi. Private bucket + Signed URL ishlatish kerak.
3. Key da bo'shliq yoki maxsus belgilar
// ❌
const key = `profil/${req.body.username}.jpg`; // ← "Ali Valiyev.jpg"
Sababi: bo'shliq va ba'zi belgilar URL da muammo chiqaradi. uuid yoki Date.now() + random son ishlatgan ma'qul.
4. S3 xatolarini ushlash
// ❌ — xato ushlashsiz
await s3.send(new PutObjectCommand({ ... }));
// ✅
try {
await s3.send(new PutObjectCommand({ ... }));
} catch (err) {
if (err.name === 'NoSuchBucket') {
return res.status(500).json({ error: 'Bucket topilmadi' });
}
throw err;
}
Sababi: tarmoq uzilishi, noto'g'ri bucket nomi, ruxsat yo'qligi — bular alohida xato turlari. Ushlanmasa, foydalanuvchi serverda "500 Internal Error" ko'radi, sabab noma'lum.
Xulosa
- S3 — cheksiz, ishonchli, global bulut fayl saqlash
PutObjectCommand— yuklash,DeleteObjectCommand— o'chirish- Signed URL — private fayllar uchun vaqtincha havola
- Frontend dan to'g'ridan-to'g'RI S3 ga yuklash — server yukini kamaytiradi
- Kalitlarni hech qachon kodga yozmang —
.envdan oling
Keyingi dars: 35-dars: Streaming — katta fayllarni stream qilish