GraphQL Infrastructure Tool
A comprehensive Flutter package that provides a robust wrapper around GraphQL operations with built-in error handling, authentication, caching, and logging capabilities.
Features
- π Flexible Authentication: Support for multiple authentication providers (Bearer tokens, API keys, custom headers)
- π¨ Smart Error Handling: Customizable exception providers with pattern matching
- π Built-in Logging: Comprehensive request/response logging with performance metrics
- πΎ Caching Support: Integrated GraphQL caching with custom cache policies
- π― Type Safety: Full type safety with generic model parsing
- π Result Wrapper: Elegant result handling with Success/Failure pattern matching
- π¨ Customizable Configuration: Flexible configuration for different environments
- π± Flutter Ready: Optimized for Flutter applications
Installation
Add this to your package's pubspec.yaml
file:
dependencies:
graphql_infra_tool: ^1.0.0
Then run:
flutter pub get
Quick Start
1. Basic Setup
import 'package:graphql_infra_tool/graphql_infra_tool.dart';
// Create a basic configuration
final config = GQLConfig(
baseURL: 'https://api.example.com/graphql',
queryPolicy: FetchPolicy.cacheFirst,
mutationPolicy: FetchPolicy.networkOnly,
);
// Initialize the GraphQL client
final gqlClient = GQLClient(config);
2. Simple Query Example
// Define your GraphQL query
const String getUserQuery = '''
query GetUser(\$id: String!) {
user(id: \$id) {
id
name
email
}
}
''';
// Execute the query
final result = await gqlClient.query<User>(
query: getUserQuery,
variable: {'id': 'user123'},
modelParser: (json) => User.fromJson(json),
);
3. Using Result Wrapper
// Wrap your GraphQL operations for elegant error handling
final result = await GQLResultWrapper.wrap(() =>
gqlClient.query<User>(
query: getUserQuery,
variable: {'id': 'user123'},
modelParser: (json) => User.fromJson(json),
)
);
// Handle the result with pattern matching
switch (result) {
case Success<User>(:final data):
print('User loaded: ${data.name}');
break;
case Failure<User>(:final exception):
print('Error: ${exception.errorModel.message}');
break;
}
Advanced Configuration
Authentication Providers
Create custom authentication providers by implementing GQLAuthProvider
:
class BearerTokenProvider implements GQLAuthProvider {
final String token;
BearerTokenProvider(this.token);
@override
String get headerKey => 'Authorization';
@override
TokenCallback get getToken => () async => 'Bearer $token';
}
class TenantIdProvider implements GQLAuthProvider {
final String tenantId;
TenantIdProvider(this.tenantId);
@override
String get headerKey => 'x-tenant-id';
@override
TokenCallback get getToken => () async => tenantId;
}
// Use in configuration
final config = GQLConfig(
baseURL: 'https://api.example.com/graphql',
authProviders: [
BearerTokenProvider('your-jwt-token'),
TenantIdProvider('tenant-123'),
],
);
Custom Exception Handling
Implement GQLExceptionProvider
for custom error handling:
class HttpExceptionProvider implements GQLExceptionProvider {
@override
String get errorCode => 'HTTP_EXCEPTION';
@override
GQLException? createException(
String errorCode,
String? errorMessage,
Map<String, dynamic>? extensions,
) {
final status = extensions?['status'];
switch (status) {
case 401:
return AppError(AppErrorModel(
message: 'Authentication required',
code: 'UNAUTHORIZED',
));
case 403:
return AppError(AppErrorModel(
message: 'Access forbidden',
code: 'FORBIDDEN',
));
default:
return AppError(AppErrorModel(
message: errorMessage ?? 'HTTP Error',
code: errorCode,
));
}
}
}
// Add to configuration
final config = GQLConfig(
baseURL: 'https://api.example.com/graphql',
exceptionProviders: [
HttpExceptionProvider(),
NotFoundExceptionProvider(),
],
);
Response Node Path Configuration
Configure response node paths to automatically extract data from nested responses:
final config = GQLConfig(
baseURL: 'https://api.example.com/graphql',
responseNodePaths: ['data', 'result.data', 'response.payload'],
);
This automatically extracts data from responses like:
{
"data": {
"user": {
"id": "123",
"name": "John Doe"
}
}
}
Complete Example
Here's a complete example showing how to set up and use the package:
import 'package:flutter/material.dart';
import 'package:graphql_infra_tool/graphql_infra_tool.dart';
class GraphQLService {
late final GQLClient _client;
GraphQLService() {
_initializeClient();
}
void _initializeClient() {
final config = GQLConfig(
baseURL: 'https://api.example.com/graphql',
authProviders: [
BearerTokenProvider('your-jwt-token'),
],
exceptionProviders: [
HttpExceptionProvider(),
],
queryPolicy: FetchPolicy.cacheFirst,
mutationPolicy: FetchPolicy.networkOnly,
responseNodePaths: ['data'],
enableLogging: true,
);
_client = GQLClient(config);
}
Future<GQLResult<User>> getUser(String userId) async {
return GQLResultWrapper.wrap(() =>
_client.query<User>(
query: '''
query GetUser(\$id: String!) {
user(id: \$id) {
id
name
email
createdAt
}
}
''',
variable: {'id': userId},
modelParser: (json) => User.fromJson(json),
)
);
}
Future<GQLResult<List<User>>> getUsers() async {
return GQLResultWrapper.wrap(() =>
_client.queryList<User>(
query: '''
query GetUsers {
users {
id
name
email
}
}
''',
modelParser: (json) => User.fromJson(json),
)
);
}
Future<GQLResult<User>> createUser(CreateUserInput input) async {
return GQLResultWrapper.wrap(() =>
_client.mutate<User>(
mutation: '''
mutation CreateUser(\$input: CreateUserInput!) {
createUser(input: \$input) {
id
name
email
}
}
''',
variable: {'input': input.toJson()},
modelParser: (json) => User.fromJson(json),
)
);
}
}
// Usage in a Flutter widget
class UserListWidget extends StatefulWidget {
@override
_UserListWidgetState createState() => _UserListWidgetState();
}
class _UserListWidgetState extends State<UserListWidget> {
final GraphQLService _graphQLService = GraphQLService();
List<User> users = [];
String? errorMessage;
bool isLoading = false;
@override
void initState() {
super.initState();
_loadUsers();
}
Future<void> _loadUsers() async {
setState(() => isLoading = true);
final result = await _graphQLService.getUsers();
result.when(
onSuccess: (data) {
setState(() {
users = data;
isLoading = false;
errorMessage = null;
});
},
onFailure: (error) {
setState(() {
errorMessage = error.message;
isLoading = false;
});
},
);
}
@override
Widget build(BuildContext context) {
if (isLoading) {
return Center(child: CircularProgressIndicator());
}
if (errorMessage != null) {
return Center(child: Text('Error: $errorMessage'));
}
return ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
final user = users[index];
return ListTile(
title: Text(user.name),
subtitle: Text(user.email),
);
},
);
}
}
API Reference
GQLConfig
Main configuration class for the GraphQL client.
GQLConfig({
required String baseURL,
TokenCallback? bearerToken,
List<GQLAuthProvider>? authProviders,
List<GQLExceptionProvider>? exceptionProviders,
Map<String, String>? defaultHeaders,
Store? cacheStore,
FetchPolicy? queryPolicy,
FetchPolicy? watchQueryPolicy,
FetchPolicy? mutationPolicy,
List<String>? responseNodePaths,
})
GQLClient
Main GraphQL client with methods for queries and mutations.
Methods
query<T>()
- Execute a GraphQL queryqueryList<T>()
- Execute a GraphQL query returning a listmutate<T>()
- Execute a GraphQL mutationmutateList<T>()
- Execute a GraphQL mutation returning a listsaveCacheData()
- Save data to cachegetCacheData()
- Retrieve data from cacheclearCache()
- Clear all cached dataupdateCache()
- Update cache with new data
GQLResult
Result wrapper using pattern matching for elegant error handling.
// Pattern matching
switch (result) {
case Success<T>(:final data):
// Handle success
break;
case Failure<T>(:final exception):
// Handle error
break;
}
// Callback style
result.when(
onSuccess: (data) => print('Success: $data'),
onFailure: (error) => print('Error: ${error.message}'),
);
GQLResultWrapper
Wrapper for automatic error handling and result transformation.
// Static method
final result = await GQLResultWrapper.wrap(() => someGraphQLOperation());
// Instance method
final wrapper = GQLResultWrapper();
final result = await wrapper.call(() => someGraphQLOperation());
Best Practices
-
Use Result Wrapper: Always wrap your GraphQL operations with
GQLResultWrapper
for consistent error handling. -
Configure Node Paths: Set up
responseNodePaths
to automatically extract data from your API responses. -
Implement Custom Providers: Create custom authentication and exception providers for your specific needs.
-
Cache Strategy: Choose appropriate cache policies based on your data requirements.
-
Type Safety: Always use generic types with your model parsers for type safety.
-
Error Handling: Implement comprehensive error handling with custom exception providers.
Contributing
We welcome contributions! Please see our Contributing Guide for details.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Support
If you encounter any issues or have questions, please:
- Check the documentation
- Search existing issues
- Create a new issue
Changelog
See CHANGELOG.md for a detailed list of changes and updates.ter
Libraries
- config/config
- config/src/gql_auth_provider
- config/src/gql_exception_provider
- config/src/graphql_config
- exceptions/exceptions
- exceptions/src/gql_error_model
- exceptions/src/gql_exceptions
- graphql_infra_tool
- Support for doing something awesome.
- result/result
- result/src/gql_result
- result/src/gql_result_wrapper
- result/src/gql_result_wrapper_impl
- service/service
- service/src/gql_client
- service/src/gql_logger
- service/src/operation_type_enum