How do I handle file uploads in a Node.js Express API?
To handle file uploads in a Node.js Express API with Uploadcare,
receive files via multer middleware, then forward them using the official @uploadcare/upload-client SDK.
This requires express, multer, and @uploadcare/upload-client. The full implementation takes 4 steps.
Install Express, multer, and the Uploadcare SDK
Install Express and multer for handling multipart requests,
and @uploadcare/upload-client — the official Uploadcare JS SDK for server-side uploads:
npm install express multer @uploadcare/upload-clientAdd environment variables to your .env file:
UPLOADCARE_PUBLIC_KEY=your_public_key_here
UPLOADCARE_SECRET_KEY=your_secret_key_hereUpload a file to Uploadcare from Express
The uploadFile function from @uploadcare/upload-client accepts a Buffer and returns file info including the UUID and CDN URL.
Wrap it in an Express route that uses multer to parse the incoming multipart request:
const express = require('express');
const multer = require('multer');
const { uploadFile } = require('@uploadcare/upload-client');
const app = express();
const upload = multer({ storage: multer.memoryStorage() });
app.post('/upload', upload.single('file'), async (req, res) => {
try {
const file = req.file;
if (!file) {
return res.status(400).json({ error: 'No file provided' });
}
const result = await uploadFile(file.buffer, {
publicKey: process.env.UPLOADCARE_PUBLIC_KEY,
fileName: file.originalname,
contentType: file.mimetype,
});
res.json({
uuid: result.uuid,
fileUrl: `https://ucarecdn.com/${result.uuid}/`,
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.listen(3000, () => console.log('Server running on port 3000'));This accepts a file via POST, uploads it through the SDK, and returns the file UUID and CDN URL. A public key alone means anyone that knows your endpoint can trigger uploads to your project.
Add signed uploads and file validation
For production, add signed uploads using the SDK’s secureSignature and secureExpire options,
plus file validation and proper error handling:
const crypto = require('crypto');
const express = require('express');
const multer = require('multer');
const { uploadFile } = require('@uploadcare/upload-client');
const app = express();
const upload = multer({
storage: multer.memoryStorage(),
limits: { fileSize: 100 * 1024 * 1024 }, // 100 MB limit
});
const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'application/pdf'];
function generateSignature() {
const expire = Math.floor(Date.now() / 1000) + 3600; // 1 hour
const signature = crypto
.createHmac('sha256', process.env.UPLOADCARE_SECRET_KEY)
.update(String(expire))
.digest('hex');
return { signature, expire };
}
app.post('/upload', upload.single('file'), async (req, res) => {
try {
const file = req.file;
if (!file) {
return res.status(400).json({ error: 'No file provided' });
}
if (!ALLOWED_TYPES.includes(file.mimetype)) {
return res.status(400).json({ error: 'File type not allowed' });
}
const { signature, expire } = generateSignature();
const result = await uploadFile(file.buffer, {
publicKey: process.env.UPLOADCARE_PUBLIC_KEY,
fileName: file.originalname,
contentType: file.mimetype,
secureSignature: signature,
secureExpire: String(expire),
});
res.json({
uuid: result.uuid,
fileUrl: `https://ucarecdn.com/${result.uuid}/`,
timestamp: new Date().toISOString(),
});
} catch (error) {
console.error('Upload error:', error);
res.status(500).json({
error: error.message,
details: process.env.NODE_ENV === 'development' ? error.stack : undefined,
});
}
});
app.listen(process.env.PORT || 3000);This pattern generates HMAC-SHA256 signatures server-side so uploads are cryptographically verified, enforces file type and size restrictions before the upload reaches Uploadcare, and returns structured error responses.
Common errors and troubleshooting
-
Problem:
secureExpiremust be a string
Cause: The SDK expectssecureExpireas a string, not a number
Fix: Wrap the Unix timestamp withString(expire)or use a template literal -
Problem: Signature invalid error
Cause: The expire value is in milliseconds instead of seconds
Fix: DivideDate.now()by 1000 before using it -
Problem: Large file uploads time out
Cause: For files over 10MB, the SDK automatically switches to multipart upload
Fix: Ensure your Express timeout and multerfileSizelimit are set high enough to accommodate your largest expected file
Next steps
Explore adding webhook listeners to process uploaded files automatically, or secure your frontend uploads with signed tokens. For an alternative approach that skips multer entirely, see how to upload files in a Node.js application using direct uploads. If you need to handle files larger than 100 MB, read about multipart file uploads and the full Upload Client SDK reference.