JAVASCRIPT

Secure Client-Side File Upload to AWS S3 using Pre-signed URLs

Enable secure direct client-to-S3 file uploads by generating temporary pre-signed URLs from a Node.js backend, improving performance and scalability.

// --- Node.js Backend (server.js) ---
const express = require('express');
const AWS = require('aws-sdk'); // Make sure to install: npm install aws-sdk
const cors = require('cors'); // Make sure to install: npm install cors

const app = express();
const port = 3001;

// Configure AWS SDK
AWS.config.update({
    accessKeyId: process.env.AWS_ACCESS_KEY_ID,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
    region: process.env.AWS_REGION || 'us-east-1' // e.g., 'us-east-1'
});

const s3 = new AWS.S3();
const S3_BUCKET_NAME = process.env.S3_BUCKET_NAME;

app.use(cors()); // Enable CORS for client-side requests
app.use(express.json());

// Endpoint to request a pre-signed URL for upload
app.post('/generate-presigned-url', (req, res) => {
    const { filename, filetype } = req.body;

    if (!filename || !filetype) {
        return res.status(400).json({ error: 'Filename and filetype are required.' });
    }

    const params = {
        Bucket: S3_BUCKET_NAME,
        Key: `uploads/${Date.now()}-${filename}`, // Unique key for the file
        Expires: 300, // URL expires in 300 seconds (5 minutes)
        ContentType: filetype,
        ACL: 'public-read' // Or 'private' if you control access
    };

    s3.getSignedUrl('putObject', params, (err, url) => {
        if (err) {
            console.error('Error generating pre-signed URL:', err);
            return res.status(500).json({ error: 'Failed to generate pre-signed URL.' });
        }
        res.json({ presignedUrl: url, fileKey: params.Key });
    });
});

app.listen(port, () => {
    console.log(`Backend server for S3 pre-signed URLs listening at http://localhost:${port}`);
    console.log('Ensure AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, S3_BUCKET_NAME are set.');
});


// --- Client-Side JavaScript (e.g., in your React/Vue/Vanilla JS frontend) ---
/*
// Example HTML input: <input type="file" id="fileInput"> <button onclick="uploadFile()">Upload</button>

async function uploadFile() {
    const fileInput = document.getElementById('fileInput');
    const file = fileInput.files[0];

    if (!file) {
        alert('Please select a file first.');
        return;
    }

    try {
        // Step 1: Request a pre-signed URL from your backend
        const response = await fetch('http://localhost:3001/generate-presigned-url', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                filename: file.name,
                filetype: file.type,
            }),
        });

        if (!response.ok) {
            throw new Error(`Backend error: ${response.statusText}`);
        }

        const { presignedUrl, fileKey } = await response.json();
        console.log('Received pre-signed URL:', presignedUrl);

        // Step 2: Upload the file directly to S3 using the pre-signed URL
        const s3UploadResponse = await fetch(presignedUrl, {
            method: 'PUT',
            headers: {
                'Content-Type': file.type,
            },
            body: file, // Send the raw file data
        });

        if (!s3UploadResponse.ok) {
            throw new Error(`S3 upload error: ${s3UploadResponse.statusText}`);
        }

        alert('File uploaded successfully to S3!');
        console.log('File successfully uploaded to S3. Key:', fileKey);
        // You might want to send 'fileKey' back to your backend to save in a database
    } catch (error) {
        console.error('Error during file upload process:', error);
        alert(`File upload failed: ${error.message}`);
    }
}

// Call uploadFile() from an event listener, e.g., a button click
// document.getElementById('uploadButton').addEventListener('click', uploadFile);
*/
How it works: This snippet demonstrates a secure and efficient way to handle file uploads by integrating with AWS S3 using pre-signed URLs. The Node.js backend generates a temporary, time-limited URL that allows the client-side JavaScript to directly upload a file to the S3 bucket without exposing AWS credentials to the frontend. This offloads the heavy lifting of file transfer from your backend, improving scalability, performance, and security, as your server only needs to authenticate the request for the URL, not process the file stream itself.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs