Categories
Technology

Serving ‘files’ in Vapor

In my experience, dynamically generating a file, serving it immediately, and not persisting it on the server is a pretty common use case. In general, this is one of two things – either a PDF download, or a CSV. While my Vapor tinkering hasn’t yet given me an opportunity to generate PDFs on the server, I have had an occasion to create a CSV, and wrote up a little helper for doing so.

import Vapor

struct TextFileResponse {
    enum ResponseType {
        case inline, attachment(filename: String)
    }
    
    var body: String
    var type: ResponseType
    var contentType: String
}

extension TextFileResponse: ResponseEncodable {
    public func encodeResponse(for request: Request) -> EventLoopFuture<Response> {
        var headers = HTTPHeaders()
        headers.add(name: .contentType, value: contentType)
        switch type {
        case .inline:
            headers.add(name: .contentDisposition, value: "inline")
        case .attachment(let filename):
            headers.add(name: .contentDisposition, value: "attachment; filename=\"\(filename)\"")
        }
        return request.eventLoop.makeSucceededFuture(.init(status: .ok, headers: headers, body: .init(string: body)))
    }
}

That’ll work for any file you can assemble as text; CSV just struck me as being the most useful example. Use ResponseType.inline for a file you want displayed in a browser tab, and .attachment if it’s for downloading.

And if you’re doing a lot of CSVs, give yourself a nice little helper:

extension TextFileResponse {
    static func csv(body: String, name: String) -> TextFileResponse {
        .init(body: body, type: .attachment(filename: name), contentType: "text/csv")
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.