Swift en el servidor es una realidad madura en 2026. El ecosistema se apoya en dos pilares: Swift NIO, el framework de I/O asíncrono de bajo nivel creado por Apple, y Vapor, el framework web de alto nivel más adoptado que corre sobre NIO. Ambos funcionan en Linux, lo que abre la puerta a despliegues en contenedores Docker con imágenes de apenas 80 MB.
Swift NIO: el motor asíncrono
Swift NIO es un framework de I/O sin bloqueo basado en el modelo de event loop. Raramente se usa directamente en aplicaciones; es la base sobre la que corren Vapor, gRPC en Swift y el cliente HTTP de AWS SDK for Swift:
import NIO
import NIOPosix
// Servidor TCP mínimo con NIO
let grupo = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
defer { try? grupo.syncShutdownGracefully() }
let arranque = ServerBootstrap(group: grupo)
.serverChannelOption(.backlog, value: 256)
.serverChannelOption(.socketOption(.so_reuseaddr), value: 1)
.childChannelInitializer { canal in
canal.pipeline.addHandlers([
ByteToMessageHandler(LineBasedFrameDecoder()),
MiManejador()
])
}
let canal = try arranque.bind(host: "0.0.0.0", port: 8080).wait()
print("Servidor escuchando en (canal.localAddress!)")
try canal.closeFuture.wait()
El antipatrón más frecuente con NIO es bloquear el EventLoop con trabajo síncrono pesado. Nunca hagas llamadas síncronas a base de datos, I/O de ficheros o computación intensiva desde el loop:
// MAL: bloquea el EventLoop
eventLoop.execute {
let resultado = baseDatos.querySync("SELECT...") // NUNCA
}
// BIEN: delegar a un thread pool
eventLoop.flatSubmit {
NIOThreadPool.singleton.runIfActive(eventLoop: eventLoop) {
baseDatos.querySync("SELECT...")
}
}
Vapor: el framework web
Vapor abstrae NIO con una API de rutas, controladores y middleware similar a Express o Gin:
import Vapor
// Punto de entrada
@main
struct App {
static func main() async throws {
var env = try Environment.detect()
try LoggingSystem.bootstrap(from: &env)
let app = Application(env)
defer { app.shutdown() }
try configure(app)
try await app.runFromAsyncMainEntrypoint()
}
}
// Configuración
func configure(_ app: Application) throws {
app.databases.use(.postgres(
hostname: Environment.get("DB_HOST") ?? "localhost",
port: 5432,
username: Environment.get("DB_USER") ?? "postgres",
password: Environment.get("DB_PASS") ?? "",
database: Environment.get("DB_NAME") ?? "miapp"
), as: .psql)
app.migrations.add(CrearProductos())
try routes(app)
}
Fluent ORM con PostgreSQL
Fluent es el ORM de Vapor, con soporte para PostgreSQL, MySQL y SQLite:
import Fluent
import Vapor
// Modelo
final class Producto: Model, Content, @unchecked Sendable {
static let schema = "productos"
@ID(key: .id)
var id: UUID?
@Field(key: "nombre")
var nombre: String
@Field(key: "precio")
var precio: Double
@Timestamp(key: "creado_en", on: .create)
var creadoEn: Date?
init() {}
init(id: UUID? = nil, nombre: String, precio: Double) {
self.id = id
self.nombre = nombre
self.precio = precio
}
}
// Migración
struct CrearProductos: AsyncMigration {
func prepare(on database: Database) async throws {
try await database.schema("productos")
.id()
.field("nombre", .string, .required)
.field("precio", .double, .required)
.field("creado_en", .datetime)
.create()
}
func revert(on database: Database) async throws {
try await database.schema("productos").delete()
}
}
// Controlador REST
struct ProductoController: RouteCollection {
func boot(routes: RoutesBuilder) throws {
let productos = routes.grouped("productos")
productos.get(use: listar)
productos.post(use: crear)
productos.group(":id") { producto in
producto.get(use: obtener)
producto.put(use: actualizar)
producto.delete(use: eliminar)
}
}
func listar(req: Request) async throws -> [Producto] {
try await Producto.query(on: req.db).all()
}
func crear(req: Request) async throws -> Producto {
let producto = try req.content.decode(Producto.self)
try await producto.save(on: req.db)
return producto
}
func obtener(req: Request) async throws -> Producto {
guard let producto = try await Producto.find(req.parameters.get("id"), on: req.db) else {
throw Abort(.notFound)
}
return producto
}
}
Dockerfile multi-stage para Swift
La imagen oficial de Swift es grande (~1.5 GB). Con un build multi-stage se compila en la imagen completa y se copia solo el binario a una imagen base de Ubuntu minimal:
// Dockerfile
// ---- Build stage ----
FROM swift:6.0-jammy AS build
WORKDIR /build
COPY Package.swift Package.resolved ./
RUN swift package resolve
COPY Sources ./Sources
RUN swift build -c release --static-swift-stdlib
// ---- Runtime stage ----
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y libssl3 libcurl4 && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=build /build/.build/release/Run ./
EXPOSE 8080
ENTRYPOINT ["./Run"]
docker-compose para desarrollo
// docker-compose.yml
// services:
// app:
// build: .
// ports: ["8080:8080"]
// environment:
// - DB_HOST=postgres
// - DB_USER=vapor
// - DB_PASS=secreto
// - DB_NAME=miapp
// depends_on: [postgres]
// postgres:
// image: postgres:16
// environment:
// - POSTGRES_USER=vapor
// - POSTGRES_PASSWORD=secreto
// - POSTGRES_DB=miapp
// volumes:
// - pgdata:/var/lib/postgresql/data
// volumes:
// pgdata:
Resumen
Swift en el servidor es una pila coherente: NIO gestiona el I/O asíncrono sin bloqueos; Vapor añade la capa web con rutas, middleware y gestión de errores; Fluent conecta con PostgreSQL con un ORM fluent en Swift; y Docker multi-stage reduce la imagen de producción a unos 80 MB. El rendimiento es comparable al de Go para APIs CRUD típicas, con la ventaja de compartir modelos y lógica de negocio con la app iOS sin duplicar código.
