3

I am setting up a new server and want to support advanced upload function. Firstly I need to validate file (filetype, filesize, maxcount), and finally upload it to some destination. I tried something with koa-multer but I cannot get multer validation errors.

multer.js

const multer = require('koa-multer')

const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, './public/uploads/')
  },
  filename: function (req, file, cb) {
    var fileFormat = (file.originalname).split('.')
    cb(null, file.fieldname + '_' + Date.now() + '.' + fileFormat[fileFormat.length - 1])
  }
})

const fileFilter = (req, file, cb) => {
  if (file.mimetype === 'image/jpeg' || file.mimetype === 'image/png') {
    cb(null, true)
  } else {
    cb(new Error('This is not image file type'), false)
  }
}

const upload = multer({
  storage: storage,
  limits: {
    fileSize: 1024 * 1024 * 1,
    files: 5
  },
  fileFilter: fileFilter
})

module.exports = upload

router.js

const Router = require('koa-router')

const multer = require('../middlewares/multer')
const auth = require('../middlewares/auth')
const controller = require('../controllers').userController

const schemas = require('../schemas/joi_schemas')
const validation = require('../middlewares/validation')

const router = new Router()

const BASE_URL = `/users`

router.post(BASE_URL, auth , validation(schemas.uPOST, 'body'), controller.

addUser)
    router.put(`${BASE_URL}/:id`, auth , multer.single('logo')(ctx, (err) => {
  if (err) {
    ctx.body = {
      success: false,
      message: 'This is not image file'
    }
  }
}),  controller.editUser)
router.delete(`${BASE_URL}/:id`, auth , controller.deleteUser)

module.exports = router.routes()

How can I solve upload problem this in best way for long term maintain of code?

Benfactor
  • 543
  • 4
  • 11
  • 31

3 Answers3

10

The simplest approach for uploading files is the following (assume the form has a file upload field called avatar:

const Koa = require('koa')
const mime = require('mime-types')
const Router = require('koa-router')
const koaBody = require('koa-body')({multipart: true, uploadDir: '.'})

const router = new Router()

router.post('/register', koaBody, async ctx => {
    try {
        const {path, name, type} = ctx.request.files.avatar
        const fileExtension = mime.extension(type)
        console.log(`path: ${path}`)
        console.log(`filename: ${name}`)
        console.log(`type: ${type}`)
        console.log(`fileExtension: ${fileExtension}`)
        await fs.copy(path, `public/avatars/${name}`)
        ctx.redirect('/')
    } catch(err) {
        console.log(`error ${err.message}`)
        await ctx.render('error', {message: err.message})
    }
})

Notice that this example uses Async Functions which allows for clean code with a standard try-catch block to handle exceptions.

Oleg2tor
  • 531
  • 1
  • 8
  • 18
Mark Tyers
  • 2,961
  • 4
  • 29
  • 52
  • 1
    Sandbox Link: https://codesandbox.io/s/koa-body-upload-tt3c2 koajs/koa-body is being used because of the need for multipart. It is a first party middleware backed by the streaming multipart parser formidable and co-body – Ray Foss Oct 13 '21 at 17:04
  • with koa 6.01 it says koaBody is not a function anymore. perhaps an update to the answer is needed. the import should be used as such: const { koaBody } = require('koa-body'); – BEvo Jul 12 '23 at 20:06
1

koa middleware is like a nested callback, you should catch "next()" not after multer

router.put(`${BASE_URL}/:id`, auth , async (ctx, next) => {
  try{
    await next()
  } catch(err) {
    ctx.body = {
      success: false,
      message: 'This is not image file'
    }
  }
}, multer.single('logo'),  controller.editUser)

but you do this, it will catch controller.editUser errors too which not been caught by controller itself.

王仁宏
  • 396
  • 1
  • 9
0

You can use one of two options:

The first is by adding callback function to the end of the route.

const multer = require('@koa/multer')

//Options to limit file size and file extension
const upload = multer({
        dest: '../avatars',
        limits: {
            fileSize: 1024*1024
        },
        fileFilter(ctx, file, cb) {
            if (!file.originalname.match(/\.(jpg|jpeg|png)$/)) {
                return cb(new Error('Please upload a World document'))
            }
            cb(undefined, true)
        }
    })



//The last callback should handle the error from multer
router
        .post('/upload', upload.single('upload'), async (ctx) => {
            ctx.status = 200
        }, (error, ctx) => {
            ctx.status = 400
            ctx.body = error.message
        })
})        

The second option is by adding try/ catch before calling multer middleware:

router
    .post('/upload', async (ctx, next) => {
              try {
            await next()
            ctx.status = 200
        } catch (error) {
            ctx.status = 400
            ctx.body = error.message
        }
    }, upload.single('upload'), async ctx => {ctx.status = 200})

In last case if exception will thrown in multer, it will be handled by try/catch in previous await next()

natanbig
  • 79
  • 1
  • 1
  • 5