Pular para o conteúdoPedro Farbo
Lição 14 / 2540 min

Upload de Arquivos

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/multer

Upload 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-presigner
typescript
// 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! 📧

Gostou do conteúdo? Sua contribuição ajuda a manter tudo online e gratuito!

PIX:0737160d-e98f-4a65-8392-5dba70e7ff3e