ersanq/networkkit
Clean async/await HTTP client for Swift. URLSession without the boilerplate.
The Problem
// π Native URLSession β 20 lines for a simple GET
guard let url = URL(string: "https://api.example.com/users") else { return }
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
let (data, response) = try await URLSession.shared.data(for: request)
guard let http = response as? HTTPURLResponse, http.statusCode == 200 else { return }
let users = try JSONDecoder().decode([User].self, from: data)The Solution
// π NetworkKit β 1 line
let users: [User] = try await Network.get("https://api.example.com/users")Features
- β
async/awaitnative β no callbacks, no Combine - β One-liners for GET, POST, PUT, PATCH, DELETE
- β Automatic JSON encoding/decoding
- β
Smart error mapping β
.unauthorized,.notFound,.noConnection - β
Fluent request builder β
.header().body().query().timeout() - β
Configurable
NetworkClientwith base URL + default headers - β
Full
NetworkResponseaccess when needed - β
Zero dependencies β wraps native
URLSession - β iOS 16+, macOS 13+, tvOS, watchOS, visionOS
Installation
https://github.com/ErsanQ/NetworkKit.package(url: "https://github.com/ErsanQ/NetworkKit", from: "1.0.0")Usage
Quick Requests
import NetworkKit
// GET
let users: [User] = try await Network.get("https://api.example.com/users")
// GET with query params
let results: SearchResult = try await Network.get(
"https://api.example.com/search",
query: ["q": "swift", "page": "1"]
)
// POST
let created: Post = try await Network.post("https://api.example.com/posts", body: newPost)
// PUT
let updated: User = try await Network.put("https://api.example.com/users/1", body: updatedUser)
// PATCH
let patched: User = try await Network.patch("https://api.example.com/users/1", body: changes)
// DELETE
try await Network.delete("https://api.example.com/posts/42")Dedicated Client (Recommended for APIs)
// Configure once
let api = NetworkClient(baseURL: "https://api.example.com")
api.defaultHeaders["Authorization"] = "Bearer \(token)"
api.defaultHeaders["X-App-Version"] = "2.0"
// Use everywhere
let me: User = try await api.get("/me")
let posts: [Post] = try await api.get("/posts", query: ["page": "1"])
let new: Post = try await api.post("/posts", body: newPost)
try await api.delete("/posts/42")Error Handling
do {
let user: User = try await api.get("/me")
} catch NetworkError.unauthorized {
refreshToken()
} catch NetworkError.noConnection {
showOfflineBanner()
} catch NetworkError.notFound {
show404()
} catch NetworkError.serverError(let code) {
logError(code)
} catch NetworkError.decodingFailed(let error) {
print("Decode error:", error)
}Custom Requests
let response = try await Network.response(for:
NetworkRequest(url: "https://api.example.com/upload")
.method(.post)
.header("Authorization", value: "Bearer \(token)")
.body(imageData)
.timeout(120)
)
print(response.statusCode)
print(response.headers["ETag"] ?? "")
let result = try response.decode(UploadResult.self)API Reference
Network (static)
| Method | Description | |--------|-------------| | get(:query:) | GET + decode | | post(:body:) | POST + decode or discard | | put(:body:) | PUT + decode | | patch(:body:) | PATCH + decode | | delete(_:) | DELETE | | response(for:) | Raw NetworkResponse |
NetworkClient
| Property | Description | |----------|-------------| | baseURL | Prepended to all relative paths | | defaultHeaders | Sent with every request | | defaultTimeout | Default: 30 seconds |
NetworkRequest (fluent builder)
.method() Β· .header() Β· .headers() Β· .body() Β· .query() Β· .timeout()
NetworkError
.noConnection Β· .unauthorized Β· .forbidden Β· .notFound Β· .serverError(statusCode:) Β· .decodingFailed(:) Β· .timeout Β· .invalidURL(:)
Requirements
- iOS 16.0+ / macOS 13.0+ / tvOS 16.0+ / watchOS 9.0+ / visionOS 1.0+
- Swift 5.9+
- Xcode 15.0+
License
MIT License. See LICENSE.
Package Metadata
Repository: ersanq/networkkit
Default branch: main
README: README.md