MIOServerKit is a lightweight, flexible Swift server framework that provides HTTP routing capabilities with multiple backend implementations. It offers a consistent API while supporting both IBM's Kitura and Apple's SwiftNIO as underlying engines.
Kitura server is deprecated. It will be delete in future releases.
- Multi-backend support (Kitura and SwiftNIO)
- Robust routing system with path parameters and regex support
- Middleware capabilities for request/response processing
- Configurable server settings
- Clean, protocol-based API design
Add MIOServerKit to your Package.swift file:
dependencies: [
.package(url: "https://github.com/yourusername/MIOServerKit.git", from: "1.0.0")
],
targets: [
.target(
name: "YourTarget",
dependencies: [
.product(name: "MIOServerKit", package: "MIOServerKit"),
// Or use a specific backend:
// .product(name: "MIOServerKit-Kitura", package: "MIOServerKit"),
// .product(name: "MIOServerKit-NIO", package: "MIOServerKit")
]
)
]import MIOServerKit
// Create a server instance
let server = Server()
// Add routes
server.router.get("/hello") { request, response, next in
response.send("Hello, World!")
next()
}
// Start the server
try server.start(port: 8080)server.router.get("/users/:id") { request, response, next in
if let userId = request.parameters["id"] {
response.send("User ID: \(userId)")
}
next()
}import MIOServerKit
// Create a server instance
let server = Server()
// Add routes
server.router.get("/hello") { context in
return "Hello, World!"
}
// Start the server
try server.start(port: 8080)server.router.get("/users/:id") { context in
if let userId = context.urlParam("id") {
return "User ID: \(userId)"
}
return "User not found"
}The RouterContext provides a convenient way to access request data and send responses. It also support sync and async/awaits for handling requests.
// Sync version
server.router.endpoint("/api/data").post { context in throws -> Any? in
// Access body parameters
let name: String = try context.bodyParam("name")
let age: Int = try context.bodyParam("age")
// Return JSON response
return ["status": "success", "data": ["name": name, "age": age]]
}
// Async version
server.router.endpoint("/api/data").post { context in async throws -> Any? in
// Access body parameters
let name: String = try context.bodyParam("name")
// Perform async operations
let userData = try await processUserData(name)
// Return JSON response
return userData
}
// With path parameters
server.router.endpoint("/users/:id").get { context in async throws -> Any? in
let userId: String = try context.urlParam("id")
let user = try await fetchUserById(userId)
return user
}Instead of registering routes by hand, any function can publish itself as an endpoint with the @Endpoint annotation:
import MIOServerKit
import MIOServerKitMacros
@Endpoint( [.get, .post], path: "/api/schema/:schema" )
func schemaHandler( context: RouterContext ) throws -> (any Sendable)? {
let schema: String = try context.urlParam( "schema" )
return [ "schema": schema ]
}
// Methods default to [.get]. Async handlers work too.
@Endpoint( path: "/api/version" )
func versionHandler( context: RouterContext ) async throws -> (any Sendable)? {
return "1.0"
}
// Static functions inside a type are supported as well.
class EntityAPI {
@Endpoint( [.delete], path: "/api/entity/:id" )
static func deleteEntity( context: RouterContext ) throws -> (any Sendable)? { return nil }
}The macro itself is a compile-time validator only — Swift compiler plugins run inside a sandbox and cannot write files. The actual route registration file is produced by the generate-endpoints tool, which runs as a pre-build step. It parses all Swift sources with swift-syntax, collects every @Endpoint annotation and writes Endpoints+Generated.swift:
# From your server project (or hook it as an Xcode/CI pre-build phase):
path/to/MIOServerKit/Scripts/generate_endpoints.sh \
--sources Sources/MyServer \
--output Sources/MyServer/Endpoints+Generated.swift
# Optionally also emit a JSON description of the routes:
# --json endpoints.json
# See all options:
# ... generate_endpoints.sh --helpThe generated file extends Router, so the only manual step is:
let server = Server()
server.router.registerGeneratedEndpoints()The tool only rewrites the file when routes actually changed (so it never dirties incremental builds), fails the build on duplicate method+path registrations, and the macro reports malformed annotations (missing path, unsupported method, wrong handler signature) directly in the compiler diagnostics.
By default, MIOServerKit will use the most appropriate backend available. To explicitly choose a backend:
// Using Kitura
import MIOServerKit_Kitura
let server = MSKServer()
// Using NIO
import MIOServerKit_NIO
let server = Server() // NIO implementationBuild the package:
swift build
Run tests:
swift test
Run a specific test:
swift test --filter MIOServerKitTests/testSpecificName