Manage File Uploads with Server Side Swift

At some point in building web application front ends you will be faced with the need to manage file uploads. The good thing is that it’s not hard with Server Side Swift.

I’m going to start with the basic “Perfect App Template”, because it already has baked in everything we need.

$ git clone File-Upload-Article
$ cd File-Upload-Article/
$ swift package generate-xcodeproj

Because we’re going to run this from Xcode and want to make sure our “Working Directory” is set correctly, we need to do an extra step once we open the project in Xcode:

  • From the “Scheme” dropdown select the black executable target along with “My Mac”
  • Now select “Edit Scheme”, and under Run > Options, select “Use custom working directory” and select your project directory.

OK so now lets make the form. In the file explorer pane, open “webroot > templates > index.mustache”

This shows us the simple “Hello, World!” example HTML.

Adding File Uploads

Instead of the simple HTML, we want a form here:

<title>File uploads</title>
<h2>File uploads</h2>
<form method=”POST” enctype=”multipart/form-data” action=””>
File to upload: <input type=”file” name=”fileup”><br>
<input type=”submit” value=”Upload files now.”>

Now we want to display this. If we have a look in the “Sources > handlers > Handlers.swift” file the static function “main” is generating plain HTML and not using the Mustache template. To switch to using the Mustache template, remove the “response.setbody” and replace with:

response.render(template: “templates/index”)

Now, run this (cmd-R, or the “Run” button to the left of the “Scheme” dropdown), and load http://localhost:8181 in a browser. You’ll see your HTML rendered in the browser window as intended.

Next, we want to add a POST route, update the handler, and a directory lister to show the uploaded file(s).

First however, we should make a directory to put our uploaded files in. I suggest a directory called “uploads” in the webroot directory.

Open “Sources > configuration > Routes.swift” and add this after the GET route for the home page:

// POST Handler for home page
routes.append([“method”:”post”, “uri”:”/”, “handler”:Handlers.main])

Back in “Handlers.swift”, we are going to update the “main” function.

Note: To access the file handling functions, we have to add import PerfectLib to the “import” area towards the top of this file.

After the “request, response in” line, add a few new lines, this is where our new code will go.

Step 1: access the file uploads:

if let uploads = request.postFileUploads, uploads.count > 0 {

Step 2: Inside that, iterate over the resulting array:

for upload in uploads {

Step 3: Inside this iterator, we get to access the file, and move it to our uploads directory:

let thisFile = File(upload.tmpFileName)
do {
let _ = try thisFile.moveTo(
path: “./webroot/uploads/\(upload.fileName)”, overWrite: true
} catch {

Now, if we execute this right now, it’s just going to upload a file we choose, and put it into the uploads directory — but we’re not finished!

Listing uploaded files

Mustache templating allows us to pass dynamic content for display, via a dictionary “context”.

Initialize this by putting this code immediately after the request, response in line:

var context = [“files”:[[String:String]]()]

This gives us an empty “files” array in the dictionary.

To list the files, we will need to create a Dir object, iterate through that object, and add a name/value pair for each to this “files” array.

let d = Dir(“./webroot/uploads”)
try d.forEachEntry(closure: {f in
} catch {

Now, pass the “context” to the Mustache processor by adding the reference:

response.render(template: “templates/index”, context: context)

All we need to do now is add 2 lines to our “index.mustache” and we’re done.


Now, we can run the project, and see it in action. When you select a file to upload and initiate it, you’ll see that file appear in the “uploads” directory as well as in the files list in the page.

There are many other things we can do with file uploads, files and directories. I encourage you to check out the following documentation links:

The source code for the “complete” example here is available at:

If you have any questions, please join the Perfect Slack channel via, and say Hi!