Handling File Uploads with Multipart Form Data

Hi,

I am trying to create a route to upload a file in the Public/uploads directory of my App. For this purpose I added the multipart-kit to my package.

Here is my route:

    app.post("upload") { req async throws -> String in
        let file = try req.content.decode(File.self)
        guard file.filename.hasSuffix(".jpg") || file.filename.hasSuffix(".png") else {
            throw Abort(.badRequest, reason: "Only JPG or PNG files allowed")
        }
        let path = req.application.directory.publicDirectory.appending(file.filename)
        try await req.fileio.writeFile(file.data, at: path)
        return "Uploaded file: \(file.filename)"
    }

I have a file appicon.png stored in the project root directory. But when I try to test the route with curl :

curl -X POST http://127.0.0.1:8080/upload -F file=@appicon.png

I get the following error :

No such key 'data' at path ''. No value associated with key "data"

I guess it must be quite straightforward mistake but I am unable to fix it.

Someone can help ?

I believe this is because the decoder expects the file to be nested, since that’s what the body will look like. So it should work with

struct MyType: Decodable {
  let file: File // note that the name here much match the key of the file here, in your case `file` because its `file=@...`, if you had `image=@...` then they property name would be `image`
}

Then do

let upload = try req.content.decode(MyType.self)

@0xTim thanks. It seems to work.

This is how I defined MyType:

struct MyType: Decodable {
  let file: File
    var name: String {
        return file.filename
    }
    var data: ByteBuffer {
        return file.data
    }
}

Otherwise in the route you have to use file.file.filname to refer to the filename. Here only file.name and file.data is necessary.

Now I am facing an issue with curl: instead of picking the file in the current directory, I don't know for what reason it is looking it in the Library/Developer/Xcode/DerivedData/Build/Products/Debug/Public folder !

I’m assuming the issue is where you’re saving the file? You need to set the custom working directory in Xcode:

Ok ! I just found out it was not on curl as I remembered there is a warning about that in the docs.

Now it fully works ! Thanks @0xTim

Just one more issue.

It is working fine with the path as defined by:

 let path = req.application.directory.publicDirectory.appending(file.name)

This places the image direclty in the Public folder whereas I would like to have it in the Uploads subfolder.

However using:

 let path = req.application.directory.publicDirectory.appending("Uploads/\(file.name)")

does not do anything (giving no error by the way).

This will probably give you /Public/Uploads/filename because you’re referencing publicDirectory.

Try printing out the variable & using

req.application.directory.workingDirectory

@evilmint I put a breakpoint and checked value of path and it is exactly what I expect, ie:

path = .../VaporProjects/myApp/Public/Uploads/appicon.png

But the file appicon.png does not get uploaded.

Whereas, with :

let path = req.application.directory.publicDirectory.appending(file.name)

so

path = .../VaporProjects/myApp/Public/appicon.png

and the file gets uploaded as expected in the Public folder.

I'd recommend you to inspect the built app: the hierarchy in Xcode might not (and most likely will not) reflect the hierarchy in the built app, I seen cases when the latter was a flattened out version.

Also, could you not use Bundle.main.url(forResource:, withExtension:) ?

1 Like

@tera could you be more specific what you mean by "inspecting the built app" ?

Regarding Bundle.main.url(forResource:, withExtension:) I am not sure how you want to use it : I believe fileio.writeFile() expects a String path not an url. And in this case it is of a folder not of a resource.

Sorry, I don't catch you.

In the build log (shown in the report navigator tab) you'd see where the app is being built to, it will be a path like so:

/Users/user/Library/Developer/Xcode/DerivedData/AppName-gjzhjglmtabhvjggthttwgfehj/Build/Products/Debug/TheApp.app

Navigate to that folder and show the package contents to reveal the actual file hierarchy inside the app package.

As for the string - you could convert URL to a path string with url.path()

@tera sorry I have no idea where to find the mentionned "report navigator tab."

Regarding the path of the file, I don't think it is the issue since I checked in Debug mode and as mentionned before the path as a String looks correct:

path = .../VaporProjects/myApp/Public/Uploads/appicon.png

It is just that the route does not write the file into the said path.

I meant this location in Xcode's UI:

I might have misread the question though. I assumed you have a fixed set of fixed resources you are shipping with your app that you'd like to upload, and the only way to change those files is via rebuilding the app.

@tera

Well, good news : I tried it again this morning and now it seems to work fine. My png file was finally uploaded into the Public/Uploads folder as expected.

(The only bad news is I have no idea what was wrong las week)

Thanks for your time and sorry for the mistake.