{"id":13872000,"url":"https://github.com/freshOS/Networking","last_synced_at":"2025-07-16T01:32:32.733Z","repository":{"id":38678512,"uuid":"236987336","full_name":"freshOS/Networking","owner":"freshOS","description":"⚡️ Concise networking code leveraging async-await, Decodable \u0026 Generics.","archived":false,"fork":false,"pushed_at":"2024-10-07T17:12:21.000Z","size":226,"stargazers_count":883,"open_issues_count":4,"forks_count":59,"subscribers_count":15,"default_branch":"master","last_synced_at":"2024-11-21T17:11:08.754Z","etag":null,"topics":["api","combine","freshos","http","httpclient","ios","json","networking","rest","rest-api","swift"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/freshOS.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"open_collective":"freshos","github":"s4cha","ko_fi":"sacha"}},"created_at":"2020-01-29T13:23:24.000Z","updated_at":"2024-11-18T09:57:22.000Z","dependencies_parsed_at":"2023-11-13T05:30:53.463Z","dependency_job_id":"59726db3-6767-4774-ace3-ab64e1515149","html_url":"https://github.com/freshOS/Networking","commit_stats":{"total_commits":109,"total_committers":13,"mean_commits":8.384615384615385,"dds":"0.21100917431192656","last_synced_commit":"a4bef0613f568ffc3a25366ed4af6cf4348501b6"},"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/freshOS%2FNetworking","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/freshOS%2FNetworking/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/freshOS%2FNetworking/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/freshOS%2FNetworking/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/freshOS","download_url":"https://codeload.github.com/freshOS/Networking/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":226046553,"owners_count":17565187,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","combine","freshos","http","httpclient","ios","json","networking","rest","rest-api","swift"],"created_at":"2024-08-05T23:00:32.078Z","updated_at":"2024-11-23T19:31:29.981Z","avatar_url":"https://github.com/freshOS.png","language":"Swift","readme":"![Networking](https://raw.githubusercontent.com/freshOS/Networking/master/banner.png)\n\n# Networking\n[![Language: Swift 5](https://img.shields.io/badge/language-swift5-f48041.svg?style=flat)](https://developer.apple.com/swift)\n![Platform: iOS 13+](https://img.shields.io/badge/platform-iOS%2013%2B-blue.svg?style=flat)\n[![SPM compatible](https://img.shields.io/badge/SPM-compatible-4BC51D.svg?style=flat)](https://swift.org/package-manager/)\n[![License: MIT](http://img.shields.io/badge/license-MIT-lightgrey.svg?style=flat)](https://github.com/freshOS/ws/blob/master/LICENSE)\n[![Build Status](https://app.bitrise.io/app/a6d157138f9ee86d/status.svg?token=W7-x9K5U976xiFrI8XqcJw\u0026branch=master)](https://app.bitrise.io/app/a6d157138f9ee86d)\n[![codebeat badge](https://codebeat.co/badges/ae5feb24-529d-49fe-9e28-75dfa9e3c35d)](https://codebeat.co/projects/github-com-freshos-networking-master)\n![Release version](https://img.shields.io/github/release/freshOS/Networking.svg)\n\nNetworking brings together `URLSession`, `async`-`await` (or `Combine` ), `Decodable` and `Generics` to simplify connecting to a JSON api.\n\n## Demo time 🍿\n\nNetworking turns this:\n```swift\nlet config = URLSessionConfiguration.default\nlet urlSession = URLSession(configuration: config, delegate: nil, delegateQueue: nil)\nvar urlRequest = URLRequest(url: URL(string: \"https://jsonplaceholder.typicode.com/users\")!)\nurlRequest.httpMethod = \"POST\"\nurlRequest.addValue(\"application/x-www-form-urlencoded\", forHTTPHeaderField: \"Content-Type\")\nurlRequest.httpBody = \"firstname=Alan\u0026lastname=Turing\".data(using: .utf8)\nlet (data, _) = try await urlSession.data(for: urlRequest)\nlet decoder = JSONDecoder()\nlet user = try decoder.decode(User.self, from: data)\n```\n\ninto: \n```swift\nlet network = NetworkingClient(baseURL: \"https://jsonplaceholder.typicode.com\")\nlet user: User = try await network.post(\"/users\", params: [\"firstname\" : \"Alan\", \"lastname\" : \"Turing\"])\n```\n\n## Video tutorial\n\nAlex from [Rebeloper](https://www.youtube.com/channel/UCK88iDIf2V6w68WvC-k7jcg) made a fantastic video tutorial, check it out [here](https://youtu.be/RM5uKTBr20c)!\n\n## How\nBy providing a lightweight client that **automates boilerplate code everyone has to write**.  \nBy exposing a **delightfully simple** api to get the job done simply, clearly, quickly.  \nGetting swift models from a JSON api is now *a problem of the past*\n\nURLSession + Combine + Generics + Protocols = Networking.\n\n## What\n- [x] Build a concise Api\n- [x] Automatically map your models\n- [x] Uses latest Apple's [Combine](https://developer.apple.com/documentation/combine) / asyn-await \n- [x] Compatible with native `Decodable` and any JSON Parser\n- [x] Embarks a built-in network logger\n- [x] Pure Swift, simple, lightweight \u0026 0 dependencies\n\n\n\n## Getting Started\n\n* [Install it](#install-it)\n* [Create a Client](#create-a-client)\n* [Make your first call](#make-your-first-call)\n* [Get the type you want back](#get-the-type-you-want-back)\n* [Pass params](#pass-params)\n* [Upload multipart data](#upload-multipart-data)\n* [Add Headers](#add-headers)\n* [Add Timeout](#add-timeout)\n* [Cancel a request](#cancel-a-request)\n* [Log Network calls](#log-network-calls)\n* [Handling errors](#handling-errors)\n* [Support JSON-to-Model parsing](#support-json-to-model-parsing)\n* [Design a clean api](#design-a-clean-api)\n\n### Install it\n`Networking` is installed via the official [Swift Package Manager](https://swift.org/package-manager/).  \n\nSelect `Xcode`\u003e`File`\u003e `Swift Packages`\u003e`Add Package Dependency...`  \nand add `https://github.com/freshOS/Networking`.\n\n### Create a Client\n\n```swift\nlet client = NetworkingClient(baseURL: \"https://jsonplaceholder.typicode.com\")\n```\n\n### Make your first call\nUse `get`, `post`, `put`, `patch` \u0026 `delete` methods on the client to make calls.\n\n```swift\nlet data: Data = try await client.get(\"/posts/1\")\n```\nAll the apis are also available in Combine:\n```swift\nclient.get(\"/posts/1\").sink(receiveCompletion: { _ in }) { (data:Data) in\n    // data\n}.store(in: \u0026cancellables)\n```\n\n### Get the type you want back\n`Networking` recognizes the type you want back via type inference.\nTypes supported are `Void`, `Data`, `Any`(JSON), `Decodable`(Your Model) \u0026 `NetworkingJSONDecodable`  \n\nThis enables keeping a simple api while supporting many types :\n```swift\nlet voidPublisher: AnyPublisher\u003cVoid, Error\u003e = client.get(\"\")\nlet dataPublisher: AnyPublisher\u003cData, Error\u003e = client.get(\"\")\nlet jsonPublisher: AnyPublisher\u003cAny, Error\u003e = client.get(\"\")\nlet postPublisher: AnyPublisher\u003cPost, Error\u003e = client.get(\"\")\nlet postsPublisher: AnyPublisher\u003c[Post], Error\u003e = client.get(\"\")\n```\n\n### Pass params\nSimply pass a `[String: CustomStringConvertible]` dictionary to the `params` parameter.\n\n```swift\nlet response: Data = try await client.posts(\"/posts/1\", params: [\"optin\" : true ])\n```\n\nParameters are `.urlEncoded` by default (`Content-Type: application/x-www-form-urlencoded`), to encode them as json\n(`Content-Type: application/json`), you need to set the client's `parameterEncoding` to `.json` as follows:\n\n```swift\nclient.parameterEncoding = .json\n```\n\n### Upload multipart data\nFor multipart calls (post/put), just pass a `MultipartData` struct to the `multipartData` parameter.\n```swift\nlet params: [String: CustomStringConvertible] = [ \"type_resource_id\": 1, \"title\": photo.title]\nlet multipartData = MultipartData(name: \"file\",\n                                  fileData: photo.data,\n                                  fileName: \"photo.jpg\",\n                                   mimeType: \"image/jpeg\")\nclient.post(\"/photos/upload\",\n            params: params,\n            multipartData: multipartData).sink(receiveCompletion: { _ in }) { (data:Data?, progress: Progress) in\n                if let data = data {\n                    print(\"upload is complete : \\(data)\")\n                } else {\n                    print(\"progress: \\(progress)\")\n                }\n}.store(in: \u0026cancellables)\n```\n\n### Add Headers\nHeaders are added via the `headers` property on the client.\n```swift\nclient.headers[\"Authorization\"] = \"[mytoken]\"\n```\n\n### Add Timeout\nTimeout (TimeInterval in seconds) is added via the optional `timeout` property on the client.\n```swift\nlet client = NetworkingClient(baseURL: \"https://jsonplaceholder.typicode.com\", timeout: 15)\n```\n\nAlternatively,\n\n```swift\nclient.timeout = 15 \n```\n\n### Cancel a request\nSince `Networking` uses the Combine framework. You just have to cancel the `AnyCancellable` returned by the `sink` call.\n\n```swift\nvar cancellable = client.get(\"/posts/1\").sink(receiveCompletion: { _ in }) { (json:Any) in\n  print(json)\n}\n```\nLater ...\n```swift\ncancellable.cancel()\n```\n\n### Log Network calls\n3 log levels are supported: `off`, `info`, `debug`\n```swift\nclient.logLevel = .debug\n```\n\n\n### Handling errors\nErrors can be handled on a Publisher, such as:\n\n```swift\nclient.get(\"/posts/1\").sink(receiveCompletion: { completion in\nswitch completion {\ncase .finished:\n    break\ncase .failure(let error):\n    switch error {\n    case let decodingError DecodingError:\n        // handle JSON decoding errors\n    case let networkingError NetworkingError:\n        // handle NetworkingError\n        // print(networkingError.status)\n        // print(networkingError.code)\n    default:\n        // handle other error types\n        print(\"\\(error.localizedDescription)\")\n    }\n}   \n}) { (response: Post) in\n    // handle the response\n}.store(in: \u0026cancellables)\n```\n\n### Support JSON-to-Model parsing.\nFor a model to be parsable by `Networking`, it only needs to conform to the `Decodable` protocol.\n\nIf you are using a custom JSON parser, then you'll have to conform to `NetworkingJSONDecodable`.  \nFor example if you are using [Arrow](https://github.com/freshOS/Arrow) for JSON Parsing.\nSupporting a `Post` model will look like this:\n```swift\nextension Post: NetworkingJSONDecodable {\n    static func decode(_ json: Any) throws -\u003e Post {\n        var t = Post()\n        if let arrowJSON = JSON(json) {\n            t.deserialize(arrowJSON)\n        }\n        return t\n    }\n}\n```\n\nInstead of doing it every models, you can actually do it once for all with a clever extension 🤓.\n\n```swift\nextension ArrowParsable where Self: NetworkingJSONDecodable {\n\n    public static func decode(_ json: Any) throws -\u003e Self {\n        var t: Self = Self()\n        if let arrowJSON = JSON(json) {\n            t.deserialize(arrowJSON)\n        }\n        return t\n    }\n}\n\nextension User: NetworkingJSONDecodable { }\nextension Photo: NetworkingJSONDecodable { }\nextension Video: NetworkingJSONDecodable { }\n// etc.\n```\n\n### Design a clean api\nIn order to write a concise api, Networking provides the `NetworkingService` protocol.\nThis will forward your calls to the underlying client so that your only have to write `get(\"/route\")` instead of `network.get(\"/route\")`, while this is overkill for tiny apis, it definitely keep things concise when working with massive apis.\n\n\nGiven an `Article` model\n```swift\nstruct Article: DeCodable {\n    let id: String\n    let title: String\n    let content: String\n}\n```\n\nHere is what a typical CRUD api would look like :\n\n```swift\nstruct CRUDApi: NetworkingService {\n\n    var network = NetworkingClient(baseURL: \"https://my-api.com\")\n\n    // Create\n    func create(article a: Article) async throws -\u003e Article {\n        try await post(\"/articles\", params: [\"title\" : a.title, \"content\" : a.content])\n    }\n\n    // Read\n    func fetch(article a: Article) async throws -\u003e Article {\n        try await get(\"/articles/\\(a.id)\")\n    }\n\n    // Update\n    func update(article a: Article) async throws -\u003e Article {\n        try await put(\"/articles/\\(a.id)\", params: [\"title\" : a.title, \"content\" : a.content])\n    }\n\n    // Delete\n    func delete(article a: Article) async throws {\n        return try await delete(\"/articles/\\(a.id)\")\n    }\n\n    // List\n    func articles() async throws -\u003e [Article] {\n        try await get(\"/articles\")\n    }\n}\n```\n\nThe Combine equivalent would look like this:\n```swift\nstruct CRUDApi: NetworkingService {\n\n    var network = NetworkingClient(baseURL: \"https://my-api.com\")\n\n    // Create\n    func create(article a: Article) -\u003e AnyPublisher\u003cArticle, Error\u003e {\n        post(\"/articles\", params: [\"title\" : a.title, \"content\" : a.content])\n    }\n\n    // Read\n    func fetch(article a: Article) -\u003e AnyPublisher\u003cArticle, Error\u003e {\n        get(\"/articles/\\(a.id)\")\n    }\n\n    // Update\n    func update(article a: Article) -\u003e AnyPublisher\u003cArticle, Error\u003e {\n        put(\"/articles/\\(a.id)\", params: [\"title\" : a.title, \"content\" : a.content])\n    }\n\n    // Delete\n    func delete(article a: Article) -\u003e AnyPublisher\u003cVoid, Error\u003e {\n        delete(\"/articles/\\(a.id)\")\n    }\n\n    // List\n    func articles() -\u003e AnyPublisher\u003c[Article], Error\u003e {\n        get(\"/articles\")\n    }\n}\n```\n","funding_links":["https://opencollective.com/freshos","https://github.com/sponsors/s4cha","https://ko-fi.com/sacha"],"categories":["Swift"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FfreshOS%2FNetworking","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FfreshOS%2FNetworking","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FfreshOS%2FNetworking/lists"}