Thursday, September 11, 2025
HomeiOS DevelopmentFile add utilizing Vapor 4

File add utilizing Vapor 4


Constructing a file add kind

Let’s begin with a fundamental Vapor undertaking, we will use Leaf (the Tau launch) for rendering our HTML information. You must observe that Tau was an experimental launch, the modifications have been reverted from the ultimate 4.0.0 Leaf launch, however you possibly can nonetheless use Tau in case you pin the precise model in your manifest file. Tau will probably be revealed afterward in a standalone repository… 🤫


import PackageDescription

let bundle = Bundle(
    title: "myProject",
    platforms: [
       .macOS(.v10_15)
    ],
    dependencies: [
        .package(url: "https://github.com/vapor/vapor", from: "4.35.0"),
        .package(url: "https://github.com/vapor/leaf", .exact("4.0.0-tau.1")),
        .package(url: "https://github.com/vapor/leaf-kit", .exact("1.0.0-tau.1.1")),
    ],
    targets: [
        .target(
            name: "App",
            dependencies: [
                .product(name: "Leaf", package: "leaf"),
                .product(name: "LeafKit", package: "leaf-kit"),
                .product(name: "Vapor", package: "vapor"),
            ],
            swiftSettings: [
                .unsafeFlags(["-cross-module-optimization"], .when(configuration: .launch))
            ]
        ),
        .goal(title: "Run", dependencies: [.target(name: "App")]),
        .testTarget(title: "AppTests", dependencies: [
            .target(name: "App"),
            .product(name: "XCTVapor", package: "vapor"),
        ])
    ]
)

Now in case you open the undertaking with Xcode, remember to setup a customized working listing first, as a result of we will create templates and Leaf will search for these view information underneath the present working listing by default. We’re going to construct a quite simple index.leaf file, you possibly can place it into the Assets/Views listing.



  
    
    
    File add instance
  
  
    

    
  

As you possibly can see, it is an ordinary file add kind, once you need to add information utilizing the browser you at all times have to make use of the multipart/form-data encryption sort. The browser will pack each subject within the kind (together with the file knowledge with the unique file title and a few meta data) utilizing a particular format and the server utility can parse the contents of this. Happily Vapor has built-in help for straightforward decoding multipart kind knowledge values. We’re going to use the POST /add route to save lots of the file, let’s setup the router first so we will render our principal web page and we’re going to put together our add path as effectively, however we are going to reply with a dummy message for now.

import Vapor
import Leaf

public func configure(_ app: Utility) throws {

    
    app.routes.defaultMaxBodySize = "10mb"
    
    
    app.middleware.use(FileMiddleware(publicDirectory: app.listing.publicDirectory))
    
    
    LeafRenderer.Choice.caching = .bypass
    app.views.use(.leaf)

    
    app.get { req in
        req.leaf.render(template: "index")
    }
    
    
    app.submit("add") { req in
        "Add file..."
    }
}

You may put the snippet above into your configure.swift file then you possibly can attempt to construct and run your server and go to http://localhost:8080, then attempt to add any file. It will not really add the file, however at the very least we’re ready to put in writing our server facet Swift code to course of the incoming kind knowledge. ⬆️

File add handler in Vapor

Now that we have now a working uploader kind we must always parse the incoming knowledge, get the contents of the file and place it underneath our Public listing. You may really transfer the file wherever in your server, however for this instance we’re going to use the Public listing so we will merely take a look at if everthing works by utilizing the FileMiddleware. If you do not know, the file middleware serves the whole lot (publicly obtainable) that’s situated inside your Public folder. Let’s code.

app.submit("add") { req -> EventLoopFuture<String> in
    struct Enter: Content material {
        var file: File
    }
    let enter = strive req.content material.decode(Enter.self)
    
    let path = app.listing.publicDirectory + enter.file.filename
    
    return req.utility.fileio.openFile(path: path,
                                           mode: .write,
                                           flags: .allowFileCreation(posixMode: 0x744),
                                           eventLoop: req.eventLoop)
        .flatMap { deal with in
            req.utility.fileio.write(fileHandle: deal with,
                                         buffer: enter.file.knowledge,
                                         eventLoop: req.eventLoop)
                .flatMapThrowing { _ in
                    strive deal with.shut()
                    return enter.file.filename
                }
        }
}

So, let me clarify what simply occurred right here. First we outline a brand new Enter sort that may include our file knowledge. There’s a File sort in Vapor that helps us decoding multipart file add kinds. We will use the content material of the request and decode this sort. We gave the file title to the file enter kind beforehand in our leaf template, however in fact you possibly can change it, however in case you accomplish that you additionally should align the property title contained in the Enter struct.

After we have now an enter (please observe that we do not validate the submitted request but) we will begin importing our file. We ask for the situation of the general public listing, we append the incoming file title (to maintain the unique title, however you possibly can generate a brand new title for the uploaded file as effectively) and we use the non-blocking file I/O API to create a file handler and write the contents of the file into the disk. The fileio API is a part of SwiftNIO, which is nice as a result of it is a non-blocking API, so our server will probably be extra performant if we use this as a substitute of the common FileManager from the Basis framework. After we opened the file, we write the file knowledge (which is a ByteBuffer object, unhealthy naming…) and eventually we shut the opened file handler and return the uploaded file title as a future string. If you have not heard about futures and guarantees it is best to examine them, as a result of they’re in all places on the server facet Swift world. Cannot look forward to async / awake help, proper? 😅

We are going to improve the add end result web page just a bit bit. Create a brand new end result.leaf file contained in the views listing.



  
    
    
    File uploaded
  
  
    

    #if(isImage):
        
#else: Present me!
#endif Add new one

So we will examine if the uploaded file has a picture extension and go an isImage parameter to the template engine, so we will show it if we will assume that the file is a picture, in any other case we will render a easy hyperlink to view the file. Contained in the submit add handler methodology we’re going to add a date prefix to the uploaded file so we can add a number of information even with the identical title.

app.submit("add") { req -> EventLoopFuture<View> in
    struct Enter: Content material {
        var file: File
    }
    let enter = strive req.content material.decode(Enter.self)

    guard enter.file.knowledge.readableBytes > 0 else {
        throw Abort(.badRequest)
    }

    let formatter = DateFormatter()
    formatter.dateFormat = "y-m-d-HH-MM-SS-"
    let prefix = formatter.string(from: .init())
    let fileName = prefix + enter.file.filename
    let path = app.listing.publicDirectory + fileName
    let isImage = ["png", "jpeg", "jpg", "gif"].accommodates(enter.file.extension?.lowercased())

    return req.utility.fileio.openFile(path: path,
                                           mode: .write,
                                           flags: .allowFileCreation(posixMode: 0x744),
                                           eventLoop: req.eventLoop)
        .flatMap { deal with in
            req.utility.fileio.write(fileHandle: deal with,
                                         buffer: enter.file.knowledge,
                                         eventLoop: req.eventLoop)
                .flatMapThrowing { _ in
                    strive deal with.shut()
                }
                .flatMap {
                    req.leaf.render(template: "end result", context: [
                        "fileUrl": .string(fileName),
                        "isImage": .bool(isImage),
                    ])
                }
        }
}

In the event you run this instance it is best to be capable to view the picture or the file straight from the end result web page.

A number of file add utilizing Vapor

By the best way, you can too add a number of information without delay in case you add the a number of attribute to the HTML file enter subject and use the information[] worth as title.


To help this we have now to change our add methodology, don’t fret it isn’t that sophisticated because it seems at first sight. 😜

app.submit("add") { req -> EventLoopFuture<View> in
    struct Enter: Content material {
        var information: [File]
    }
    let enter = strive req.content material.decode(Enter.self)

    let formatter = DateFormatter()
    formatter.dateFormat = "y-m-d-HH-MM-SS-"
    let prefix = formatter.string(from: .init())
    
    struct UploadedFile: LeafDataRepresentable {
        let url: String
        let isImage: Bool
        
        var leafData: LeafData {
            .dictionary([
                "url": url,
                "isImage": isImage,
            ])
        }
    }
    
    let uploadFutures = enter.information
        .filter { $0.knowledge.readableBytes > 0 }
        .map { file -> EventLoopFuture<UploadedFile> in
            let fileName = prefix + file.filename
            let path = app.listing.publicDirectory + fileName
            let isImage = ["png", "jpeg", "jpg", "gif"].accommodates(file.extension?.lowercased())
            
            return req.utility.fileio.openFile(path: path,
                                                   mode: .write,
                                                   flags: .allowFileCreation(posixMode: 0x744),
                                                   eventLoop: req.eventLoop)
                .flatMap { deal with in
                    req.utility.fileio.write(fileHandle: deal with,
                                                 buffer: file.knowledge,
                                                 eventLoop: req.eventLoop)
                        .flatMapThrowing { _ in
                            strive deal with.shut()
                            return UploadedFile(url: fileName, isImage: isImage)
                        }
                    
                }
        }

    return req.eventLoop.flatten(uploadFutures).flatMap { information in
        req.leaf.render(template: "end result", context: [
            "files": .array(files.map(.leafData))
        ])
    }
}

The trick is that we have now to parse the enter as an array of information and switch each doable add right into a future add operation. We will filter the add candidates by readable byte dimension, then we map the information into futures and return an UploadedFile end result with the correct file URL and is picture flag. This construction is a LeafDataRepresentable object, as a result of we need to go it as a context variable to our end result template. We even have to vary that view as soon as once more.



  
    
    
    Recordsdata uploaded
  
  
    

    #for(file in information):
        #if(file.isImage):
        
#else: #(file.url)
#endif #endfor Add new information

Nicely, I do know it is a lifeless easy implementation, but it surely’s nice if you wish to follow or learn to implement file uploads utilizing server facet Swift and the Vapor framework. It’s also possible to add information on to a cloud service utilizing this system, there’s a library referred to as Liquid, which has similarities to Fluent, however for file storages. At present you need to use Liquid to add information to the native storage or you need to use an AWS S3 bucket or you possibly can write your personal driver utilizing LiquidKit. The API is fairly easy to make use of, after you configure the motive force you possibly can add information with just some strains of code.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments