Close Menu
geekfence.comgeekfence.com
    What's Hot

    What Productivity Really Means – O’Reilly

    November 12, 2025

    The EU’s AI Act

    November 12, 2025

    The economics of the software development business

    November 12, 2025
    Facebook X (Twitter) Instagram
    • About Us
    • Contact Us
    Facebook Instagram
    geekfence.comgeekfence.com
    • Home
    • UK Tech News
    • AI
    • Big Data
    • Cyber Security
      • Cloud Computing
      • iOS Development
    • IoT
    • Mobile
    • Software
      • Software Development
      • Software Engineering
    • Technology
      • Green Technology
      • Nanotechnology
    • Telecom
    geekfence.comgeekfence.com
    Home»iOS Development»File upload using Vapor 4
    iOS Development

    File upload using Vapor 4

    AdminBy AdminNovember 12, 2025No Comments8 Mins Read0 Views
    Facebook Twitter Pinterest LinkedIn Telegram Tumblr Email
    File upload using Vapor 4
    Share
    Facebook Twitter LinkedIn Pinterest Email


    Learn how to implement a basic HTML file upload form using the Leaf template engine and Vapor, all written in Swift of course.

    Building a file upload form

    Let’s start with a basic Vapor project, we’re going to use Leaf (the Tau release) for rendering our HTML files. You should note that Tau was an experimental release, the changes were reverted from the final 4.0.0 Leaf release, but you can still use Tau if you pin the exact version in your manifest file. Tau will be published later on in a standalone repository… 🤫

    // swift-tools-version:5.3
    import PackageDescription
    
    let package = Package(
        name: "myProject",
        platforms: [
           .macOS(.v10_15)
        ],
        dependencies: [
            .package(url: " from: "4.35.0"),
            .package(url: " .exact("4.0.0-tau.1")),
            .package(url: " .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: .release))
                ]
            ),
            .target(name: "Run", dependencies: [.target(name: "App")]),
            .testTarget(name: "AppTests", dependencies: [
                .target(name: "App"),
                .product(name: "XCTVapor", package: "vapor"),
            ])
        ]
    )
    

    Now if you open the project with Xcode, don’t forget to setup a custom working directory first, because we’re going to create templates and Leaf will look for those view files under the current working directory by default. We are going to build a very simple index.leaf file, you can place it into the Resources/Views directory.

    
    
      
        
        
        File upload example
      
      
        
    
        
      
    
    

    As you can see, it’s a standard file upload form, when you want to upload files using the browser you always have to use the multipart/form-data encryption type. The browser will pack every field in the form (including the file data with the original file name and some meta info) using a special format and the server application can parse the contents of this. Fortunately Vapor has built-in support for easy decoding multipart form data values. We are going to use the POST /upload route to save the file, let’s setup the router first so we can render our main page and we are going to prepare our upload path as well, but we will respond with a dummy message for now.

    import Vapor
    import Leaf
    
    public func configure(_ app: Application) throws {
    
        /// config max upload file size
        app.routes.defaultMaxBodySize = "10mb"
        
        /// setup public file middleware (for hosting our uploaded files)
        app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))
        
        /// setup Leaf template engine
        LeafRenderer.Option.caching = .bypass
        app.views.use(.leaf)
    
        /// index route
        app.get { req in
            req.leaf.render(template: "index")
        }
        
        /// upload handler
        app.post("upload") { req in
            "Upload file..."
        }
    }
    

    You can put the snippet above into your configure.swift file then you can try to build and run your server and visit http://localhost:8080, then try to upload any file. It won’t actually upload the file, but at least we are prepared to write our server side Swift code to process the incoming form data. ⬆️

    File upload handler in Vapor

    Now that we have a working uploader form we should parse the incoming data, get the contents of the file and place it under our Public directory. You can actually move the file anywhere on your server, but for this example we are going to use the Public directory so we can simply test if everthing works by using the FileMiddleware. If you don’t know, the file middleware serves everything (publicly available) that is located inside your Public folder. Let’s code.

    app.post("upload") { req -> EventLoopFuture in
        struct Input: Content {
            var file: File
        }
        let input = try req.content.decode(Input.self)
        
        let path = app.directory.publicDirectory + input.file.filename
        
        return req.application.fileio.openFile(path: path,
                                               mode: .write,
                                               flags: .allowFileCreation(posixMode: 0x744),
                                               eventLoop: req.eventLoop)
            .flatMap { handle in
                req.application.fileio.write(fileHandle: handle,
                                             buffer: input.file.data,
                                             eventLoop: req.eventLoop)
                    .flatMapThrowing { _ in
                        try handle.close()
                        return input.file.filename
                    }
            }
    }
    

    So, let me explain what just happened here. First we define a new Input type that will contain our file data. There is a File type in Vapor that helps us decoding multipart file upload forms. We can use the content of the request and decode this type. We gave the file name to the file input form previously in our leaf template, but of course you can change it, but if you do so you also have to align the property name inside the Input struct.

    After we have an input (please note that we don’t validate the submitted request yet) we can start uploading our file. We ask for the location of the public directory, we append the incoming file name (to keep the original name, but you can generate a new name for the uploaded file as well) 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 part of SwiftNIO, which is great because it’s a non-blocking API, so our server will be more performant if we use this instead of the regular FileManager from the Foundation framework. After we opened the file, we write the file data (which is a ByteBuffer object, bad naming…) and finally we close the opened file handler and return the uploaded file name as a future string. If you haven’t heard about futures and promises you should read about them, because they are everywhere on the server side Swift world. Can’t wait for async / awake support, right? 😅

    We will enhance the upload result page just a little bit. Create a new result.leaf file inside the views directory.

    
    
      
        
        
        File uploaded
      
      
        
    
        #if(isImage):
            File upload using Vapor 4
    #else: Show me!
    #endif Upload new one

    So we’re going to check if the uploaded file has an image extension and pass an isImage parameter to the template engine, so we can display it if we can assume that the file is an image, otherwise we’re going to render a simple link to view the file. Inside the post upload handler method we are going to add a date prefix to the uploaded file so we will be able to upload multiple files even with the same name.

    app.post("upload") { req -> EventLoopFuture in
        struct Input: Content {
            var file: File
        }
        let input = try req.content.decode(Input.self)
    
        guard input.file.data.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 + input.file.filename
        let path = app.directory.publicDirectory + fileName
        let isImage = ["png", "jpeg", "jpg", "gif"].contains(input.file.extension?.lowercased())
    
        return req.application.fileio.openFile(path: path,
                                               mode: .write,
                                               flags: .allowFileCreation(posixMode: 0x744),
                                               eventLoop: req.eventLoop)
            .flatMap { handle in
                req.application.fileio.write(fileHandle: handle,
                                             buffer: input.file.data,
                                             eventLoop: req.eventLoop)
                    .flatMapThrowing { _ in
                        try handle.close()
                    }
                    .flatMap {
                        req.leaf.render(template: "result", context: [
                            "fileUrl": .string(fileName),
                            "isImage": .bool(isImage),
                        ])
                    }
            }
    }
    

    If you run this example you should be able to view the image or the file straight from the result page.

    Multiple file upload using Vapor

    By the way, you can also upload multiple files at once if you add the multiple attribute to the HTML file input field and use the files[] value as name.


    To support this we have to alter our upload method, don’t worry it’s not that complicated as it looks at first sight. 😜

    app.post("upload") { req -> EventLoopFuture in
        struct Input: Content {
            var files: [File]
        }
        let input = try req.content.decode(Input.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 = input.files
            .filter { $0.data.readableBytes > 0 }
            .map { file -> EventLoopFuture in
                let fileName = prefix + file.filename
                let path = app.directory.publicDirectory + fileName
                let isImage = ["png", "jpeg", "jpg", "gif"].contains(file.extension?.lowercased())
                
                return req.application.fileio.openFile(path: path,
                                                       mode: .write,
                                                       flags: .allowFileCreation(posixMode: 0x744),
                                                       eventLoop: req.eventLoop)
                    .flatMap { handle in
                        req.application.fileio.write(fileHandle: handle,
                                                     buffer: file.data,
                                                     eventLoop: req.eventLoop)
                            .flatMapThrowing { _ in
                                try handle.close()
                                return UploadedFile(url: fileName, isImage: isImage)
                            }
                        
                    }
            }
    
        return req.eventLoop.flatten(uploadFutures).flatMap { files in
            req.leaf.render(template: "result", context: [
                "files": .array(files.map(\.leafData))
            ])
        }
    }
    

    The trick is that we have to parse the input as an array of files and turn every possible upload into a future upload operation. We can filter the upload candidates by readable byte size, then we map the files into futures and return an UploadedFile result with the proper file URL and is image flag. This structure is a LeafDataRepresentable object, because we want to pass it as a context variable to our result template. We also have to change that view once again.

    
    
      
        
        
        Files uploaded
      
      
        
    
        #for(file in files):
            #if(file.isImage):
            
    #else: #(file.url)
    #endif #endfor Upload new files

    Well, I know this is a dead simple implementation, but it’s great if you want to practice or learn how to implement file uploads using server side Swift and the Vapor framework. You can also upload files directly to a cloud service using this technique, there is a library called Liquid, which is similar to Fluent, but for file storages. Currently you can use Liquid to upload files to the local storage or you can use an AWS S3 bucket or you can write your own driver using LiquidKit. The API is pretty simple to use, after you configure the driver you can upload files with just a few lines of code.



    Source link

    Share. Facebook Twitter Pinterest LinkedIn Tumblr Email

    Related Posts

    SwiftUI can’t have working rounded corners with AsyncImage and ultra-wide images

    November 11, 2025

    Transitions in SwiftUI · objc.io

    November 10, 2025

    Keyboard shortcuts for Export Unmodified Original in Photos for Mac – Ole Begemann

    November 9, 2025

    Using Tool Calling to Supercharge Foundation Models

    November 8, 2025

    A deep dive into Collections, Sequences, and Iterators in Swift – Donny Wals

    November 6, 2025

    Create a multi-line, editable text view using TextEditor in SwiftUI. – iOSTutorialJunction

    November 5, 2025
    Top Posts

    Microsoft 365 Copilot now enables you to build apps and workflows

    October 29, 20256 Views

    Here’s the latest company planning for gene-edited babies

    November 2, 20254 Views

    Skills, Roles & Career Guide

    November 4, 20252 Views
    Don't Miss

    What Productivity Really Means – O’Reilly

    November 12, 2025

    We’ve been bombarded with claims about how much generative AI improves software developer productivity: It…

    The EU’s AI Act

    November 12, 2025

    The economics of the software development business

    November 12, 2025

    Sophos Firewall v22 security enhancements – Sophos News

    November 12, 2025
    Stay In Touch
    • Facebook
    • Instagram
    About Us

    At GeekFence, we are a team of tech-enthusiasts, industry watchers and content creators who believe that technology isn’t just about gadgets—it’s about how innovation transforms our lives, work and society. We’ve come together to build a place where readers, thinkers and industry insiders can converge to explore what’s next in tech.

    Our Picks

    What Productivity Really Means – O’Reilly

    November 12, 2025

    The EU’s AI Act

    November 12, 2025

    Subscribe to Updates

    Please enable JavaScript in your browser to complete this form.
    Loading
    • About Us
    • Contact Us
    • Disclaimer
    • Privacy Policy
    • Terms and Conditions
    © 2025 Geekfence.All Rigt Reserved.

    Type above and press Enter to search. Press Esc to cancel.