Software Engineering & Digital Products for Global Enterprises since 2006
CMMi Level 3SOC 2ISO 27001
View all services
Staff Augmentation
Embed senior engineers in your team within weeks.
Dedicated Teams
A ring-fenced squad with PM, leads, and engineers.
Build-Operate-Transfer
We hire, run, and transfer the team to you.
Contract-to-Hire
Try the talent. Convert when you're ready.
ForceHQ
Skill testing, interviews and ranking — powered by AI.
RoboRingo
Build, deploy and monitor voice agents without code.
MailGovern
Policy, retention and compliance for enterprise email.
Vishing
Test and train staff against AI-driven voice attacks.
CyberForceHQ
Continuous, adaptive security training for every team.
IDS Load Balancer
Built for Multi Instance InDesign Server, to distribute jobs.
AutoVAPT.ai
AI agent for continuous, automated vulnerability and penetration testing.
Salesforce + InDesign Connector
Bridge Salesforce data into InDesign to design print catalogues at scale.
View all solutions
Banking, Financial Services & Insurance
Cloud, digital and legacy modernisation across financial entities.
Healthcare
Clinical platforms, patient engagement, and connected medical devices.
Pharma & Life Sciences
Trial systems, regulatory data, and field-force enablement.
Professional Services & Education
Workflow automation, learning platforms, and consulting tooling.
Media & Entertainment
AI video processing, OTT platforms, and content workflows.
Technology & SaaS
Product engineering, integrations, and scale for tech companies.
Retail & eCommerce
Shopify, print catalogues, web-to-print, and order automation.
View all industries
Blog
Engineering notes, opinions, and field reports.
Case Studies
How clients shipped — outcomes, stack, lessons.
White Papers
Deep-dives on AI, talent models, and platforms.
Portfolio
Selected work across industries.
View all resources
About Us
Who we are, our story, and what drives us.
Co-Innovation
How we partner to build new products together.
Careers
Open roles and what it's like to work here.
News
Press, announcements, and industry updates.
Leadership
The people steering MetaDesign.
Locations
Gurugram, Brisbane, Detroit and beyond.
Contact Us
Talk to sales, hiring, or partnerships.
Request TalentStart a Project
Mobile Development

Integrating Backend Services: RESTful APIs and GraphQL in Flutter

SS
Sukriti Srivastava
Technical Content Lead
December 1, 2024
10 min read
Integrating Backend Services: RESTful APIs and GraphQL in Flutter — Mobile Development | MetaDesign Solutions

REST vs. GraphQL: Choosing the Right Backend Integration

Every Flutter app that displays dynamic data needs to communicate with a backend server. The two dominant paradigms—RESTful APIs and GraphQL—solve the data-fetching problem differently, and choosing the right one (or combining both) depends on your data shape, team expertise, and performance requirements. REST uses multiple endpoints with fixed response structures. GraphQL uses a single endpoint where the client specifies exactly what data it needs. Understanding the trade-offs is essential before writing your first API call.

Basic REST Integration with the http Package

Flutter's official `http` package provides lightweight HTTP networking. Make a GET request: `final response = await http.get(Uri.parse('https://api.example.com/users'));`. Parse the JSON response: `final users = jsonDecode(response.body) as List;`. For POST requests, include headers and body: `await http.post(uri, headers: {'Content-Type': 'application/json'}, body: jsonEncode({'name': 'John'}))`. Always check `response.statusCode` before parsing—200 for success, 401 for unauthorized, 404 for not found, 500 for server error. The `http` package is ideal for simple apps with a few API calls and no advanced requirements.

Advanced REST with Dio: Interceptors, Retry, and Cancellation

Dio is the most popular HTTP client in the Flutter ecosystem, offering features the `http` package lacks. Interceptors enable global request/response processing: add authentication headers to every request, log all API calls, or implement automatic token refresh when a 401 is received. Request cancellation with `CancelToken` prevents wasted bandwidth when users navigate away. Retry logic with `dio_smart_retry` automatically retries failed requests with exponential backoff. Form data and file uploads work natively: `FormData.fromMap({'file': await MultipartFile.fromFile(path)})`. Configure base URLs, timeouts, and default headers in a single `Dio()` instance shared across the app.

GraphQL Integration with graphql_flutter

The `graphql_flutter` package provides a full-featured GraphQL client for Flutter. Configure the client: `GraphQLClient(link: HttpLink('https://api.example.com/graphql'), cache: GraphQLCache())`. Use the `Query` widget for declarative data fetching: `Query(options: QueryOptions(document: gql('{users { id name email }}'))`, builder: (result, {refetch, fetchMore}) { ... })`. The key advantage: request exactly the fields you need—no over-fetching (receiving unnecessary data) or under-fetching (needing multiple requests). This reduces payload sizes by 30–70% compared to REST for complex data graphs.

Mutations, Subscriptions, and Real-Time Data

Mutations modify server-side data: `Mutation(options: MutationOptions(document: gql('mutation CreateUser($name: String!) { createUser(name: $name) { id name } }'))`. Subscriptions enable real-time data streams via WebSocket: `Subscription(options: SubscriptionOptions(document: gql('subscription { messageAdded { id text sender } }')))`. Configure the WebSocket link: `WebSocketLink('wss://api.example.com/graphql')`. Combine HTTP and WebSocket links using `Link.split()` to route subscriptions through WebSocket and queries/mutations through HTTP. This enables chat apps, live dashboards, and collaborative editing features.

Transform Your Publishing Workflow

Our experts can help you build scalable, API-driven publishing systems tailored to your business.

Book a free consultation

Client-Side Caching and Offline Strategy

Both REST and GraphQL benefit from client-side caching. GraphQL's `GraphQLCache` with `InMemoryStore` provides normalized caching—each entity is stored once by its ID, and any query referencing that entity receives the cached version automatically. For REST, use packages like `dio_cache_interceptor` or implement manual caching with `Hive` or `shared_preferences`. The cache-first policy shows cached data immediately and fetches updates in the background. The network-only policy always fetches fresh data. Choose policies per query based on data freshness requirements.

Structured Error Handling and Connectivity Awareness

Robust error handling separates amateur from production-quality apps. Create a centralized error handler that categorizes failures: network errors (no connectivity—show cached data or offline UI), timeout errors (slow server—retry with backoff), authentication errors (expired token—refresh and retry), validation errors (invalid input—show field-level messages), and server errors (500—show generic error with retry). Use `connectivity_plus` to detect network state and proactively disable API-dependent features when offline. For GraphQL, handle both `graphQLErrors` (schema validation, authorization) and `linkException` (network) separately.

Repository Pattern: Abstracting the Data Layer

The Repository Pattern creates a clean abstraction between your Flutter UI and the data source. Define a `UserRepository` interface with methods like `Future> getUsers()` and `Future createUser(String name)`. Create `RestUserRepository` and `GraphqlUserRepository` implementations. Inject the repository into your state management layer (Riverpod, BLoC, Provider). This enables switching between REST and GraphQL without changing any UI code, facilitates testing with mock repositories, and keeps business logic completely isolated from networking details.

FAQ

Frequently Asked Questions

Common questions about this topic, answered by our engineering team.

Use REST for simple apps with well-defined endpoints and fixed data shapes. Use GraphQL when you need to fetch complex, nested data from multiple resources in a single request, or when different screens need different subsets of the same data. Many apps use both depending on the use case.

Use Dio when you need interceptors (global auth headers, logging, token refresh), request cancellation, retry logic, file uploads with progress tracking, or advanced configuration. The http package is sufficient for simple apps with a few straightforward API calls.

graphql_flutter uses normalized caching where each entity is stored once by its ID. Any query referencing that entity receives the cached version. Policies include cache-first (show cached immediately, fetch in background), network-only (always fresh), and cache-only (offline).

Create a centralized error handler that categorizes failures: network errors (show offline UI), timeouts (retry with backoff), auth errors (refresh token), validation errors (field-level messages), and server errors (generic error with retry). Use connectivity_plus for proactive network detection.

The Repository Pattern abstracts the data source behind an interface (e.g., UserRepository). Create separate implementations for REST and GraphQL. This enables switching data sources without UI changes, simplifies testing with mocks, and isolates networking from business logic.

Discussion

Join the Conversation

Ready when you are

Let's build something great together.

A 30-minute call with a principal engineer. We'll listen, sketch, and tell you whether we're the right partner — even if the answer is no.

Talk to a strategist
Need help with your project? Let's talk.
Book a call