Swift Networking is a modern Swift networking library built entirely around a declarative programming model. From defining requests to configuring clients and handling responses, everything is expressed clearly and fluentlyy.
Inspired by Swift & SwiftUI’s design philosophy, it allows you to define network behavior in a way that is readable, modular, and test-friendly — all while keeping boilerplate to a minimum.
- 🧾 Fully declarative request & response design
- ⚙️ Custom clients, interceptors, headers, and parameters via DSL
- 🔄 Built-in support for request/response modifiers and interceptors
- 🧪 Easy-to-test and modular architecture
- 🧰 Modular, extensible architecture
This guide walks you through the core building blocks:
- Defining a request.
- Adding headers, parameters, and body.
- Building a client.
- Sending a request.
To define a request, use the @Request
macro. This adds conformance to the Request
protocol, which requires a request
property similar to SwiftUI’s body.
Inside that property, you start with an HTTPRequest
, which represents the core of the request.
You can:
- Provide a url to override the client’s base URL
- Add an optional path
- Set the HTTP method using
method(_:)
- Append extra path components using
appending(_:)
Example
@Request struct TestingRequest {
var request: some Request {
HTTPRequest(url: "https://www.example.com", path: "gallery")
.method(.get)
.appending("cats", "images")
}
}
This creates a simple GET request to https://www.google.com/test/cats/images
.
You can also compose and override requests:
@Request struct TestRequest {
@Header var test: String {
return "Computed"
}
var request: some Request {
TestingRequest() // TestingRequest instead of HTTPRequest
.timeout(90)
.method(.post)
}
}
Networking
offers multiple ways to add headers, query parameters, and request bodies. You can use macros for top level values or chaining modifiers for inline customization.
Use Parameter
to create parameters from String
, Int
, Double
, or Bool
values, including arrays of those types.
You can also use ParametersGroup
to group multiple parameters into one modifier.
To add parameters to a request, you use the @Parameter
macro, or dynamically through modifier methods:
@Request struct SearchRequest {
@Parameter var query: String // Using the name of the property
@Parameter("search_query") var query: String // Using a custom name.
let includeFilter: Bool
var request: some Request {
HTTPRequest(path: "search")
.appendingParameters {
Parameter("query", value: "cats")
if includeFilter {
Parameter("filter", value: "popular")
}
}.appendingParameter("sorting", value: "ascending")
.appendingParameter("filters", values: ["new", "free"])
}
}
Use Header
to define headers from String
, Int
, Double
, or Bool
values.
You can also use HeadersGroup
if you want to group multiple headers in a single modifier, or inject a raw dictionary [String: String]
.
You also get convenience types like: AcceptLanguage
, ContentDisposition
& ContentType
.
Similar to parameters, headers can also be declared statically using the @Header
macro or applied dynamically using modifiers:
@Request struct AuthenticatedRequest {
@Header var token: String // Using the name of the property.
@Header("Authorization") var token: String // Using a custom name.
let includeLang: Bool
var request: some Request {
HTTPRequest(path: "me")
.additionalHeaders {
Header("Custom-Header", value: "value")
if includeLang {
AcceptLanguage("en")
}
}.additionalHeader("version", value: "1.0")
}
}
Currently, Networking
supports JSON
and FormData
request bodies.
To apply a body to a request, you can use the body(_:)
modifier, or convenience modifiers for json
:
HTTPRequest(path: "upload")
.body {
// JSON
JSON(["dict": "data"]) // Dictionary
JSON(Data()) // Raw data.
JSON(EncodableUser()) // Encodable types.
// FormData
FormData {
FormDataBody( /// Raw data
"Image",
data: Data(),
fileName: "image.png",
mimeType: .png
)
FormDataFile( // Data from a file.
"File",
fileURL: URL(filePath: "filePath"),
fileName: "file",
mimeType: .fileURL
)
}
}
HTTPRequest(path: "create")
.json(["name": "John", "age": 30]) // Dictionary.
.json(Data()) // Raw data.
.json(EncodableUser()) // Encodable types.
Caution
If multiple .body()
or .json()
modifiers are used, the last one overrides the previous. This applies to all modifiers, modifier order matters.
You can also include modifiers directly in the HTTPRequest
initializer. This gives you a clean, SwiftUI style declaration for most common request scenarios.
HTTPRequest(path: "submit") {
Header("X-Token", value: "123")
Parameter("query", value: "swift")
JSON(["key": "value"])
}
To define a networking client, use the @Client
macro. This macro adds conformance to the NetworkClient
protocol, which requires a session
property. This property returns a Session
instance that describes how your requests should be configured and executed.
Important
Do not call the session property directly. It’s a computed property that creates a new session each time it’s accessed. Always send requests using the client instance itself (e.g. try await client.dataTask(...)). You should also hold on to the client instance created either using a singleton or by dependency injection to avoid creating multiple instances.
Inside the session property, you can create and customize a Session
using a closure that returns a configured URLSessionConfiguration
.
From there, you can apply additional behaviors such as Base URL, Logging, Retry policy, Authorization, Response validation, Encoding/decoding strategies.
@Client struct MyClient {
var session: Session {
Session {
URLSessionConfiguration.default
.urlCache(.shared)
.requestCachePolicy(.returnCacheDataElseLoad)
.timeoutIntervalForRequest(90)
.timeoutIntervalForResource(90)
.httpMaximumConnectionsPerHost(2)
.waitForConnectivity(true)
.headers {
Header("Key", value: "Value")
}
}
.authorization(BearerAuthProvider())
.baseURL("https://example.com")
.decode(with: JSONDecoder())
.encode(with: JSONEncoder())
.retry(limit: 2, delay: 2)
.enableLogs(true)
.validate(for: [.badGateway, .created])
}
}
You can define custom configuration keys using the @Config
macro & extending ConfigurationValues
:
extension ConfigurationValues {
@Config var customConfig = CustomValue()
}
Then, you can set it on a task or on the session using the modifier configuration(_:_:)
:
@Client struct MyClient {
var session: Session {
Session()
.configuration(\.customConfig, CustomValue())
}
}
let data = try await client.dataTask(TestingRequest())
.configuration(\.customConfig, CustomValue())
.response()
This allows you to define project-specific config values and inject them anywhere in your request or session pipeline.
To send a request, you start by creating a task from a client. The framework provides two main types of tasks:
dataTask
— for requests that return data.downloadTask
— for file downloads.
Each task can be configured individually using the same modifiers available on Session
(e.g. retry, decoding, etc.), giving you full control per request.
let task = MyClient()
.dataTask(MyRequest())
.retry(limit: 2)
.validate(for: [.ok, .notModified])
To start a task you either call resume()
manually, or access the response directly (recommended) using response()
or decode it to a specific type using decode(as:)
let task = MyClient().dataTask(MyRequest())
task.resume()
let result = try await task.response()
let user = try await task.decode(as: User.self)
Note
A task will only send the request once.
If response()
or decode(as:)
is called multiple times, the framework will await the result of the first call instead of resending the request.
Add via Swift Package Manager:
.package(url: "https://github.com/SwiftyJoeyy/swift-networking.git", from: "1.0.0")
Then add "Networking"
to your target dependencies.
These enhancements are planned for future releases of Networking
to further improve flexibility, control, and developer experience:
-
🪄 Simplified Request API Quick request execution using a default client instance for lightweight use cases.
-
🔄 Resumable Downloads Support for partial downloads and automatic resume handling across app launches or interruptions.
-
📤 Upload Task Support Upload data or files with progress tracking, cancellation, and retry support.
-
🏷️ Request Tagging Tag and categorize requests into logical groups for analytics, cancellation, debugging, and tracing.
-
🧪 Built-in Testing Support Tools for mocking, stubbing, and asserting requests and responses with zero boilerplate.
-
🔄 Request Execution & Prioritization Control request flow with custom executors, in-flight limits, and per-task priority that can escalate or drop dynamically.
-
📽 Request Recording & Playback Capture and replay real request traffic for debugging, offline development, and test validation.
The documentation is provided by swiftpackageindex.
Licensed under the Apache 2.0 License. See the LICENSE file.