HTTP Client
- Introduction
- Configuration
- Making Requests
- RESTful Resources
- Handling Responses
- File Uploads
- Interceptors
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
Using MagicFile (Recommended)
// 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());
}
}