Uploading an image

The next feature we will need to implement in our image controller is the logic to handle when a user submits an image Upload Image on the home page. Even though the form is on the home page of our app, we decided to house the logic to handle uploading within our image controller because logically, this makes the most sense (since this feature primarily has to do with images, and not the home page per se). This was purely a personal decision, and you can house the logic wherever you please.

You should note that the HTML for the form on the home page has its action set to /images, and its method is post. This perfectly matches the route we set up previously, where we listen for a post to the /images route and call the image controller's create function.

The create function in our image controller will have a few key responsibilities:

  • It should generate a unique filename for the image, which will also act as an identifier
  • It should save the uploaded file to the filesystem and ensure that it is an image file
  • Finally, it should redirect the control to the image/image_id route once its task is complete, to display the actual image

As we will work with the filesystem in this function, we will need to include a few modules from the Node.js core set of modules, specifically the filesystem (fs) and the path (path) modules.

Before we begin adding the necessary code for the Upload Image section, we will need a small fix to be applied on the configuration of the application. Further, we will need to add an extra module in the configure file to support the file uploads, namely multer. Add it as a dependency to our application using the following command:

npm install multer --save

Now, go to the configuration file that is server/configure.js and require it via:

multer = require('multer');

You can place this under the initially required modules in the file. Then, insert the following snippet under the Handlebars engine method:

app.use(multer({
dest: path.join(__dirname, 'public/upload/temp')
}));

Now, our upload actions will work fine, as expected.

Let's begin by first editing the controllers/image.js file and inserting the two new require statements at the very top of the file:

const fs = require('fs'),
path = require('path');

Next, take the create function's original code:

res.send('The image:create POST controller');
res.redirect('/images/1');

Replace this original code with the following code:

const saveImage = function() {
// to do...
};
saveImage();

Here, we created a function called saveImage and executed it immediately after we declared it. This might look a little odd, but the reason for this will become clear when we implement database calls in the following chapter. The main reason is that we will call saveImage repeatedly to ensure that the unique identifier we generated is in fact unique and doesn't already exist in the database as a previously saved image's identifier.

Let's review a breakdown of the code that will be inserted in the saveImage function (replacing the // to do... comment). I will cover each line of code for this function and then give you the entire block of code at the end:

let possible = 'abcdefghijklmnopqrstuvwxyz0123456789',
imgUrl = '';

We will need to generate a random, six-digit alphanumeric string to represent a unique identifier for an image. This identifier will work in a similar fashion to other websites that provide short URLs for unique links (for example, bit.ly). To do this, we will first provide a string of possible characters that can be used while generating the random string:

for(let i=0; i < 6; i+=1) {
imgUrl += possible.charAt(Math.floor(Math.random() *
possible.length));
}

Then, loop-over for six times and randomly pull out a single character from our string of possible characters, appending it in each cycle. By the end of this for loop, we should have a string that consists of six random letters and/or numbers, for example, a8bd73:

const tempPath = req.files.file.path,
ext = path.extname(req.files.file.name).toLowerCase(),
targetPath = path.resolve(`./public/upload/${imgUrl}${ ext}`);

Here, we declare three constants: where our uploaded files will be stored temporarily, the file extension of the file that was uploaded (for instance, .png, .jpg, and so on), and a destination where the uploaded image should ultimately reside.

For both the latter variables, we will use the path node module, which works great while dealing with filenames and paths and getting information from a file (such as a file extension). Next, we will move the image from its temporary upload path to its final destination:

if (ext === '.png' || ext === '.jpg' || ext === '.jpeg' || ext ===
'.gif') {
fs.rename(tempPath, targetPath, (err) => {
if (err) throw err;
res.redirect(`/images/ ${imgUrl}`);
});
} else {
fs.unlink(tempPath, () => {
if (err) throw err;
res.json(500, { error: 'Only image files are allowed.' });
});
}

The preceding code performs some validation. Specifically, it conducts checks to make sure that the uploaded file extension matches a list of allowable extensions, namely known image file types. If a valid image file was uploaded, it is moved from the temp folder via the filesystem's rename function. Note how the filesystem (fs) rename function takes three parameters: the original file, the new file, and a callback function.

The callback function is executed once the rename is complete. If the node doesn't work this way (always relying on callback functions), it's quite likely that your code will execute immediately following the execution of the rename function and try to work against a file that doesn't exist yet (that is, the rename function doesn't even finish doing its work). Using a callback function, we effectively tell the node that once the rename of the file is finished and the file is ready and placed where it should be, then it can execute the code in the callback function.

The else condition that follow handles the situation when the uploaded file was invalid (that is, not an image), so we call the unlink function of the fs module, which will delete the original file (from the temp directory it was uploaded to) and then send a simple JSON 500 with an error message.

Here is the complete saveImage function (again, the following code will replace // to do... from earlier):

const possible = 'abcdefghijklmnopqrstuvwxyz0123456789',
imgUrl = '';
for (let i = 0; i < 6; i += 1) {
imgUrl += possible.charAt(Math.floor(Math.random() *
possible.length));
}
const tempPath = req.files.file.path,
ext = path.extname(req.files.file.name).toLowerCase(),
targetPath = path.resolve(`./public/upload/${ imgUrl }${ ext }`);
if (ext === '.png' || ext === '.jpg' || ext === '.jpeg' || ext ===
'.gif') {
fs.rename(tempPath, targetPath, (err) => {
if (err) throw err;
res.redirect('/images/${ext}');
});
} else {
fs.unlink(tempPath, () => {
if (err) throw err;
res.json(500, { error: 'Only image files are allowed.' });
});
}

With this new code in place, we can now successfully upload an image file via the form on the home page. Give it a try by launching the app and opening it in a browser. Once there, click on the Browse button in the main form, and select an image file from your computer. If successful, the image file should exist within the public/upload folder of your project with a new random filename.

Ensure that you have the public/upload folders created in your project, or you will get runtime errors when you attempt to write files to a location that doesn't exist. The write permissions might need to be set on the folder, depending on your OS and security access.

After the upload form completes and the create controller function does its work, it will redirect you to the individual image page for the image that was uploaded.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.89.18