home / skills / kaakati / rails-enterprise-dev / http-integration

This skill helps you implement robust HTTP integration patterns, including providers, authenticated clients, and structured error handling for reliable APIs.

npx playbooks add skill kaakati/rails-enterprise-dev --skill http-integration

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
3.4 KB
---
name: "HTTP Integration Patterns"
description: "HTTP client configuration, API providers, error handling, and request/response patterns"
version: "1.0.0"
---

# HTTP Integration Patterns

## HTTP Provider Pattern

```dart
import 'dart:convert';
import 'package:http/http.dart' as http;

class UserProvider {
  final http.Client _client;
  final String _baseUrl;
  
  UserProvider(this._client, {String? baseUrl})
      : _baseUrl = baseUrl ?? 'https://api.example.com';
  
  Map<String, String> get _headers => {
    'Content-Type': 'application/json',
    'Accept': 'application/json',
  };
  
  Future<UserModel> fetchUser(String id) async {
    final response = await _client.get(
      Uri.parse('$_baseUrl/users/$id'),
      headers: _headers,
    ).timeout(Duration(seconds: 10));
    
    if (response.statusCode == 200) {
      return UserModel.fromJson(json.decode(response.body));
    } else if (response.statusCode == 404) {
      throw ServerException(message: 'User not found');
    } else {
      throw ServerException(
        message: 'Failed to fetch user',
        statusCode: response.statusCode,
      );
    }
  }
  
  Future<List<UserModel>> fetchAllUsers() async {
    final response = await _client.get(
      Uri.parse('$_baseUrl/users'),
      headers: _headers,
    ).timeout(Duration(seconds: 10));
    
    if (response.statusCode == 200) {
      final List<dynamic> data = json.decode(response.body);
      return data.map((json) => UserModel.fromJson(json)).toList();
    } else {
      throw ServerException(message: 'Failed to fetch users');
    }
  }
  
  Future<UserModel> createUser(Map<String, dynamic> data) async {
    final response = await _client.post(
      Uri.parse('$_baseUrl/users'),
      headers: _headers,
      body: json.encode(data),
    ).timeout(Duration(seconds: 10));
    
    if (response.statusCode == 201) {
      return UserModel.fromJson(json.decode(response.body));
    } else {
      throw ServerException(message: 'Failed to create user');
    }
  }
  
  Future<void> deleteUser(String id) async {
    final response = await _client.delete(
      Uri.parse('$_baseUrl/users/$id'),
      headers: _headers,
    ).timeout(Duration(seconds: 10));
    
    if (response.statusCode != 204) {
      throw ServerException(message: 'Failed to delete user');
    }
  }
}
```

## Authenticated HTTP Client

```dart
class AuthenticatedClient extends http.BaseClient {
  final http.Client _inner;
  final String Function() _getToken;
  
  AuthenticatedClient(this._inner, this._getToken);
  
  @override
  Future<http.StreamedResponse> send(http.BaseRequest request) async {
    final token = _getToken();
    if (token.isNotEmpty) {
      request.headers['Authorization'] = 'Bearer $token';
    }
    request.headers['Content-Type'] = 'application/json';
    request.headers['Accept'] = 'application/json';
    
    return _inner.send(request);
  }
}

// Usage in bindings
Get.lazyPut<http.Client>(
  () => AuthenticatedClient(
    http.Client(),
    () => Get.find<StorageService>().token ?? '',
  ),
);
```

## Error Handling

```dart
try {
  final response = await _client.get(url);
  return parseResponse(response);
} on SocketException {
  throw NetworkException();
} on TimeoutException {
  throw ServerException(message: 'Request timeout');
} on FormatException {
  throw ServerException(message: 'Invalid response format');
} catch (e) {
  throw ServerException(message: e.toString());
}
```

Overview

This skill documents HTTP Integration Patterns for reliable API consumption: client configuration, provider wrappers, authentication injection, and robust error handling. It presents a portable approach you can apply across Rails-backed services or any API-driven project for predictable request/response behavior. The patterns emphasize testability, timeouts, and clear mapping between HTTP status codes and domain errors.

How this skill works

A provider class wraps an HTTP client and exposes typed methods (fetch, create, delete) that build URIs, set JSON headers, apply timeouts, and parse responses into domain models. An authenticated client decorates requests with Authorization headers and standard content headers before forwarding to an inner client. Centralized error mapping converts networking, timeout, and format errors into domain-specific exceptions so calling code handles outcomes consistently.

When to use it

  • When you need a single place to manage API base URL, headers, and timeouts.
  • When integrating multiple endpoints with consistent response parsing into models.
  • When you must inject authentication tokens into every request without repeating code.
  • When tests require swapping the real HTTP client for a mock or fake.
  • When you need predictable exception types for network, timeout, or API errors.

Best practices

  • Keep provider methods small and focused: one responsibility per HTTP method.
  • Use dependency injection for the HTTP client to enable testing and configuration.
  • Apply conservative timeouts and surface timeout errors as explicit exceptions.
  • Map status codes to meaningful domain errors (e.g., 404 -> NotFound, 401 -> Unauthorized, 5xx -> ServerError).
  • Serialize and deserialize JSON centrally; validate format and throw clear parse errors.

Example use cases

  • A UserProvider that fetches, lists, creates, and deletes users while returning typed models.
  • An AuthenticatedClient that automatically attaches Bearer tokens from a secure store.
  • A background job that retries on transient errors but fails fast on client errors (4xx).
  • Integration tests that swap the real HTTP client for an in-memory mock to assert request URIs and bodies.
  • A gateway service that translates downstream API errors into domain-specific responses for UI layers.

FAQ

How should I handle retries for transient errors?

Retry only on idempotent requests and transient failures (network errors, 5xx). Use exponential backoff and limit retries to avoid cascading load.

Where do I store the API base URL and token?

Keep the base URL in configuration/environment and inject it into providers. Store tokens in a secure storage service and inject a token getter into the authenticated client.