Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Loopback 4: Upload multipart/form-data via POST method

I'm working in Loopback 4 and getting stuck in creating a POST method so that client can call this method and upload a multipart/form-data. I read some examples:

  • https://medium.com/@jackrobertscott/upload-files-to-aws-s3-in-loopback-29a3f01119f4
  • https://github.com/strongloop/loopback-example-storage

But, look like they are not suitable for Loopback 4.

Could you help me to upload multipart/form-data via POST method in Loopback4.

like image 983
Võ Lê Duy Anh Avatar asked Nov 01 '18 14:11

Võ Lê Duy Anh


People also ask

How do I upload a multipart form data?

Multipart form data: The ENCTYPE attribute of <form> tag specifies the method of encoding for the form data. It is one of the two ways of encoding the HTML form. It is specifically used when file uploading is required in HTML form. It sends the form data to server in multiple parts because of large size of file.

What is loopback4?

LoopBack is an award-winning, highly extensible, open-source Node. js and TypeScript framework based on Express. It enables you to quickly create APIs and microservices composed from backend systems such as databases and SOAP or REST services.


2 Answers

Support for multipart/form-data was added to LoopBack 4 recently via https://github.com/strongloop/loopback-next/pull/1936.

Because there are many different ways how to process the uploaded files, LoopBack 4 does not provide a generic file-upload solution out of the box. Instead, it allows applications to implement their own file-upload handler.

In the examples below, I am configuring multer to use memory storage. This is probably not what you want to do in production, please refer to multer documentation to learn how to configure different storage backend.

Also note that you can use multer in TypeScript too, just install & add @types/multer to your devDependencies.

1. Handle file upload inside your controller method

You can tell LoopBack to skip body parsing and pass the full request to your controller method. In the controller method, call multer to handle file upload. A full working example can be found in file-upload.acceptance.ts, I am cross-posting the controller implementation here.

class FileUploadController {
  @post('/show-body', {
    responses: {
      200: {
        content: {
          'application/json': {
            schema: {
              type: 'object',
            },
          },
        },
        description: '',
      },
    },
  })
  async showBody(
    @requestBody({
      description: 'multipart/form-data value.',
      required: true,
      content: {
        'multipart/form-data': {
          // Skip body parsing
          'x-parser': 'stream',
          schema: {type: 'object'},
        },
      },
    })
    request: Request,
    @inject(RestBindings.Http.RESPONSE) response: Response,
  ): Promise<Object> {
    const storage = multer.memoryStorage();
    const upload = multer({storage});
    return new Promise<object>((resolve, reject) => {
      upload.any()(request, response, err => {
        if (err) return reject(err);
        resolve({
          files: request.files,
          fields: (request as any).fields,
        });
      });
    });
  }
}

2. Register custom LB4 BodyParser

Alternatively, you can move parsing of file upload requests to a specialized body parser, and thus simplify your controller methods to receive the parsed result. This is especially useful when you have more than one controller method accepting file uploads.

A full working example can be found in file-upload-with-parser.acceptance.ts, I am cross-posting the relevant snippets here.

The parser:

class MultipartFormDataBodyParser implements BodyParser {
  name = FORM_DATA;

  supports(mediaType: string) {
    // The mediaType can be
    // `multipart/form-data; boundary=--------------------------979177593423179356726653`
    return mediaType.startsWith(FORM_DATA);
  }

  async parse(request: Request): Promise<RequestBody> {
    const storage = multer.memoryStorage();
    const upload = multer({storage});
    return new Promise<RequestBody>((resolve, reject) => {
      upload.any()(request, {} as any, err => {
        if (err) return reject(err);
        resolve({
          value: {
            files: request.files,
            fields: (request as any).fields,
          },
        });
      });
    });
  }
}

Parser registration in your Application's constructor:

app.bodyParser(MultipartFormDataBodyParser);

And finally the controller:

class FileUploadController {
  @post('/show-body', {
    responses: {
      200: {
        content: {
          'application/json': {
            schema: {
              type: 'object',
            },
          },
        },
        description: '',
      },
    },
  })
  async showBody(
    @requestBody({
      description: 'multipart/form-data value.',
      required: true,
      content: {
        [FORM_DATA]: {
          schema: {type: 'object'},
        },
      },
    })
    body: unknown,
  ) {
    return body;
  }
}
like image 121
Miroslav Bajtoš Avatar answered Sep 19 '22 14:09

Miroslav Bajtoš


Loopback 4 team is implementing this feature: https://github.com/strongloop/loopback-next/pull/1880

Hope the we will have it soon.

like image 39
Võ Lê Duy Anh Avatar answered Sep 22 '22 14:09

Võ Lê Duy Anh