Coding Journal

Jordan Vidrine

Daily Coding Journal

An experienced business owner + musician with a demonstrated history of management in the Energy sector and an insatiable appetite for learning new and complex skills. Transitioning from an ownership role & seeking employment in Web Development as a Jr Full Stack Developer.

August 13th 2019

Wow. What a week! Since last Tuesday up until this morning my time has been spent setting up, rehearsing, performing, and setting down from my Art Spark performance. It was the best musical performance and activity I have ever had the privilege to participate in! If you’d like to check out some of that info, feel free to browse through my artist facebook page

Now that I’m finished with the performance aspect of the grant, I will be able to devote more time to learning to code. So, lets move on!

Uploading files with multer npm package

The first part of my learning today was back to the uploading files section of the node course. We are doing this by using the multer npm package. I started off today with a challenge to:

  1. Limit the upload size to 1MB
  2. Only allow jpg, jpeg, or png files

Here’s how I did that.

// Multer avatar upload
const avatarUpload = multer({
  dest: 'avatars',
  limits: {
    fileSize: 1000000,
  },
  fileFilter(req, file, cb) {
    if (!file.originalname.match(/.(jpeg|jpg|png)$/)) {
      return cb(new Error('Please upload an image!'))
    }
    cb(undefined,true)
  }
})

router.post('/users/me/avatar', avatarUpload.single('avatar'), (req,res) => {
  res.send()
})

Handling errors in Express

We will go over handling errors with custom middleware in express. This is cool because I figured out how to do this on my own while working on the YNAB text messaging app. To get up and running and see how this works, we did the following middleware in our index.js file to throw an error.

const errorMiddleware = (req,res,next) => {
  throw new Error('From my middleware')
}

app.post('/upload', errorMiddleware, (req,res) => {
  res.send()
})

When using postman, we are returned with some html with the error Error: From my middleware at the top of the response. We want JSON however, so we will get this done. What we need to do is add a new callback at the end of app.post that will handle errors appropriately.

app.post('/upload', errorMiddleware, (req,res) => {
  res.send()
}, (error, req, res, next) => {
  res.status(400).send({error: error.message})
})

This will give us a JSON response with the error and its error message. We can now change that errorMiddleware back to the multer script we were using earlier. We have to be sure that we call the error callback with the correct arguments (error, req, res, next) because this lets Express know it is an error handling function.

Using Authentication to add images to a user profile

Our first step is really easy and straightforward. Express accepts multiple middleware so we can just add our Auth middleware before the multer call.

router.post('/users/me/avatar', auth, avatarUpload.single('avatar'), (req,res) => {
  res.send()
}, (error, req, res, next) => {
  res.status(400).send({error: error.message})
})

The next step is going to be to figure out where we are going to store the file the user uploads. We will not be storing it in the project file system as when we deploy our app to a site like AWS or heroku, each time we deploy, the file system gets reloaded, essentially erasing any uploaded files.

We will store the image binary data onto the user model by adding this to our User model.

avatar: {
  type: Buffer
}

What we need to do is somehow access the data that multer is saving. Right now, multer saves the file into a directory we specified, and does not pass this information onto the final callback before we exit the route handler.

To do this, we just need to remove our option for dest: 'avatars',. This will cause multer to send the file data to the next step in the route handler. The data will be accessible on req.file. It is an object that contains data bout the file. For what we need, we will be accessing the req.file.buffer data to save to the User model.

Now, our route and handler should look like this.

router.post('/users/me/avatar', auth, avatarUpload.single('avatar'), async (req,res) => {
  req.user.avatar = req.file.buffer
  await req.user.save()
  res.send()
}, (error, req, res, next) => {
  res.status(400).send({error: error.message})
})

This will convert toe image file into binary data, and store that on the user model on the avatar keyword. To render this in a browser, you can use this syntax in an <img> tag: <img src="data:image/jpg;base64,/binarydatahere....."/>

Deleting Avatar from profile

This lesson ended with a challenge. For the challenge I needed to:

  1. Setup DELETE /users/me/avatar
  2. Add authentication
  3. Set the field to undefined and save the user sending back a 200
  4. Test your work by creating a new request for task app in postman

Here is how I did this:

router.delete('/users/me/avatar', auth, async (req,res) => {
  try {
    req.user.avatar = undefined
    await req.user.save()
    res.status(200).send()
  } catch (e) {
    res.status(400).send(e)
  }
})

Serving up files to the user

To serve files, like the avatar image the user has uploaded, we will create a url route that will send the browser the file being requested. We will do this by adding another route to our user routes file. What this route will do is look for a user based on their ID and in the response, send the jpg.

// specifies a new route, that will user the User ID to get the avatar
router.get('/users/:id/avatar', async (req,res) => {
  try {
    const user = await User.findById(req.params.id)

    if (!user || !user.avatar) {
      throw new Error()
    }

    // this will set the response header for what it needs to be when delivering a jpg
    res.set('Content-Type', 'image/jpg')
    // this sends the binary data from user.avatar
    res.send(user.avatar)
  } catch (e) {
    res.status(404).send()
  }
})

Cropping + Formatting jpg for user needs

We will use an npm module called Sharp to format images users upload before saving. Sharp can also convert all uploaded images to a certain type, so there will be a uniformity in our database of the user images. To do this, lets require sharp in the user routes file and change the upload avatar section like so:

router.post('/users/me/avatar', auth, avatarUpload.single('avatar'), async (req,res) => {
  const buffer = await sharp(req.file.buffer).resize({ width: 250, height: 250 }).png().toBuffer()
  req.user.avatar = buffer
  await req.user.save()
  res.send()
}, (error, req, res, next) => {
  res.status(400).send({error: error.message})
})

What the above code does is takes the file the user has sent, resizes it to 250x250, converts it to a .png, and then converts it to binary data for us to save to the user model. This is so amazing to me! Such a neat little package.

Thats all for my node learning today! On to the next chunk of the day… Eloquent Javascript!

Back to blog...