Swift isn't just for iOS/macOS — it also runs on Linux and has a strong ecosystem for server-side development. The same language, the same types, on both client and server. In this guide we'll take a deep look at the Swift on Server ecosystem.
💡 Quick Note: The Swift Server Work Group (SSWG) is the official group, sponsored by Apple, that governs the server-side Swift ecosystem.
Table of Contents
- Swift on Linux
- Platform Differences
- SwiftNIO Fundamentals
- Vapor vs Hummingbird
- Database Integration
- Structured Concurrency on Server
- Docker and Deployment
- Serverless Swift with AWS Lambda
- Sharing Models Between Client and Server
- Monitoring and Logging
- Error Handling Strategies
- SSWG Package Ecosystem
- Production Checklist
- Performance Benchmarks
Swift on Linux
Swift is officially supported on Ubuntu, Amazon Linux, and CentOS:
1# Installing Swift on Ubuntu2wget https://download.swift.org/swift-5.9.2-release/ubuntu2204/swift-5.9.2-RELEASE/swift-5.9.2-RELEASE-ubuntu22.04.tar.gz3tar xzf swift-5.9.2-RELEASE-ubuntu22.04.tar.gz4export PATH=$PWD/swift-5.9.2-RELEASE-ubuntu22.04/usr/bin:$PATH5swift --versionPlatform Differences
Feature | macOS | Linux |
|---|---|---|
Foundation | Apple Foundation | swift-corelibs-foundation |
Dispatch | libdispatch | libdispatch |
Objective-C | ✅ Available | ❌ Not available |
UIKit/AppKit | ✅ Available | ❌ Not available |
Crypto | CryptoKit | swift-crypto |
async/await | ✅ | ✅ |
SwiftNIO Fundamentals
SwiftNIO is Apple's event-driven networking framework. It's inspired by Netty:
1import NIO2 3// Simple echo server4let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)5let bootstrap = ServerBootstrap(group: group)6 .childChannelInitializer { channel in7 channel.pipeline.addHandler(EchoHandler())8 }9 .childChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)10 11let channel = try bootstrap.bind(host: "0.0.0.0", port: 8080).wait()12print("Server running on \(channel.localAddress!)")13try channel.closeFuture.wait()14 15class EchoHandler: ChannelInboundHandler {16 typealias InboundIn = ByteBuffer17 typealias OutboundOut = ByteBuffer18 19 func channelRead(context: ChannelHandlerContext, data: NIOAny) {20 // Send the incoming data straight back21 context.write(data, promise: nil)22 }23 24 func channelReadComplete(context: ChannelHandlerContext) {25 context.flush()26 }27}Vapor vs Hummingbird
Feature | Vapor | Hummingbird |
|---|---|---|
Maturity | Since 2016 | Since 2021 |
Community | Large | Growing |
ORM | Fluent (built-in) | External |
WebSocket | ✅ Built-in | ✅ Plugin |
Templating | Leaf | Mustache |
Performance | High | Very high |
Size | Large framework | Minimal, modular |
Learning curve | Medium | Low |
1// Hummingbird minimal server2import Hummingbird3 4let app = HBApplication(configuration: .init(address: .hostname("0.0.0.0", port: 8080)))5app.router.get("/hello") { _ in6 "Hello from Hummingbird!"7}8try await app.start()Database Integration
1// PostgreSQL (Fluent + Vapor)2app.databases.use(.postgres(3 hostname: Environment.get("DB_HOST") ?? "localhost",4 port: 5432,5 username: "vapor",6 password: "secret",7 database: "myapp"8), as: .psql)9 10// MongoDB (MongoKitten)11import MongoKitten12let db = try await MongoDatabase.connect(to: "mongodb://localhost/myapp")13let users = db["users"]14let results = try await users.find(["age": ["$gte": 18]]).decode(User.self).allResults()Structured Concurrency on Server
1// Server-side async/await2func handleRequest(req: Request) async throws -> Response {3 async let user = fetchUser(id: req.userId)4 async let orders = fetchOrders(userId: req.userId)5 async let recommendations = fetchRecommendations(userId: req.userId)6 7 let dashboard = try await DashboardResponse(8 user: user,9 orders: orders,10 recommendations: recommendations11 )12 return Response(status: .ok, body: .init(data: try JSONEncoder().encode(dashboard)))13}Docker and Deployment
1# Multi-stage build - small final image2FROM swift:5.9-jammy as build3WORKDIR /app4COPY Package.* ./5RUN swift package resolve6COPY . .7RUN swift build -c release --static-swift-stdlib8 9FROM ubuntu:22.0410RUN apt-get update && apt-get install -y libcurl4 && rm -rf /var/lib/apt/lists/*11COPY --from=build /app/.build/release/App /usr/local/bin/12EXPOSE 808013CMD ["App", "serve", "--hostname", "0.0.0.0", "--port", "8080"]1# docker-compose.yml2version: '3.8'3services:4 app:5 build: .6 ports: ["8080:8080"]7 environment:8 - DB_HOST=db9 - DATABASE_URL=postgres://vapor:secret@db:5432/myapp10 depends_on: [db]11 db:12 image: postgres:16-alpine13 environment:14 POSTGRES_USER: vapor15 POSTGRES_PASSWORD: secret16 POSTGRES_DB: myapp17 volumes: ["pgdata:/var/lib/postgresql/data"]18volumes:19 pgdata:Serverless Swift with AWS Lambda
1import AWSLambdaRuntime2 3@main4struct MyHandler: SimpleLambdaHandler {5 func handle(_ event: APIGatewayV2Request, context: LambdaContext) async throws -> APIGatewayV2Response {6 let name = event.queryStringParameters?["name"] ?? "World"7 return APIGatewayV2Response(8 statusCode: .ok,9 body: "Hello, \(name)!"10 )11 }12}Sharing Models Between Client and Server
1// SharedModels SPM package2// The iOS app and the Vapor backend use the same models3public struct CreateOrderRequest: Codable, Sendable {4 public let items: [OrderItem]5 public let shippingAddress: Address6 7 public struct OrderItem: Codable, Sendable {8 public let productId: UUID9 public let quantity: Int10 }11 12 public struct Address: Codable, Sendable {13 public let street: String14 public let city: String15 public let country: String16 }17}Monitoring and Logging
1import Logging2 3let logger = Logger(label: "com.myapp.server")4logger.info("Request received", metadata: ["path": "\(req.url.path)"])5logger.error("Database error", metadata: ["error": "\(error)"])6 7// Structured logging integrated with Grafana/ELKError Handling Strategies
Error handling in server-side Swift is critical in production applications:
1// Custom error type2enum AppError: AbortError {3 case userNotFound4 case invalidInput(String)5 case databaseError(String)6 case unauthorized7 case rateLimited8 9 var status: HTTPResponseStatus {10 switch self {11 case .userNotFound: return .notFound12 case .invalidInput: return .badRequest13 case .databaseError: return .internalServerError14 case .unauthorized: return .unauthorized15 case .rateLimited: return .tooManyRequests16 }17 }18 19 var reason: String {20 switch self {21 case .userNotFound: return "User not found"22 case .invalidInput(let detail): return "Invalid input: \(detail)"23 case .databaseError(let detail): return "Database error: \(detail)"24 case .unauthorized: return "You are not authorized"25 case .rateLimited: return "Too many requests, please wait"26 }27 }28}29 30// Global error middleware31struct ErrorHandlerMiddleware: AsyncMiddleware {32 func respond(to request: Request, chainingTo next: AsyncResponder) async throws -> Response {33 do {34 return try await next.respond(to: request)35 } catch let error as AppError {36 request.logger.warning("App error: \(error.reason)")37 let body = ErrorResponse(error: true, reason: error.reason)38 let response = Response(status: error.status)39 try response.content.encode(body)40 return response41 } catch {42 request.logger.error("Unexpected error: \(error)")43 let body = ErrorResponse(error: true, reason: "An unexpected error occurred")44 let response = Response(status: .internalServerError)45 try response.content.encode(body)46 return response47 }48 }49}50 51struct ErrorResponse: Content {52 let error: Bool53 let reason: String54}SSWG Package Ecosystem
Packages approved by the Swift Server Work Group and considered production-ready:
Package | Use | SSWG Level |
|---|---|---|
SwiftNIO | Event-driven networking | Graduated |
AsyncHTTPClient | HTTP client | Graduated |
SwiftLog | Structured logging | Graduated |
SwiftMetrics | Monitoring metrics | Graduated |
swift-crypto | Cryptography | Graduated |
PostgresNIO | PostgreSQL driver | Sandbox |
RediStack | Redis client | Sandbox |
MongoSwift | MongoDB driver | Sandbox |
Soto | AWS SDK for Swift | Incubating |
swift-distributed-actors | Distributed computing | Incubating |
1// Monitoring with SwiftMetrics2import Metrics3import Prometheus4 5// Prometheus metrics6let requestCounter = Counter(label: "http_requests_total", dimensions: [("method", ""), ("path", "")])7let requestDuration = Timer(label: "http_request_duration_seconds")8 9// Usage in middleware10struct MetricsMiddleware: AsyncMiddleware {11 func respond(to request: Request, chainingTo next: AsyncResponder) async throws -> Response {12 let startTime = DispatchTime.now()13 requestCounter.increment(dimensions: [14 ("method", request.method.rawValue),15 ("path", request.url.path)16 ])17 18 let response = try await next.respond(to: request)19 20 let duration = Double(DispatchTime.now().uptimeNanoseconds - startTime.uptimeNanoseconds) / 1_000_000_00021 requestDuration.record(duration)22 23 return response24 }25}Production Checklist
Checklist to work through before taking a server-side Swift application to production:
Item | Description | Priority |
|---|---|---|
TLS/HTTPS | Nginx reverse proxy or built-in TLS | Critical |
Rate limiting | Per-IP request limit | Critical |
CORS settings | Define allowed origins | High |
Health check | Liveness/readiness via a /health endpoint | High |
Structured logging | JSON format, log level configuration | High |
Graceful shutdown | Catch SIGTERM, finish in-flight requests | Medium |
Database pool | Configure the connection pool size | Medium |
Environment config | Read secrets from .env | Critical |
Monitoring | Prometheus + Grafana or Datadog | High |
Backup | Database backup strategy | Critical |
Performance Benchmarks
Framework | Req/sec (JSON) | Latency p99 | Memory |
|---|---|---|---|
Vapor | 85K | 2.1ms | 45MB |
Hummingbird | 120K | 1.4ms | 25MB |
Actix (Rust) | 180K | 0.8ms | 15MB |
Express (Node) | 42K | 4.8ms | 120MB |
Django (Python) | 8K | 22ms | 180MB |
Easter Egg
You found a hidden gem!
There's a hidden detail in this section. Want to uncover it?
GOLDEN TIP
The most valuable insight in this article
This tip holds the article's most important takeaway.
Reader Reward
Congratulations! Since you read this article all the way through, I have a special gift for you:
Sources:
Tags
iOS Development News
Weekly Swift tips, SwiftUI tricks and iOS best practices. No spam, only valuable content.
We respect your privacy. You can unsubscribe at any time.

