neeteshraj/swiftfetch
SwiftFetch is a lightweight, production-ready networking helper built on `URLSession` and Swift Concurrency. It gives you a simple facade to configure a base URL, compose requests, decode JSON, and handle multipart uploads with clear, typed errors.
Features
- Base URL configuration with default headers
- Typed HTTP method and request/response wrappers
- Async/await networking on top of
URLSession - JSON decoding helper with consistent error mapping
- Nested JSON decoding via key path or transform
- Multipart form-data builder for file uploads
- Streaming multipart builder for large payloads
- Opt-in retry policy with backoff
- Optional jitter and custom retry decision hook
- Interceptor pipeline with lightweight logging helper
- Default query params plus per-request timeout/cache overrides
- Metrics hook for timing without dependencies
- Test-friendly design via injectable
URLSession - Included
MockFetchClientfor unit tests
Requirements
- Swift 5.9+
- iOS 15+, macOS 12+, tvOS 15+, watchOS 8+
Installation
Add SwiftFetch to your Package.swift dependencies:
dependencies: [
.package(url: "https://github.com/neeteshraj/SwiftFetch.git", from: "1.0.0")
]Then add SwiftFetch to your target:
.target(
name: "YourApp",
dependencies: [
.product(name: "SwiftFetch", package: "SwiftFetch")
]
)Usage Examples
Configure once (instance-based)
import SwiftFetch
let api = FetchService(
baseURL: URL(string: "https://api.example.com")!,
defaultHeaders: ["Authorization": "Bearer <token>"]
)
let users: [User] = try await api.getJSON("/users")GET JSON
struct User: Decodable {
let id: Int
let name: String
}
let users: [User] = try await SwiftFetch.getJSON(
"/users",
query: ["limit": "20"]
)POST JSON
struct CreateUser: Encodable { let name: String }
struct CreatedUser: Decodable { let id: Int; let name: String }
let created: CreatedUser = try await SwiftFetch.postJSON(
"/users",
body: CreateUser(name: "Ada")
)Enable retries (opt-in)
SwiftFetch.configure(
baseURL: URL(string: "https://api.example.com")!,
retryPolicy: .init(
isEnabled: true,
maxRetries: 2,
initialBackoff: 0.2,
backoffMultiplier: 2.0,
jitterRange: 0.8...1.2
)
)Interceptors (logging example)
let logger = LoggingInterceptor()
let api = FetchService(
baseURL: URL(string: "https://api.example.com")!,
interceptors: [logger]
)Multipart upload
var form = MultipartFormData()
form.addField(name: "description", value: "Profile picture")
form.addData(
name: "file",
filename: "avatar.jpg",
mimeType: "image/jpeg",
data: Data(/* file bytes */)
)
let (body, contentType) = form.build()
let uploadRequest = FetchRequest(
url: URL(string: "/upload")!,
method: .post,
headers: ["Content-Type": contentType],
body: body
)
let uploadResponse = try await SwiftFetch.client.perform(uploadRequest)
print("Upload status:", uploadResponse.statusCode)Troubleshooting
- Relative URL without base: Call
SwiftFetch.configurewith a base URL before sending relative paths, orFetchError.invalidURLis thrown. - Non-2xx status:
FetchError.statusCode(_, data:)includes the response body to help diagnose server errors. - Decoding failures:
FetchError.decodingFailedwraps the decoder error—log it to see the exact mismatch. - Custom sessions: Inject a custom
URLSessionviaFetchClient.Configurationto add caching, auth handling, orURLProtocolstubs for testing.
License
MIT License
Package Metadata
Repository: neeteshraj/swiftfetch
Default branch: main
README: README.md