Table of Contents#
- Prerequisites
- Step 1: Set Up Your AWS S3 Bucket
- 1.1 Create an S3 Bucket
- 1.2 Configure CORS for the Bucket
- 1.3 Create an IAM User with S3 Permissions
- Step 2: Configure Webmerge to Upload Files to S3
- Step 3: Set Up the Node.js Backend
- 3.1 Initialize the Node.js Project
- 3.2 Install Dependencies
- 3.3 Configure AWS SDK v3
- 3.4 Create an Endpoint to Generate Presigned URLs
- Step 4: Build the ReactJS Frontend
- 4.1 Create a React App
- 4.2 Install Dependencies
- 4.3 Build a Download Component
- Step 5: Test the Implementation
- Troubleshooting Common Issues
- Conclusion
- References
Prerequisites#
Before starting, ensure you have the following:
- An AWS account (with permissions to create S3 buckets and IAM users).
- A Webmerge account (sign up here if you don’t have one).
- Basic knowledge of ReactJS (frontend) and Node.js/Express (backend).
- Node.js (v14+) and npm/yarn installed.
- A code editor (e.g., VS Code).
Step 1: Set Up Your AWS S3 Bucket#
1.1 Create an S3 Bucket#
First, create an S3 bucket to store Webmerge-generated files:
- Go to the AWS S3 Console.
- Click Create bucket.
- Enter a unique bucket name (e.g.,
my-webmerge-files-2024). - Choose a region (e.g.,
us-east-1). - Under "Object Ownership," select ACLs disabled (recommended for security).
- Leave other settings as default and click Create bucket.
1.2 Configure CORS for the Bucket#
CORS (Cross-Origin Resource Sharing) allows your React frontend (running on a different origin, e.g., http://localhost:3000) to access S3 resources.
- In your S3 bucket, go to the Permissions tab.
- Scroll to Cross-origin resource sharing (CORS) and click Edit.
- Paste the following CORS configuration (replace
http://localhost:3000with your frontend’s production URL later):[ { "AllowedHeaders": ["*"], "AllowedMethods": ["GET"], "AllowedOrigins": ["http://localhost:3000"], "MaxAge": 3000 } ] - Click Save changes.
1.3 Create an IAM User with S3 Permissions#
To allow your Node.js backend to access S3, create an IAM user with limited permissions:
- Go to the AWS IAM Console.
- Navigate to Users > Add users.
- Enter a username (e.g.,
s3-webmerge-downloader). - Under "Select AWS access type," check Access key - Programmatic access.
- Click Next: Permissions.
- Select Attach existing policies directly > Create policy.
- Use the JSON editor to paste this policy (replace
your-bucket-name):{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::your-bucket-name/*" } ] } - Name the policy (e.g.,
S3WebmergeGetObjectPolicy) and click Create policy.
- Use the JSON editor to paste this policy (replace
- Back in the "Add user" flow, search for your new policy and select it.
- Click Next > Create user.
- Save the Access key ID and Secret access key (you’ll need these for the backend).
Step 2: Configure Webmerge to Upload Files to S3#
Webmerge can automatically upload generated files to your S3 bucket. Here’s how to set it up:
- Log in to Webmerge.
- Go to Documents > Select your document (or create a new one).
- Click the Deliver tab > Add Delivery > Amazon S3.
- Configure the S3 integration:
- Access Key ID: Use the IAM user’s access key from Step 1.3.
- Secret Access Key: Use the IAM user’s secret key.
- Bucket Name: Your S3 bucket name (e.g.,
my-webmerge-files-2024). - Path: Optional folder (e.g.,
webmerge-output/—files will be stored ass3://bucket-name/webmerge-output/filename.pdf). - File Name: Use Webmerge’s template variables (e.g.,
{{timestamp}}-invoice.pdfto generate unique filenames).
- Save the delivery settings.
Webmerge will now upload generated files to your S3 bucket at the specified path. Note the full S3 object key (e.g., webmerge-output/20240520-invoice.pdf)—you’ll need this to download files later.
Step 3: Set Up the Node.js Backend#
The backend will generate presigned URLs for S3 objects. Presigned URLs are temporary, secure links that grant limited access to download a file (without exposing AWS credentials to the frontend).
3.1 Initialize the Node.js Project#
- Create a new folder for your backend (e.g.,
s3-download-backend). - Run
npm init -yto initialize apackage.json.
3.2 Install Dependencies#
Install required packages:
npm install express cors dotenv @aws-sdk/client-s3 @aws-sdk/s3-request-presigner express: Web framework for Node.js.cors: Enable CORS for the backend.dotenv: Load environment variables.@aws-sdk/client-s3: AWS SDK v3 for S3 interactions.@aws-sdk/s3-request-presigner: Generate presigned URLs.
3.3 Configure AWS SDK v3#
Create a .env file in your backend folder to store secrets:
PORT=5000
AWS_REGION=us-east-1
AWS_ACCESS_KEY_ID=your-iam-access-key
AWS_SECRET_ACCESS_KEY=your-iam-secret-key
S3_BUCKET_NAME=your-bucket-name
S3_WEBMERGE_PATH=webmerge-output/ # Path where Webmerge stores files (from Step 2) 3.4 Create an Endpoint to Generate Presigned URLs#
Create an index.js file (the backend entry point):
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const { S3Client, GetObjectCommand } = require('@aws-sdk/client-s3');
const { getSignedUrl } = require('@aws-sdk/s3-request-presigner');
const app = express();
// Middleware
app.use(cors()); // Allow CORS from all origins (restrict in production)
app.use(express.json());
// Initialize S3 Client
const s3Client = new S3Client({
region: process.env.AWS_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
}
});
// Endpoint to generate presigned URL
app.get('/get-presigned-url', async (req, res) => {
try {
const { fileName } = req.query; // Get filename from frontend
if (!fileName) {
return res.status(400).json({ error: 'fileName is required' });
}
// S3 object key = Webmerge path + filename (e.g., "webmerge-output/invoice.pdf")
const key = `${process.env.S3_WEBMERGE_PATH}${fileName}`;
// Create a GetObject command
const command = new GetObjectCommand({
Bucket: process.env.S3_BUCKET_NAME,
Key: key
});
// Generate presigned URL (expires in 5 minutes)
const presignedUrl = await getSignedUrl(s3Client, command, { expiresIn: 300 });
res.json({ presignedUrl });
} catch (error) {
console.error('Error generating presigned URL:', error);
res.status(500).json({ error: 'Failed to generate download URL' });
}
});
// Start server
app.listen(process.env.PORT, () => {
console.log(`Backend running on http://localhost:${process.env.PORT}`);
}); Step 4: Build the ReactJS Frontend#
The React app will let users input a filename, request a presigned URL from the backend, and trigger a download.
4.1 Create a React App#
- Create a new React app:
npx create-react-app s3-download-frontend cd s3-download-frontend
4.2 Install Dependencies#
Install axios to make API calls to the backend:
npm install axios 4.3 Build a Download Component#
Replace src/App.js with this code:
import { useState } from 'react';
import axios from 'axios';
import './App.css';
function App() {
const [fileName, setFileName] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const handleDownload = async () => {
setLoading(true);
setError('');
try {
// Request presigned URL from backend
const response = await axios.get('http://localhost:5000/get-presigned-url', {
params: { fileName }
});
const { presignedUrl } = response.data;
// Trigger download by creating a temporary anchor tag
const anchor = document.createElement('a');
anchor.href = presignedUrl;
anchor.download = fileName; // Set the filename users see
document.body.appendChild(anchor);
anchor.click();
document.body.removeChild(anchor);
} catch (err) {
setError('Failed to download file. Check filename or try again later.');
console.error('Download error:', err);
} finally {
setLoading(false);
}
};
return (
<div className="App">
<h1>Download Webmerge Files from S3</h1>
<div className="download-container">
<input
type="text"
placeholder="Enter filename (e.g., invoice.pdf)"
value={fileName}
onChange={(e) => setFileName(e.target.value)}
disabled={loading}
/>
<button onClick={handleDownload} disabled={loading || !fileName}>
{loading ? 'Generating Link...' : 'Download File'}
</button>
</div>
{error && <p className="error">{error}</p>}
</div>
);
}
export default App; Add basic styling in src/App.css:
.App {
text-align: center;
padding: 2rem;
}
.download-container {
margin: 2rem auto;
max-width: 500px;
display: flex;
gap: 1rem;
}
input {
flex: 1;
padding: 0.8rem;
font-size: 1rem;
}
button {
padding: 0.8rem 1.5rem;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:disabled {
background: #6c757d;
cursor: not-allowed;
}
.error {
color: #dc3545;
margin-top: 1rem;
} Step 5: Test the Implementation#
-
Start the Backend:
Ins3-download-backend, runnode index.js. You should see:
Backend running on http://localhost:5000. -
Start the Frontend:
Ins3-download-frontend, runnpm start. The React app will open athttp://localhost:3000. -
Generate a Test File with Webmerge:
- In Webmerge, create a test document and trigger a merge. Webmerge will upload the file to your S3 bucket at
s3://your-bucket-name/webmerge-output/filename.pdf.
- In Webmerge, create a test document and trigger a merge. Webmerge will upload the file to your S3 bucket at
-
Download the File:
- In the React app, enter the filename (e.g.,
invoice.pdf) and click "Download File". - The file should download to your device!
- In the React app, enter the filename (e.g.,
Troubleshooting Common Issues#
-
CORS Errors:
- Ensure S3 CORS allows your frontend origin (check Step 1.2).
- In the backend, restrict
cors()to your frontend URL in production (e.g.,cors({ origin: 'https://your-frontend.com' })).
-
"Access Denied" in Presigned URL:
- Verify the IAM user has
s3:GetObjectpermissions (Step 1.3). - Ensure the
S3_WEBMERGE_PATHin.envmatches the path Webmerge uses (e.g.,webmerge-output/).
- Verify the IAM user has
-
File Not Found:
- Confirm the filename exists in S3 at
s3://bucket-name/path/filename. - Check the S3 bucket in the AWS Console to verify the file was uploaded by Webmerge.
- Confirm the filename exists in S3 at
-
Presigned URL Expired:
- The URL expires after 5 minutes (configured via
expiresIn: 300in the backend). Adjust this value if needed.
- The URL expires after 5 minutes (configured via
Conclusion#
You’ve successfully built a secure system to download Webmerge-generated files from AWS S3 using ReactJS and Node.js. By using presigned URLs, you avoid exposing AWS credentials to the frontend, ensuring security.
Next steps:
- Add validation for filenames in the frontend.
- List all available files in S3 (extend the backend to fetch object keys).
- Add authentication to restrict downloads to authorized users.