A modern, type-safe Swift networking library designed for building robust and maintainable API clients. Features automatic JSON decoding, comprehensive error handling, and a declarative request specification pattern.
✨ Type-Safe Requests - Protocol-based API specifications ensure compile-time safety
🔄 Automatic JSON Decoding - Built-in snake_case to camelCase conversion
🚨 Comprehensive Error Handling - Detailed error types for better debugging
⚡ Async/Await Support - Modern Swift concurrency for clean, readable code
🎯 Flexible Configuration - Customizable URL sessions and JSON decoders
📱 Multi-Platform - Supports iOS 13+ and macOS 10.15+
Add this package to your project by adding the following to your Package.swift
file:
dependencies: [
.package(url: "https://github.com/Archichil/swift-api-client.git", from: "1.0.0")
]
Or add it through Xcode by going to File → Add Package Dependencies and entering the repository URL.
import APIClient
let baseURL = URL(string: "https://api.example.com")!
let client = APIClient(baseURL: baseURL)
// You can use enum for multiple endpoints
struct GetUserSpec: APIClient.APISpecification {
let userId: Int
var endpoint: String { "/users/\(userId)" }
var method: APIClient.HttpMethod { .get }
var queryParameters: [String: String]? { nil }
var headers: [String: String]? {
["Authorization": "Bearer \(authToken)"]
}
var body: Data? { nil }
}
let getUserSpec = GetUserSpec(userId: 123)
do {
let user: User = try await client.sendRequest(getUserSpec)
print("User: \(user.name)")
} catch NetworkError.requestFailed(let statusCode) {
print("Request failed with status: \(statusCode)")
} catch NetworkError.decodingFailed(let error) {
print("Decoding failed: \(error)")
} catch {
print("Unexpected error: \(error)")
}
The main client class for making network requests:
public struct APIClient {
public init(
baseURL: URL,
urlSession: URLSession = URLSession.shared,
decoder: JSONDecoder = JSONDecoder(),
useSnakeCaseConversion: Bool = true
)
public func sendRequest<T: Decodable>(_ specification: APISpecification) async throws -> T
}
Define your API requests by conforming to this protocol:
protocol APISpecification {
var endpoint: String { get }
var method: HttpMethod { get }
var headers: [String: String]? { get }
var queryParameters: [String: String]? { get }
var body: Data? { get }
}
Supported HTTP methods:
enum HttpMethod: String, CaseIterable {
case get = "GET"
case post = "POST"
case patch = "PATCH"
case put = "PUT"
case delete = "DELETE"
case head = "HEAD"
case options = "OPTIONS"
}
The library provides comprehensive error types:
enum NetworkError: Error, LocalizedError {
case invalidURL
case invalidResponse
case requestFailed(statusCode: Int)
case decodingFailed(DecodingError)
case unknown(Error)
}
struct GetUsersSpec: APIClient.APISpecification {
var endpoint: String { "/users" }
var method: APIClient.HttpMethod { .get }
var headers: [String: String]? {
["Accept": "application/json"]
}
var queryParameters: [String: String]? { nil }
var body: Data? { nil }
}
let users: [User] = try await client.sendRequest(GetUsersSpec())
struct CreateUserSpec: APIClient.APISpecification {
let user: CreateUserRequest
var endpoint: String { "/users" }
var method: APIClient.HttpMethod { .post }
var headers: [String: String]? {
[
"Content-Type": "application/json",
"Authorization": "Bearer \(authToken)"
]
}
var queryParameters: [String: String]? { nil }
var body: Data? {
try? JSONEncoder().encode(user)
}
}
let newUser: User = try await client.sendRequest(CreateUserSpec(user: userRequest))
For non-JSON responses (like images or files):
let imageData: Data = try await client.sendRequest(GetImageSpec(imageId: "123"))
let customSession = URLSession(configuration: .ephemeral)
let client = APIClient(baseURL: baseURL, urlSession: customSession)
let customDecoder = JSONDecoder()
customDecoder.dateDecodingStrategy = .iso8601
let client = APIClient(baseURL: baseURL, decoder: customDecoder)
- iOS 13.0+ / macOS 10.15+
- Xcode 16.0+
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.