Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to parse multipart/form-data body with Koa?

Because I spent some (too much) time figuring out this simple requirement. I am documenting here the way to achieve multipart/form-data body parsing with Koa.

In my case, the reason of the confusion was the number of alternatives available out there:

  • koa-body
  • koa-better-body
  • koa-body-parser
  • koa-bodyparser

And I wanted to find the most minimalist/close to express/koa/node way/philosophy of doing things.

So here it is. Below. In accepted answer. Hope this helps.

like image 346
eightyfive Avatar asked Nov 17 '15 07:11

eightyfive


4 Answers

You have to use koa-multer as stated in the official Koa wiki.

So a simple setup would look like:

const koa = require('koa');
const multer = require('koa-multer');

const app = koa();

app.use(multer());

app.use(function *() {
  this.body = this.req.body;
});

A couple of notes:

  • Multer will only parse bodies of requests of type multipart/form-data
  • Notice the use of this.req.body instead of Koa's supercharged this.request (not sure if this is intentional but this is confusing for sure... I would expect the parsed body to be available on this.request...)

And sending this HTML form as FormData:

<form>
  <input type="hidden" name="topsecret" value="1">
  <input type="text" name="area51[lat]" value="37.235065">
  <input type="text" name="area51[lng]" value="-115.811117">
  ...
</form>

Would give you access to nested properties as expected:

// -> console.log(this.req.body)
{
  "topsecret": 1,
  "area51": {
    "lat": "37.235065",
    "lng": "-115.811117",
  }
}
like image 54
eightyfive Avatar answered Oct 31 '22 16:10

eightyfive


For Koa2, you can use async-busboy as other solutions dont support promises or async/await.

Example from the docs:

import asyncBusboy from 'async-busboy';

// Koa 2 middleware
async function(ctx, next) {
  const {files, fields} = await asyncBusboy(ctx.req);

  // Make some validation on the fields before upload to S3
  if ( checkFiles(fields) ) {
    files.map(uploadFilesToS3)
  } else {
    return 'error';
  }
}
like image 34
silkAdmin Avatar answered Oct 31 '22 17:10

silkAdmin


I have three solutions that works for me:

  1. koa-body, note it parses multipart/form-data only with multipart: true option.
const Koa = require('koa');
const koaBody = require('koa-body');
const Router = require('koa-router');

const app = new Koa();
const router = new Router();

app.use(koaBody({ multipart: true }));

router.post('/', async ctx => {
    const body = ctx.request.body;
    // some code...
});

app.use(router.routes());

app.listen(3000);
  1. koa-bodyparser, parses multipart/form-data only with koa2-formidable middleware before it.
const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
const formidable = require('koa2-formidable');
const Router = require('koa-router');

const app = new Koa();
const router = new Router();

app.use(formidable());
app.use(bodyParser());

router.post('/', async ctx => {
    const body = ctx.request.body;
    // some code...
});

app.use(router.routes());

app.listen(3000);
  1. @koa/multer, note it parses multipart/form-data only if installed multer package. Also note that koa-multer is deprecated, do not use it.
const Koa = require('koa');
const Router = require('koa-router');
const multer = require('@koa/multer');

const app = new Koa();
const router = new Router();
const upload = multer(); // you can pass options here

app.use(upload.any());

router.post('/', async ctx => {
    const body = ctx.request.body;
    // some code...
});

app.use(router.routes());

app.listen(3000);
like image 9
ns16 Avatar answered Oct 31 '22 17:10

ns16


I went through the same investigation than you and here are other ways to achieve multipart/form-data body parsing with Koa.

co-busboy:

var koa = require('koa');
var parse = require('co-busboy');

const app = koa();

app.use(function* (next) {
  // the body isn't multipart, so busboy can't parse it 
  if (!this.request.is('multipart/*')) return yield next;

  var parts = parse(this),
      part,
      fields = {};
  while (part = yield parts) {
    if (part.length) {
      // arrays are busboy fields 
      console.log('key: ' + part[0]);
      console.log('value: ' + part[1]);

      fields[part[0]] = part[1];
    } else {
      // it's a stream, you can do something like:
      // part.pipe(fs.createWriteStream('some file.txt'));
    }
  }

  this.body = JSON.stringify(fields, null, 2);
})

koa-body:

var koa = require('koa');
var router = require('koa-router');
var koaBody = require('koa-body')({ multipart: true });

const app = koa();

app.use(router(app));

app.post('/', koaBody, function *(next) {
  console.log(this.request.body.fields);

  this.body = JSON.stringify(this.request.body, null, 2);
});

In both cases you will have a response like:

{
  "topsecret": 1,
  "area51": {
    "lat": "37.235065",
    "lng": "-115.811117",
  }
}

But personally, I prefer the way koa-body works. Plus, is compatible with other middleware like koa-validate.

Also, if you specify an upload dir to koa-body, it will save the uploaded file for you:

var koaBody = require('koa-body')({
  multipart: true,
  formidable: { uploadDir: path.join(__dirname, 'tmp') }
});
like image 5
satanas Avatar answered Oct 31 '22 17:10

satanas