Backend Development 18 min read

Building a RESTful API with Perfect in Swift: A Server‑Side Development Guide

This article walks through building a RESTful API using the Perfect framework in Swift, covering framework comparison, environment setup with Docker, project initialization, routing, database integration via MySQL‑Swift, error handling, logging, deployment, and lessons learned from server‑side Swift development.

Qunar Tech Salon
Qunar Tech Salon
Qunar Tech Salon
Building a RESTful API with Perfect in Swift: A Server‑Side Development Guide

Since Swift’s release, developers have been eager to use it for server‑side programming, especially after it became open‑source and Linux‑compatible in December 2015. Various Swift web frameworks (Kitura, Perfect, Vapor, Zewo) emerged, each with its own strengths.

The author evaluated these frameworks and chose Perfect for a simple internal information‑management platform because it offered a balanced feature set and acceptable weight for a lightweight CRUD project.

Environment configuration – macOS development uses Xcode, while Linux deployment relies on Docker (Ubuntu images) to provide a consistent Swift toolchain. Dependencies are managed with Swift Package Manager (SPM), and third‑party libraries such as SwiftLog and MySQL‑Swift are added.

Coding steps – The project is created with swift package init , then swift package generate-xcodeproj for Xcode support. SPM builds the executable and resolves dependencies. Routing follows an Express‑like API: a Routes object is created, base URI "/api" is set, and individual routes (e.g., GET /staff/{id} ) are added.

func makeURLRoutes() -> Routes {
    var routes = Routes()
    var apiRoutes = Routes(baseUri: "/api")
    var api = Routes()
    api.add(method: .get, uri: "/staff/{id}", handler: fetchStaffById)
    apiRoutes.add(routes: api)
    routes.add(routes: apiRoutes)
    SLogWarning("\(routes.navigator.description)")
    return routes
}

Perfect defines typealias RequestHandler = (HTTPRequest, HTTPResponse) -> () , so each handler receives request and response objects. The defer statement ensures response.completed() is called regardless of early exits.

defer {
    response.completed()
}

Database operations use MySQL‑Swift with a connection pool. Queries are executed inside a closure passed to pool.execute , leveraging Swift’s generics and higher‑order functions. Errors are caught with do‑catch and distinguished via switch on the error type.

do {
    let staffData = try StaffDataBaseUtil.sharedInstance.searchStaffByID(idString)
    try response.setBody(json: jsonBody(errorCode: returnCode, data: ["staff": staffData]))
} catch let error {
    switch error {
    case let error as QueryError:
        // database error handling
    default:
        // generic server error handling
    }
}

Perfect provides its own JSON extensions, allowing dictionaries to be encoded directly without external libraries.

extension Dictionary: JSONConvertible {
    public func jsonEncodedString() throws -> String {
        var s = "{"
        var first = true
        for (k, v) in self {
            guard let strKey = k as? String else { throw JSONConversionError.invalidKey(k) }
            if !first { s.append(",") } else { first = false }
            s.append(try strKey.jsonEncodedString())
            s.append(":")
            s.append(try jsonEncodedStringWorkAround(v))
        }
        s.append("}")
        return s
    }
}

Timezone differences between macOS and Linux caused timestamp mismatches; the fix was to set DateFormatter.timeZone explicitly (e.g., "Asia/Shanghai").

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
dateFormatter.timeZone = TimeZone(identifier: "Asia/Shanghai")
let dateString = dateFormatter.string(from: timestamp.date())

Logging is handled with SwiftLog; a custom middleware logs request URL, method, parameters, and headers.

func incomeMiddleware(request: HTTPRequest) {
    SLogInfo("Request URL: " + request.uri)
    SLogInfo("Request Method: " + request.method.description)
    SLogVerbose("Request Params: " + String(describing: request.params()))
    for (name, detail) in request.headers {
        SLogVerbose("Request HEADER: " + name.standardName.uppercased() + " -> " + detail)
    }
}

Deployment bundles the compiled binary and required shared libraries (libCHTTPParser.so, libCOpenSSL.so, libLinuxBridge.so) into a Docker container. Swift officially supports Ubuntu; attempts on CentOS require manual compilation and are not recommended for production.

Conclusion – Swift brings strong typing, protocol extensions, and expressive syntax to server‑side development, but challenges remain: ABI stability, cross‑platform compatibility, and limited Linux ecosystem support. The community’s Server APIs Work Group aims to address these gaps, and future Swift releases are expected to improve Linux support and tooling.

DockermysqlSwiftspmRESTful APIPerfectserver-side
Qunar Tech Salon
Written by

Qunar Tech Salon

Qunar Tech Salon is a learning and exchange platform for Qunar engineers and industry peers. We share cutting-edge technology trends and topics, providing a free platform for mid-to-senior technical professionals to exchange and learn.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.