search ESC

Searching…

No results for "".

Type at least 2 characters to search.

Docs

HTTP Client

Introduction

Magic provides a powerful HTTP client through the Http facade. Built on top of Dio, it offers a clean, expressive API for making HTTP requests and handling responses—just like you'd expect from Laravel.

Configuration

Network Config

Create lib/config/network.dart:

Map get networkConfig => {
  'network': {
    'default': 'api',
    'drivers': {
      'api': {
        'base_url': env('API_BASE_URL', 'https://api.example.com/v1'),
        'timeout': 10000,
        'headers': {
          'Accept': 'application/json',
          'Content-Type': 'application/json',
        },
      },
    },
  },
};

Register in Config

await Magic.init(
  configFactories: [
    () => appConfig,
    () => networkConfig,
  ],
);

Don't forget to add NetworkServiceProvider to your app providers:

'providers': [
  (app) => NetworkServiceProvider(app),
  // ...
],

Making Requests

GET Requests

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

// With query parameters
final response = await Http.get('/users', query: {
  'page': 1,
  'per_page': 25,
  'sort': 'name',
});

// Access the data
if (response.successful) {
  final users = response.body; // Parsed JSON
}

POST Requests

final response = await Http.post('/users', data: {
  'name': 'John Doe',
  'email': '[email protected]',
  'password': 'secret123',
});

if (response.successful) {
  final user = response.body;
  Magic.success('Success', 'User created!');
}

PUT, PATCH & DELETE

// PUT - Full update
await Http.put('/users/1', data: {
  'name': 'Jane Doe',
  'email': '[email protected]',
});

// PATCH - Partial update
await Http.patch('/users/1', data: {
  'name': 'Jane Smith',
});

// DELETE
await Http.delete('/users/1');

RESTful Resources

For RESTful APIs, Magic provides resource helper methods:

// GET /users
final all = await Http.index('users');

// GET /users/1
final one = await Http.show('users', '1');

// POST /users
final created = await Http.store('users', {
  'name': 'New User',
  'email': '[email protected]',
});

// PUT /users/1
final updated = await Http.update('users', '1', {
  'name': 'Updated Name',
});

// DELETE /users/1
await Http.destroy('users', '1');

Handling Responses

The MagicResponse object provides helpful properties and methods for handling API responses.

Response Properties

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

// Status checks
response.successful        // true if 2xx status
response.failed           // true if 4xx or 5xx
response.unauthorized     // true if 401
response.forbidden        // true if 403
response.notFound         // true if 404
response.isValidationError // true if 422

// Access data
response.statusCode       // HTTP status code
response.body            // Parsed response body
response['key']          // Direct access to body key
response.dataAs()  // Typed access

Validation Errors

Magic handles Laravel-style 422 validation errors elegantly:

final response = await Http.post('/register', data: formData);

if (response.isValidationError) {
  // Get all errors as a Map
  final errors = response.errors;
  // {'email': ['Email already taken'], 'password': ['Too short']}
  
  // Get flat list of all error messages
  final allMessages = response.errorsList;
  // ['Email already taken', 'Too short']
  
  // Get just the first error (useful for snackbars)
  final firstError = response.firstError;
  // 'Email already taken'
  
  // Get the main error message
  final message = response.errorMessage;
  // 'The given data was invalid.'
}

Controller Integration

Use ValidatesRequests mixin in your controller for automatic error handling:

class AuthController extends MagicController with ValidatesRequests {
  Future register(Map data) async {
    clearErrors();
    
    final response = await Http.post('/register', data: data);
    
    if (response.successful) {
      // Handle success
    } else {
      // Automatically populates controller errors from 422 response
      handleApiError(response, fallback: 'Registration failed');
    }
  }
}

File Uploads

// Pick and upload an image
final image = await Pick.image();

if (image != null) {
  final response = await image.upload('/upload', fieldName: 'avatar');
  
  if (response.successful) {
    final url = response['url'];
  }
}

// With additional form data
final response = await image.upload(
  '/upload',
  fieldName: 'photo',
  data: {'title': 'Profile Photo', 'public': true},
);

Using Http.upload()

final file = await Pick.file(extensions: ['pdf', 'doc']);

final response = await Http.upload(
  '/documents',
  data: {'title': 'My Document'},
  files: {'document': file},
);

Interceptors

Create interceptors to modify requests or handle responses globally:

class AuthInterceptor extends Interceptor {
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
    // Add auth token to every request
    final token = await Auth.getToken();
    if (token != null) {
      options.headers['Authorization'] = 'Bearer $token';
    }
    handler.next(options);
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    // Log or transform successful responses
    handler.next(response);
  }

  @override
  void onError(DioException err, ErrorInterceptorHandler handler) {
    // Handle errors globally
    if (err.response?.statusCode == 401)

Register interceptors in your NetworkServiceProvider:

class NetworkServiceProvider extends ServiceProvider {
  @override
  void boot() {
    Http.addInterceptor(AuthInterceptor());
    Http.addInterceptor(LoggingInterceptor());
  }
}

Driver Plugin Hook

For SDK integrations that need direct Dio access (e.g., sentry_dio for performance tracing, certificate pinning), use configureDriver():

final driver = Magic.make('network');
driver.configureDriver((dio) {
  dio.addSentry(); // Full Sentry performance tracing
});

[!NOTE] configureDriver() is specific to DioNetworkDriver. Resolve using Magic.make('network') to access it.