Skip to content
AB

A Production Swift Networking Layer with URLSession

Centralize authentication, envelopes, correlation IDs, decoding, safe diagnostics, and transport error mapping.

Ashish BhakhandPublished

Calling URLSession.shared.data(for:) from every feature repeats decisions about base URLs, authentication, encoding, errors, timeouts, and logging.

A shared client owns transport policy:

Code
func request<Response: Decodable, Body: Encodable>(
    path: String,
    method: HTTPMethod,
    body: Body?
) async throws(AppError) -> Response

The client obtains the current access token, creates a correlation ID, encodes snake-case or camel-case consistently, validates the HTTP response, unwraps the API envelope, and maps failures into AppError.

Feature API services remain small:

Code
func loadReports() async throws(AppError) -> [ReportDTO] {
    try await backend.request(path: "/api/v1/reports", method: .get)
}

Log method, route template, duration, status, response size, attempt, and correlation ID. Avoid raw authorization headers, request bodies, health values, and chat content.

Cancellation must propagate. Do not catch CancellationError and turn it into a generic offline failure. A cancelled search or abandoned screen is expected control flow.

Keep transport DTOs at the boundary and map them into domain models. That protects features when the API adds metadata or changes envelope details.

A networking layer should make the safe behavior automatic while still exposing policy choices such as timeout and retry. If every call requires ten flags, the abstraction has moved complexity rather than reduced it.

← Back to all writing