Advanced Flutter Networking Part 1: Uploading a File to a REST API from Flutter Using a Multi-Part (form-data) POST Request

My Flutter book is pretty light on advanced HTTP networking topics, focusing instead on giving a more well-rounded approach that, when it comes to networking, explains how to use the http networking package for basic requests, shows an example of an app that makes GET requests, and then goes a bit more specific with Firebase.

My blog is here for those who need more than that, and the first topic I’m going to cover is how to upload a file using a multi-part/form-data POST request.

You can find all of the code contained in this example in this GitHub repository.

What is a Multi-Part POST Request

Usually, the body of a POST request is made of textual key-value pairs. With a multipart POST request, you can also include files with binary content (images, various documents, etc.), in addition to the regular text values.

Making Multi-Part POST Requests with Flutter’s HTTP Library

This little section is going to focus on one thing: creating a function that, given a file path and an URL, is able to send that file to that URL with that multipart POST request.

The next section is going to focus on how to build an app that lets the user insert the URL to which to send the request, pick an image and send it to the server, getting back the status of the request.

This means we’re not going to worry about dependencies in this section. All you need to know for this section is that we’ve got this at the top of our main.dart:

import 'package:http/http.dart' as http;

This allows us to create (not send) a multipart POST request using

var req = http.MultipartRequest('POST', Uri.parse(url));

This req object has a member Map called fields for textual values and a List called files to which you can add MultipartFiles.

The MultipartFile

The most important element of this whole thing is the MultipartFile. It can be constructed in a few ways:

  • the default MultipartFile(key, stream, length) constructor, which you can use if you need to create the file from a Stream of bytes of which we know the length;
  • the MultipartFile.fromBytes(key, bytes) factory method, which attaches a file obtained from a List of bytes;
  • the MultipartFile.fromString(key, string) factory method, which attaches a text file containing the string passed to it;
  • the MultipartFile.fromPath(key, path) factory method, which attaches the file found at the given path.

In case you’re wondering, bytes simply means integers. That’s all a byte is. An 8-bit integer value. A list of integers, coupled with an encoding (that can usually be inferred with the help of the extension that’s part of the file name) is all you need to get images, documents, videos, music or text.

If you’re using one of the first two constructors for the MultipartFile, remember to set a filename using the specific named filename argument, as not doing that will cause issues with our back-end.

Here are a few examples: if you have a file and a path to it, here’s how you create a multipart POST request and attach that file to the request:

  • Using the default constructor you could write a function like the following:
  • UsingMultipartFile.fromBytes() you could write a function like the following:
  • UsingMultipartFile.fromPath() you could write a function like the following:

Adding Regular Text Fields

You could add regular text fields to a Multipart POST requests simply by adding, after settomg initialized the MultipartRequest object, an item in the fields member Map:

var request = http.MultipartRequest('POST', Uri.parse(url));
request.fields['key'] = 'value';

Building an App Around That

Now, to see it in action, let’s write an app that allows the user to insert an URL, pick an image, and then upload the image to the server at that URL. We’ll write the very simple back-end code required to accept files this way in Node after we’re done with the app. Feel free to skip ahead if you’re more interested in that.

Let’s start, as we must, with an evaluation of the dependencies and the pubspec.yaml file. We’ll use two packages from Dart Pub: the image_picker Flutter package that will handle the image selection dialog for us, about which I wrote a tutorial on Medium a year ago (not part of the metered paywall in case you’re wondering) and the Dart http package that provides the networking features we need.

Add both the dependencies in pubspec.yaml (check the GitHub repo I linked at the start of the post if you don’t know how to do that) and import them both into the lib/main.dart file along with the usual Flutter Material Design API:

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:image_picker/image_picker.dart';

The Start Page

Now let’s think about the actions the user needs to take in our app: in order to upload images, we first need an URL, so let’s show a screen that allows the user to set the URL first, and then we allow them to upload images. We’ll call this first screen StartPage. This is the usual Flutter code used to initialize an app, this tutorial really is meant for those who don’t need much explanation as to what it does and why (that’s more the realm of introductory tutorials and books like the one I’ve written):

We use a TextEditingController to let the user both submit the value from the Android keyboard or press our cusdtom FlatButton below the TextField.

The Upload Page

Here’s the code for the upload page, I’ll explain it down below after the code:

uploadImage() is the simplest of the functions we saw earlier to upload images: we use MultipartFile.fromPath() directly and return the status string of the request. That’s going to be the state variable, which is what is shown in the middle of the screen to the user. The URL is the one we get from the StartPage.

The FloatingActionButton is fairly simple: ImagePicker.pickImage() shows the familiar image picking screen to the user returns (through a Future) the path to the file the user picks.

We use that (as part of the floating action button’s onPressed callback) together with the URL passed by the StartPage to call the uploadImage function and re-render the view using setState to show the state of the request to the user.

Writing a Simple Back-End in Node to Handle Multi-Part POST Requests

For this section, the tools we’re going to use are Node, Express and Multer. There’s not going to be much code involved, this is a fairly simple affair.

First of all, install Node and NPM according to the instructions for your operating system on Node’s official website.

After that, create a directory, run the command

$ npm init

and fill out to your preference the choices given to you (the default values are fine in case of doubt)

and then

$ npm --save install express multer

Create a file called index.js with the following content:

As you can see it’s not much code, but I’m going to explain it nonetheless.

The first 4 lines are simply dealing with dependencies: the first three are straight-up imports of dependencies, whereas line 4 sets up the Multer middleware (the one that handles file uploads) to save uploaded files to the uploads directory.

Line 6 instantiates an Express server object, as the Express API documentation requires.

Creating an Express Rule for Handling Multipart POST requests

After that, on lines 8 to 19, we define a rule for incoming POST requests to the /upload path (in other words, it responds to POST requests sent to <server IP>:<port>/upload), it takes one file on the picture key, and it fires the callback function we defined as (req, res) {...} in the lines 8 to 19.

The callback prints to the console the name of the file that was received (on line 9), it then renames the file to the original file name.

It does that on lines 10 to 16 by creating, as is done using Node’s fs interface, streams (much like FILE* variables in C) that allow our script to read from an input file (req.file.path is the path Multer saved the received file to) and write to an output file (using the passed req.file.originalname, which is the filename specified in the POST request by the sender, which is our Flutter app). When this is finished (lines 13 to 16) we delete (unlinkSync) the temporary file created by Multer (which has a name that ensures there aren’t two files with the same name but doesn’t even keep the same extension as the original file) and send back a response saying everything was OK.

If an error happens (often because no file name was specified, on line 17) we will inform the sender of that.

On lines 22 and 23 we start the server on the port specified by the PORT environment variable or on port 3000 if no port was specified using environment variables.

That’s it.

Making Our Example Work

To use the example code, start the server by running node index.js. Then, fire up the Flutter app on a phone/emulator, get your PC’s local IP (if the phone is connected to the same local network or it’s running on an emulator) to substitute to <server_ip> in http://<server_ip>:3000/upload, which is what you need to type into the TextField in the app. For example, if your PC’s IP is 192.168.1.2, you’d type in http://192.168.1.2:3000/upload. After that, press the + icon, pick an image, and you’ll see the Node script will print out a message like Received image_picker<gibberish>.jpg. By opening the uploads subdirectory in the directory where you have the back-end Node script, you’ll find a file called image_picker<gibberish>.jpg.

I hope you found this tutorial useful and I invite you to tell me what topic I should cover next.