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.