Uma aplicação React moderna para upload de arquivos utilizando URLs pré-assinadas do Amazon S3. O projeto oferece uma interface intuitiva de drag-and-drop com acompanhamento de progresso em tempo real e tratamento robusto de erros.
Este projeto demonstra uma implementação completa e robusta para upload de arquivos para AWS S3 utilizando URLs pré-assinadas. A arquitetura serverless garante que o backend controle as permissões de upload enquanto o frontend realiza o upload diretamente para o S3, com rastreamento automático de status através de triggers e DynamoDB, proporcionando uma solução escalável e eficiente.
- 🎯 Interface drag-and-drop intuitiva
- 📊 Acompanhamento de progresso de upload em tempo real
- 🔒 Upload seguro utilizando URLs pré-assinadas do AWS S3
- 🗑️ Remoção de arquivos da fila antes do upload
- 🎨 Interface moderna e responsiva
- 🔔 Notificações de sucesso/erro
- 🚀 Upload paralelo de múltiplos arquivos
- 📊 Rastreamento automático de status via DynamoDB
- ⚡ Triggers S3 para atualização automática de status
- React 19.1.0 - Biblioteca principal para construção da interface
- TypeScript - Tipagem estática para maior robustez
- Vite - Build tool moderna e rápida
- Tailwind CSS 4.1.11 - Framework CSS utilitário
- Axios - Cliente HTTP para requisições
- React Dropzone - Componente drag-and-drop
- Lucide React - Biblioteca de ícones
- Radix UI - Componentes acessíveis (Progress, Slot)
- Sonner - Biblioteca de notificações toast
- ESLint - Linting e formatação de código
- TypeScript ESLint - Regras específicas para TypeScript
- AWS S3 - Armazenamento de arquivos
- AWS Lambda - Geração de URLs pré-assinadas e processamento de triggers
- Amazon DynamoDB - Rastreamento de status dos uploads
- S3 Event Notifications - Triggers automáticos para atualização de status
- Seleção de Arquivo: Usuário seleciona arquivos via drag-and-drop ou clique
- Requisição de URL: Frontend solicita URL pré-assinada para cada arquivo via Lambda
- Validação e Registro: Lambda valida o arquivo, gera URL com permissões temporárias e salva status "PENDING" no DynamoDB
- Upload Direto: Frontend faz upload diretamente para S3 usando a URL pré-assinada
- Trigger S3: Quando o arquivo é carregado no S3, um evento é disparado
- Atualização de Status: Lambda de trigger atualiza o status para "UPLOADED" no DynamoDB
- Progresso: Acompanhamento em tempo real do progresso de cada upload no frontend
- Node.js (versão 18 ou superior)
- npm ou yarn
- Conta AWS configurada (para o backend)
- Clone o repositório
git clone https://github.com/MarcosvBueno/aws-presigned-url-images.git
cd aws-presigned-url-images- Instale as dependências
npm install- Execute o projeto em modo de desenvolvimento
npm run dev- Acesse a aplicação Abra http://localhost:5173 no seu navegador
npm run dev # Inicia o servidor de desenvolvimento
npm run build # Gera build de produção
npm run preview # Preview do build de produção
npm run lint # Executa o linterO projeto utiliza uma arquitetura serverless completa na AWS:
- AWS Lambda (Presigned URL):
https://klh52wtuwl7c4xsbkvxdnh2v3a0pgruo.lambda-url.us-east-1.on.aws/ - AWS Lambda (S3 Trigger): Processa eventos de upload e atualiza status
- Amazon S3: Armazenamento de arquivos com event notifications
- Amazon DynamoDB: Tabela para rastreamento de status dos uploads
- Região:
us-east-1
- Geração de URL: Lambda recebe requisição e retorna URL pré-assinada
- Registro no DynamoDB: FileKey é salvo com status "PENDING"
- Upload S3: Arquivo é enviado diretamente para o bucket
- Trigger Automático: S3 dispara evento para Lambda de processamento
- Atualização de Status: DynamoDB é atualizado para "UPLOADED"
Requisição:
POST /
{
"fileName": "exemplo.png"
}Resposta:
{
"signedUrl": "https://bucket.s3.amazonaws.com/...",
"fileKey": "unique-file-key"
}Tabela de Status:
{
"fileKey": "string", // Chave primária
"status": "PENDING", // PENDING | UPLOADED | FAILED
"fileName": "string", // Nome original do arquivo
"uploadedAt": "timestamp",
"metadata": {
"size": "number",
"contentType": "string"
}
}import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { randomUUID } from 'node:crypto'
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { DynamoDBClient, PutItemCommand } from "@aws-sdk/client-dynamodb"
const s3Client = new S3Client();
const dynamoDBClient = new DynamoDBClient();
export const handler = async (event) => {
const {fileName} = JSON.parse(event.body);
if(!fileName) {
return {
statusCode: 400,
body: JSON.stringify({
error: 'file name is required'
})
}
}
const fileKey = `${randomUUID()}-${fileName}`;
const s3Command = new PutObjectCommand({
Bucket: 'nome-do-seu-bucket',
Key: fileKey,
});
const dynamoDBCommand = new PutItemCommand({
TableName: 'presigned-url-files',
Item: {
fileKey: {
S: fileKey
},
originalFileName: {
S: fileName
},
status: {
S: 'PENDING'
},
expiresAt: {
N: (Date.now() + 60000).toString()
},
}
});
const signedUrl = await getSignedUrl(s3Client, s3Command, {expiresIn: 60});
await dynamoDBClient.send(dynamoDBCommand);
return {
statusCode: 200,
body: JSON.stringify({
signedUrl,
fileKey
})
}
};import { DynamoDBClient, UpdateItemCommand } from "@aws-sdk/client-dynamodb";
import { PutCommand } from '@aws-sdk/lib-dynamodb';
const dynamoDBClient = new DynamoDBClient()
export const handler = async (event) => {
const commands = event.Records.map(record => {
return new UpdateItemCommand({
TableName: "presigned-url-files",
Key: {
fileKey: {
S: decodeURIComponent(record.s3.object.key),
}
},
UpdateExpression: "SET #status = :status REMOVE #expiresAt",
ExpressionAttributeNames: {
"#status": "status",
"#expiresAt": "expiresAt",
},
ExpressionAttributeValues: {
":status": { S: "UPLOADED" },
},
});
})
await Promise.all(commands.map(command => dynamoDBClient.send(command)))
}src/
├── components/
│ └── ui/ # Componentes UI reutilizáveis
│ ├── button.tsx
│ ├── progress.tsx
│ └── sonner.tsx
├── lib/
│ └── utils.ts # Utilitários (cn, etc.)
├── services/
│ ├── getPresignedUrl.ts # Serviço para obter URLs pré-assinadas
│ └── uploadFile.ts # Serviço para upload de arquivos
├── styles/
│ └── index.css # Estilos globais
├── App.tsx # Componente principal
└── main.tsx # Entrada da aplicação
Para executar este projeto, você precisará configurar os seguintes recursos na AWS:
-
S3 Bucket
- Bucket para armazenamento de arquivos
- Configuração de CORS para permitir uploads do frontend
- Event notifications configuradas para trigger de Lambda
-
DynamoDB Table
- Tabela com
fileKeycomo chave primária - Atributos:
status,fileName,uploadedAt,metadata
- Tabela com
-
AWS Lambda Functions
- Função para geração de URLs pré-assinadas
- Função para processamento de triggers S3
- Permissões adequadas para S3 e DynamoDB
-
IAM Roles
- Role para Lambda com permissões de S3 e DynamoDB
- Políticas de acesso adequadas
Para desenvolvimento local, você pode configurar:
VITE_LAMBDA_URL=sua-lambda-url-aqui
VITE_AWS_REGION=us-east-1
VITE_S3_BUCKET=seu-bucket-s3- Tamanho máximo: Atualmente limitado a 1MB (configurável no Lambda)
- Tipos de arquivo: PNG (configurável no Lambda)
- Região AWS: us-east-1 (configurável)
- Tabela DynamoDB: Configurável para diferentes ambientes
- Bucket S3: Configurável com diferentes políticas de acesso
Este projeto está sob a licença MIT. Veja o arquivo LICENSE para mais detalhes.
Marcos Bueno
- GitHub: @MarcosvBueno
⭐ Se este projeto te ajudou, considere dar uma estrela no repositório!