Upload de Arquivos
Gerenciar uploads é comum em APIs. Vamos implementar upload local e em cloud (S3).
Instalação
bash
npm install multernpm install -D @types/multerUpload Local
typescript
// src/config/upload.tsimport multer from 'multer';import path from 'path';import { randomUUID } from 'crypto'; const uploadDir = path.resolve(__dirname, '..', '..', 'uploads'); const storage = multer.diskStorage({ destination: (req, file, cb) => { cb(null, uploadDir); }, filename: (req, file, cb) => { const ext = path.extname(file.originalname); const filename = `${randomUUID()}${ext}`; cb(null, filename); },}); const fileFilter = (req: any, file: Express.Multer.File, cb: multer.FileFilterCallback) => { const allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']; if (allowedMimes.includes(file.mimetype)) { cb(null, true); } else { cb(new Error('Tipo de arquivo não permitido')); }}; export const upload = multer({ storage, fileFilter, limits: { fileSize: 5 * 1024 * 1024, // 5MB },});Rotas de Upload
typescript
// src/routes/upload.routes.tsimport { Router } from 'express';import { upload } from '../config/upload';import { authenticate } from '../middlewares/auth.middleware'; const router = Router(); // Upload únicorouter.post('/single', authenticate, upload.single('image'), (req, res) => { if (!req.file) { return res.status(400).json({ error: 'Nenhum arquivo enviado' }); } res.json({ success: true, data: { filename: req.file.filename, path: `/uploads/${req.file.filename}`, size: req.file.size, }, });}); // Upload múltiplorouter.post('/multiple', authenticate, upload.array('images', 10), (req, res) => { const files = req.files as Express.Multer.File[]; res.json({ success: true, data: files.map(file => ({ filename: file.filename, path: `/uploads/${file.filename}`, size: file.size, })), });}); export default router;Upload para AWS S3
bash
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presignertypescript
// src/services/storage.service.tsimport { S3Client, PutObjectCommand, DeleteObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';import { getSignedUrl } from '@aws-sdk/s3-request-presigner';import { randomUUID } from 'crypto'; const s3Client = new S3Client({ region: process.env.AWS_REGION!, credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID!, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!, },}); const BUCKET = process.env.AWS_S3_BUCKET!; export class StorageService { async upload(file: Express.Multer.File, folder: string = 'uploads'): Promise<string> { const ext = file.originalname.split('.').pop(); const key = `${folder}/${randomUUID()}.${ext}`; await s3Client.send(new PutObjectCommand({ Bucket: BUCKET, Key: key, Body: file.buffer, ContentType: file.mimetype, })); return `https://${BUCKET}.s3.amazonaws.com/${key}`; } async delete(url: string): Promise<void> { const key = url.split('.com/')[1]; await s3Client.send(new DeleteObjectCommand({ Bucket: BUCKET, Key: key, })); } async getSignedUrl(key: string, expiresIn: number = 3600): Promise<string> { const command = new GetObjectCommand({ Bucket: BUCKET, Key: key }); return getSignedUrl(s3Client, command, { expiresIn }); }}Usando em Produtos
typescript
// src/routes/product.routes.tsrouter.post('/:id/images', authenticate, authorize('ADMIN'), upload.array('images', 5), asyncHandler(async (req, res) => { const files = req.files as Express.Multer.File[]; const storageService = new StorageService(); const urls = await Promise.all( files.map(file => storageService.upload(file, 'products')) ); const product = await productService.addImages(req.params.id, urls); res.json({ success: true, data: product }); }));Resumo
- ✅ Multer para processar uploads
- ✅ Validação de tipo e tamanho
- ✅ Upload local e para S3
- ✅ URLs assinadas para arquivos privados
Próxima aula: Envio de E-mails! 📧