We all know that images are stored in binary. When uploading images, parsing the body is cumbersome without using a third-party library. So let’s take a look at how koA and related libraries can handle uploaded images.

The following code and steps are only for reference. Please visit Github for detailed code

Create a database and create a file table

      async createAvatar (filename, mimetype, size, userId) {
        const state = `INSERT INTO avatar (filename, mimetype, size, user_id) VALUES (? ,? ,? ,?) `;
        const result = await connection.execute(state, [filename, mimetype, size, userId])
        return result[0]}Copy the code

The most common way to do some business is to upload user images or some big action pictures. In the case of Nuggets, it’s uploading user profile pictures and post background images.

Handle avatar uploads

For KOA, there is a library called Koa-Multer that makes it easy for us to handle uploaded images. The specific use will not be introduced, please see the previous article. Koa studies. And if we want to do something with an image, we can do it through the JimP library. The volume is small.

First we need to encapsulate an image upload middleware.

    const path = require("path")
    const multer = require("koa-multer");
    const Jimp = require("jimp");
    const { AVATAR_PATH, PICTURE_PATH } = require(".. /app/filePath");
    
    // Process the avatar upload
    const avatarUpload = multer({
      dest: path.resolve(__dirname, AVATAR_PATH)
    })

    // Handle image uploads
    const pictureUpload = multer({
      dest: path.resolve(__dirname, PICTURE_PATH)
    })

    // Process the user avatar
    const avatarHandler = avatarUpload.single("avatar")


    // Handle the action dynamic avatar
    const pictureHandler = pictureUpload.array("picture")

    // Handle image size
    // The size of the image is determined according to the w* H passed in from the front end. There is no complex processing here, we only do large and medium processing.
    const pictureResizeHandler = async (ctx, next) => {
      const files = ctx.req.files;
      for (let file of files) {
        Jimp.read(file.path).then(image= > {
          // ADAPTS to the width and height
          image.resize(1280, Jimp.AUTO).write(`${file.path}-large`);
          image.resize(640, Jimp.AUTO).write(`${file.path}-middle`);
          image.resize(320, Jimp.AUTO).write(`${file.path}-small`); })}await next()
    }
    
    module.exports = {
      avatarHandler,
      pictureHandler,
      pictureResizeHandler
    }
Copy the code

In general, we click the avatars and are accessed through http://localhost/users/avatar/:userId this way. The image is saved in the User table.

     // Create an avatar
      async createAvatar (ctx, next) {
        // Get the uploaded profile picture information.
        const { filename, mimetype, size } = ctx.req.file;
        const userId = ctx.user.id;
        const result = await createAvatar(filename, mimetype, size, userId)
        // Save the avatar's URL to the Users table
        const avatarUrl = `${APP_HOST}:${APP_PORT}/users/avatar/${userId}`
        await saveAvatar(avatarUrl, userId)
        ctx.body = result
      }
Copy the code

Through the above operation, the picture will be saved to the local specified folder, next, we need to read the avatar information in the database, to return the local avatar to the user.

Let’s read the profile picture first

  // Get the profile picture details
  async detailAvatar (userId) {
    const state = `SELECT * FROM avatar WHERE user_id = ? ; `;
    const [result] = await connection.execute(state, [userId])
    return result.pop()
  }
Copy the code

Now we can read the local file and return the avatar. By user ID

     // Return the user profile picture
      async getUserAvatar (ctx, next) {
        const { userId } = ctx.params;
        // Get the details of the current image from the database. It is then used to get the image locally.
        const avatarInfo = await detailAvatar(userId);

        // 2. Provide image information for browser parsing
        ctx.response.set('content-type', avatarInfo.mimetype);
        ctx.body = fs.createReadStream(path.resolve(__dirname, `${AVATAR_PATH}/${avatarInfo.filename}`));
      }
Copy the code

Let’s take a look at the effect

Handle image upload

In fact, the upload process is the same. However, we also need to deal with the size of the image, which is the above middleware. Create a picture table and get the details of the picture based on the picture name.

     // Create a dynamic image
      async createPicture (filename, mimetype, size, userId, actionId) {
        const state = `INSERT INTO file (filename, mimetype, size, user_id, action_id) VALUES (? ,? ,? ,? ,?) `;
        const result = await connection.execute(state, [filename, mimetype, size, userId, actionId])
        return result[0]}Copy the code

In general, we click the avatars, is through http://localhost/actions/images/:filename? Type =small. Since images and articles are many-to-one or many-to-many, we need to save the action_id in the File table.

      // Create an action image
      async createPicture (ctx, next) {
        const files = ctx.req.files;
        const userId = ctx.user.id;
        const actionId = ctx.request.query.actionId;
        for (let file of files) {
          const { filename, mimetype, size } = file
          await createPicture(filename, mimetype, size, userId, actionId)
        }
        ctx.body = "Upload successful"
      }
Copy the code

By doing this, the image will be saved to the local folder (and save three sizes of images). Next, we need to read the corresponding image information in the database, to return the local image.

First to read the profile picture information, according to the picture name (automatically generated name)

      // Get image details
      async getActionPictureInfoByFileName (filename) {
        const state = `SELECT * FROM file WHERE filename = ? ; `;
        const [result] = await connection.execute(state, [filename])
        return result
      }
Copy the code

Now we can read the local file and return the image information. Based on the image name, we can then specify the Type field of query to determine what size image to return.

 // Return the action picture
  async getActionPictureByFileName (ctx, next) {
    // Filename passed through params
    const filename = ctx.request.params.filename;
    // The type passed in through query
    const imageType = ctx.request.query.type;
    let imageUrl = ""
    const types = ["small"."middle"."large"];
    
    if (types.includes(imageType)) {
      imageUrl = path.resolve(__dirname, `${PICTURE_PATH}/${filename}-${imageType}`)}else {
      imageUrl = path.resolve(__dirname, `${PICTURE_PATH}/${filename}`)}const result = await getActionPictureInfoByFileName(filename)
    ctx.response.set("content-type", result[0].mimetype)
    ctx.body = fs.createReadStream(imageUrl)
  }
Copy the code

Let’s take a look at the effect

The above code and steps are only for reference. Please visit Github for specific codes