Declarative async/await REST API framework using Swift Concurrency and Codable. With standard implementation using URLSession and JSON encoder/decoder. Built for Swift 6.1+ with full concurrency safety.
- Swift 6.1+
- iOS 15+, macOS 12+, tvOS 15+, watchOS 8+
Add the following line to your Swift Package Manager dependencies:
.package(url:"https://github.com/futuredapp/FTAPIKit.git", from:"2.0.0")The main feature of this library is to provide documentation-like API for defining web services. This is achieved using declarative and protocol-oriented programming in Swift.
The framework provides two core protocols reflecting the physical infrastructure:
URLServerprotocol defining single web service with built-in URLSession support.Endpointprotocol defining access points for resources.
Combining instances of type conforming to URLServer and Endpoint we can build request.
URLServer has convenience methods for calling endpoints using URLSession.
This package contains predefined Endpoint protocols.
Use cases like multipart upload, automatic encoding/decoding
are separated in various protocols for convenience.
Endpointprotocol has empty body. Typically used inGETendpoints.DataEndpointsends provided data in body.UploadEndpointuploads file from a URL usingURLSessionupload task.MultipartEndpointcombines body parts intoInputStreamand sends them to server. Body parts are represented byMultipartBodyPartstruct and provided to the endpoint in an array.URLEncodedEndpointsends body in URL query format.RequestEndpointhas encodable request which is encoded using encoding of theURLServerinstance.
Firstly we need to define our server. Structs are preferred but not required:
structHTTPBinServer:URLServer{letbaseUri=URL(string:"http://httpbin.org/")!
leturlSession=URLSession(configuration:.default)}If we want to use custom formatting we just need to add our encoding/decoding configuration:
structHTTPBinServer:URLServer{...letdecoding:Decoding=JSONDecoding{ decoder in
decoder.keyDecodingStrategy =.convertFromSnakeCase
}letencoding:Encoding=JSONEncoding{ encoder in
encoder.keyEncodingStrategy =.convertToSnakeCase
}}If we need to create specific request, add some headers, usually to provide authorization we can override default request building mechanism.
structHTTPBinServer:URLServer{...func buildRequest(endpoint:Endpoint)asyncthrows->URLRequest{varrequest=trybuildStandardRequest(endpoint: endpoint)
request.addValue("MyApp/1.0.0", forHTTPHeaderField:"User-Agent")return request
}}Most basic GET endpoint can be implemented using Endpoint protocol,
all default properties are inferred.
structGetEndpoint:Endpoint{letpath="get"}Let's take more complicated example like updating some model. We need to supply encodable request and decodable response.
structUpdateUserEndpoint:RequestResponseEndpoint{typealiasResponse=Userletrequest:Userletpath="user"}When we have server and endpoint defined we can call the web service using async/await:
letserver=HTTPBinServer()letendpoint=UpdateUserEndpoint(request: user)letupdatedUser=tryawait server.call(response: endpoint)One of the key features in FTAPIKit 2.0 is the ability to use async operations in buildRequest. This enables use cases like:
- Token Refresh: Await token refresh before building the request
- Dynamic Configuration: Fetch configuration or headers asynchronously
- Rate Limiting: Implement delays or throttling
Example with async token refresh:
structMyServer:URLServer{letbaseUri=URL(string:"https://api.example.com")!
lettokenManager:TokenManagerfunc buildRequest(endpoint:Endpoint)asyncthrows->URLRequest{
// Refresh token if needed
await tokenManager.refreshIfNeeded()varrequest=trybuildStandardRequest(endpoint: endpoint)
request.addValue("Bearer \(tokenManager.token)", forHTTPHeaderField:"Authorization")return request
}}For scenarios where you need to configure requests at the call site (rather than in the server),
use the RequestConfiguring protocol. This is useful for:
- Adding authorization headers in an API service layer
- Per-request configuration that varies by context
- Keeping server implementations simple and reusable
structAuthorizedConfiguration:RequestConfiguring{letauthService:AuthServicefunc configure(_ request:inoutURLRequest)asyncthrows{lettoken=tryawait authService.getValidAccessToken()
request.setValue("Bearer \(token)", forHTTPHeaderField:"Authorization")}}
// Usage - configuration is optional with nil default
letserver=HTTPBinServer()letauthConfig=AuthorizedConfiguration(authService: authService)
// Public endpoint - no configuration needed
letpublicData=tryawait server.call(response: publicEndpoint)
// Protected endpoint - with configuration
letprotectedData=tryawait server.call(response: protectedEndpoint, configuring: authConfig)Monitor request lifecycle with the NetworkObserver protocol:
finalclassLoggingObserver:NetworkObserver{func willSendRequest(_ request:URLRequest)->String{letid=UUID().uuidString
print("[\(id)] Sending: \(request.url!)")return id
}func didReceiveResponse(for request:URLRequest, response:URLResponse?, data:Data?, context:String){print("[\(context)] Received response")}func didFail(request:URLRequest, error:Error, context:String){print("[\(context)] Failed: \(error)")}}structMyServer:URLServer{letbaseUri=URL(string:"https://api.example.com")!
letnetworkObservers:[anyNetworkObserver]=[LoggingObserver()]}The framework uses the APIError protocol for error handling. The default implementation APIError.Standard covers common cases:
do{letresponse=tryawait server.call(response: endpoint)}catchlet error as APIError.Standard{switch error {case.connection(let urlError):
// Network connectivity issue
case .client(let statusCode, _, _):
// 4xx client error
case.server(let statusCode, _, _):
// 5xx server error
case .decoding(let decodingError):
// Response parsing failed
default:break}}For custom error parsing, define a type conforming to APIError and set it as the ErrorType on your server:
structMyServer:URLServer{typealiasErrorType=MyCustomErrorletbaseUri=URL(string:"https://api.example.com")!
}Current maintainer and main contributor is Matěj Kašpar Jirásek, matej.jirasek@futured.app.
We want to thank other contributors, namely:
FTAPIKit is available under the MIT license. See the LICENSE file for more information.
