Ok so instead of the old proces:
- Call API and Get signed URL
- Upload to S3
- Notify my API that upload is complete and provide image url to save
I am doing the following now:
- Upload file to API
- API then uploads it to S3 and saves it
It’s much easier to deal with, it just uses more server resources but it’s not really a big deal unless if you have a file sharing website.
So on the front end, I’m still using the FileTransfer plugin, since it works well if I’m just dealing with my own server. The only difference (as I mentioned earlier) is that now I upload files directly to my own server instead of the 3-step upload. I only changed the settings so that it uses POST
instead of PUT
, and I added field name, so it posts it as a form (multipart form submission).
On the back-end, I had to install multer
which is a middleware for Express that handles file uploads. Multer can parse various types of file uploads and then it sets req.files
property for you. Similar to how express-jwt
and similar libraries set the req.user
property.
Step 1: I created a file that I can import from any file in my API, because I have multiple routes that accept photo uploads and I don’t want to create multiple instances of multer:
import * as multer from 'multer';
export default multer({
dest: 'tmp/',
limits: {
fields: 0,
fieldSize: 5000000
}
});
Step 2: Again, since I’m uploading from different routes, it makes sense to make a helper function that handles the upload to S3, so I created the following:
export class ImageUploader {
static uploadImage(key: string, filePath: string): Promise<string> {
return new Promise<string>((resolve, reject) => {
let s3 = new aws.S3();
fs.readFile(filePath, (err, data) => {
if(err) return reject(err);
s3.upload({
Bucket: 'MY_BUCKET_NAME',
Key: key, // file path + name
Body: data, // this is the image data
ACL: 'public-read' // permissions for anyone to read
}, (err, r) => {
// delete temporary file after we're done uploading
fs.unlink(filePath); // fs is from require('fs'), built into nodejs
if(err) reject(err);
else {
resolve(r.Location); // resolve the file public URL
}
});
});
});
}
}
Step 3: I did the following in my route page (this is a simplified version of what’s actually happening)
import Upload from '../incudes/multer.ts'; // this is the file above
import { ImageUploader } from '../services/image-uploader.ts'; // the static class above
function updateAvatar(req, res) {
let updateDoc = (url?: string) => {
User.findByIdAndUpdate(req.auth.user_id, {avatar: url}, (err, doc) => {
if(err) Utils.res(res, 400); // my helper function that handles responses
else Utils.res(res); // my helper function that handles responses
});
};
let file = req.file;
// the user wishes to delete their saved avatar
if(!file) return updateDoc(null);
// TODO check mime types, size.. etc
// this is the `key` value for S3
// it's basically the path + file name to store the image at
let file_name = 'avatars/' + req.auth.user_id + '/' + Date.now() + '.jpg';
// call our helper function to upload the file
ImageUploader.uploadImage(file_name, file.path)
.then((url) => {
updateDoc(url);
})
.catch(e => {
console.log(e);
Utils.res(res, 400); // my helper function that handles responses
});
}
I hope that helped