philiprehberger_api_client 0.2.0 copy "philiprehberger_api_client: ^0.2.0" to clipboard
philiprehberger_api_client: ^0.2.0 copied to clipboard

Declarative API client with typed responses, retries, and interceptors

philiprehberger_api_client #

Tests pub package Last updated

Declarative API client with typed responses, retries, and interceptors

Requirements #

  • Dart >= 3.6

Installation #

Add to your pubspec.yaml:

dependencies:
  philiprehberger_api_client: ^0.2.0

Then run:

dart pub get

Usage #

import 'package:philiprehberger_api_client/api_client.dart';

final client = ApiClient(baseUrl: 'https://api.example.com');
final response = await client.get('/users');
print(response.jsonList);
client.close();

GET Requests #

// Simple GET
final response = await client.get('/users');

// GET with query parameters
final filtered = await client.get('/users', query: {'role': 'admin'});

// Access typed response data
final users = response.jsonList;
final user = response.jsonMap;

POST, PUT, PATCH Requests #

// POST with JSON body (automatically serialized)
final created = await client.post('/users', body: {
  'name': 'Alice',
  'email': 'alice@example.com',
});

// PUT request
await client.put('/users/1', body: {'name': 'Updated'});

// PATCH request
await client.patch('/users/1', body: {'email': 'new@example.com'});

// DELETE request
await client.delete('/users/1');

Typed Deserialization #

// GET with typed response
final user = await client.getTyped<User>(
  '/users/1',
  decoder: (json) => User.fromJson(json),
);

// POST with typed response
final created = await client.postTyped<User>(
  '/users',
  body: {'name': 'Alice'},
  decoder: (json) => User.fromJson(json),
);

Caching #

// Cache GET responses in memory
final cache = CacheInterceptor(
  ttl: const Duration(minutes: 5),
  maxEntries: 100,
);
client.addInterceptor(cache);

// Invalidate specific paths
cache.invalidate('/users');

// Clear entire cache
cache.clearAll();

Interceptors #

// Add headers to every request
client.addInterceptor(HeaderInterceptor({
  'Authorization': 'Bearer token',
  'Accept': 'application/json',
}));

// Log requests and responses
client.addInterceptor(LogInterceptor(print));

// Custom interceptor
class AuthRefreshInterceptor extends Interceptor {
  @override
  ApiRequest onRequest(ApiRequest request) {
    return request.withHeaders({'Authorization': 'Bearer $token'});
  }

  @override
  void onError(Object error) {
    if (error is HttpError && error.statusCode == 401) {
      // Handle token refresh
    }
    throw error;
  }
}

Retry Configuration #

final client = ApiClient(
  baseUrl: 'https://api.example.com',
  retryConfig: const RetryConfig(
    maxAttempts: 3,
    initialDelay: Duration(milliseconds: 500),
    backoffMultiplier: 2.0,
    retryableStatuses: {408, 429, 500, 502, 503, 504},
  ),
);

Error Handling #

try {
  final response = await client.get('/users');
} on HttpError catch (e) {
  print('HTTP ${e.statusCode}');
} on TimeoutError catch (e) {
  print('Request timed out: ${e.message}');
} on RetryExhaustedError catch (e) {
  print('Failed after ${e.attempts} attempts');
} on ApiError catch (e) {
  print('API error: ${e.message}');
}

Response Inspection #

final response = await client.get('/users/1');

// Status helpers
response.isSuccess;     // true for 2xx
response.isClientError; // true for 4xx
response.isServerError; // true for 5xx

// Typed JSON access
response.json;     // dynamic
response.jsonMap;  // Map<String, dynamic>
response.jsonList; // List<dynamic>

// Metadata
response.statusCode; // 200
response.headers;    // Map<String, String>
response.duration;   // Duration

API #

ApiClient #

Method / Property Description
ApiClient({required String baseUrl, Duration timeout, RetryConfig? retryConfig, http.Client? httpClient}) Create an API client
get(String path, {Map<String, String>? query, Map<String, String>? headers}) Send a GET request
post(String path, {Object? body, Map<String, String>? headers}) Send a POST request
put(String path, {Object? body, Map<String, String>? headers}) Send a PUT request
patch(String path, {Object? body, Map<String, String>? headers}) Send a PATCH request
delete(String path, {Map<String, String>? headers}) Send a DELETE request
getTyped<T>(String path, {required T Function(Map<String, dynamic>) decoder, Map<String, String>? query, Map<String, String>? headers}) Send a GET request and deserialize the response
postTyped<T>(String path, {required T Function(Map<String, dynamic>) decoder, Object? body, Map<String, String>? headers}) Send a POST request and deserialize the response
addInterceptor(Interceptor interceptor) Add a request/response interceptor
removeInterceptor(Interceptor interceptor) Remove an interceptor
close() Close the underlying HTTP client

ApiRequest #

Method / Property Description
ApiRequest({required String method, required Uri uri, Map<String, String> headers, String? body}) Create an API request
method The HTTP method
uri The fully resolved URI
headers The request headers
body The request body
withHeaders(Map<String, String> extra) Create a copy with merged headers

ApiResponse #

Method / Property Description
statusCode The HTTP status code
headers The response headers
body The response body as a string
duration The time taken to complete the request
isSuccess Whether the status code is 2xx
isClientError Whether the status code is 4xx
isServerError Whether the status code is 5xx
json Decode the body as JSON (dynamic)
jsonMap Decode the body as a JSON map
jsonList Decode the body as a JSON list

Error Types #

Class Description
ApiError Base error for API operations
HttpError Thrown for non-2xx status codes; exposes statusCode
TimeoutError Thrown when a request exceeds the timeout
RetryExhaustedError Thrown when all retry attempts fail; exposes attempts and lastError

Interceptor #

Method Description
onRequest(ApiRequest request) Called before a request is sent; return a modified or same request
onResponse(ApiResponse response) Called after a response is received; return a modified or same response
onError(Object error) Called on error; can throw or handle

Built-in Interceptors #

Class Description
HeaderInterceptor(Map<String, String> headers) Adds headers to every request
LogInterceptor(void Function(String) log) Logs requests and responses to a callback
CacheInterceptor({Duration ttl, int maxEntries}) Caches GET responses in memory with TTL and size limit

RetryConfig #

Method / Property Description
RetryConfig({int maxAttempts, Duration initialDelay, double backoffMultiplier, Set<int> retryableStatuses}) Create retry configuration
maxAttempts Maximum number of retry attempts (default: 3)
initialDelay Initial delay before the first retry (default: 500ms)
backoffMultiplier Multiplier for each subsequent retry (default: 2.0)
retryableStatuses Status codes that trigger a retry (default: 408, 429, 500, 502, 503, 504)
delayForAttempt(int attempt) Calculate delay for a given attempt number
shouldRetry(int statusCode) Whether a status code should trigger a retry

Development #

dart pub get
dart analyze --fatal-infos
dart test

Support #

If you find this project useful:

Star the repo

🐛 Report issues

💡 Suggest features

❤️ Sponsor development

🌐 All Open Source Projects

💻 GitHub Profile

🔗 LinkedIn Profile

License #

MIT

2
likes
160
points
--
downloads

Documentation

API reference

Publisher

verified publisherphiliprehberger.com

Declarative API client with typed responses, retries, and interceptors

Homepage
Repository (GitHub)
View/report issues

Topics

#http #api #networking

License

MIT (license)

Dependencies

http

More

Packages that depend on philiprehberger_api_client