Understanding REST and GraphQL APIs: Key Differences, Use Cases, and Implementation

Engineering

APIs

Comparison

Summary

This article details REST and GraphQL, popular API architectures, explaining their unique characteristics, strengths, and limitations. Implementation examples with ExpressJS, MongoDB, and Flutter illustrate practical applications. Comparisons guide developers on choosing the right API based on project needs.

Key insights:
  • API Characteristics: REST relies on resource-based endpoints and HTTP methods; GraphQL offers single-endpoint querying with flexible data requests.

  • Performance Differences: REST supports HTTP caching, benefiting stable applications, while GraphQL minimizes data transfer through efficient querying.

  • Error Handling: REST uses HTTP status codes, while GraphQL centralizes errors in the response body for clarity.

  • Implementation Guidance: Detailed setups for REST and GraphQL APIs using ExpressJS and MongoDB highlight the integration process.

  • Framework Recommendations: Express.js, Django REST, Apollo Server, and Hasura are outlined as top frameworks for REST and GraphQL API development.

  • GraphQL in Flutter: Efficient use of GraphQL with Flutter involves structured queries, code generation, and specific libraries like graphql_flutter.

  • Use Case Fit: REST is suitable for straightforward, stable data; GraphQL benefits dynamic, data-intensive apps with nested data needs.

  • Development Complexity: REST’s simplicity eases maintenance, while GraphQL requires a schema and may involve more setup complexity.

Introduction

APIs, or Application Programming Interfaces, are the unseen connectors that allow different software systems to work together, making digital experiences smooth and interconnected. Much like skilled servers in a restaurant, APIs take requests from one application, relay them to the appropriate service, and return the requested data. This allows developers to create robust applications by tapping into existing services and data sources. Every time you check the weather or book a ride, APIs are at work, passing information between systems to deliver real-time results.

When developing modern web applications, choosing the right API architecture is necessary for smooth functioning of your end product. Two widely-used options are REST (Representational State Transfer) APIs and GraphQL. Each has unique strengths: REST is known for its straightforward structure and reliability, while GraphQL offers flexibility, especially for complex data needs. Understanding their differences can help developers make informed decisions for their projects.

This article explores the differences between REST and GraphQL, guides you on choosing the best fit for your project, and provides examples of implementing each in a backend with MongoDB and a Flutter frontend, as well as top libraries to enhance your API interactions.

REST APIs: An Overview

Representational State Transfer is an architectural style that organizes data into resources accessed by unique URLs (endpoints). Since its development in the early 2000s, REST has become the backbone of web-based APIs. REST relies on resource-based endpoints and uses standard HTTP methods to perform actions on data.

1. Key Characteristics of REST APIs

Stateless Communication: Each client request to the server must contain all the necessary information for processing, with no retained state. This model enables scalability and reliability.

HTTP Methods: REST uses HTTP methods like GET, POST, PUT, and DELETE to perform CRUD (Create, Read, Update, Delete) operations on resources.

Uniform Resource Identifier (URI) Structure: REST APIs use structured URIs to represent resources, such as `/products` for a list of products or `/products/{id}` for a specific product.

2. Implementation Examples for REST API

To retrieve a list of products in a REST API:

GET /products

To add a new product:

POST /products
Content-Type: application/json

{
    "name": "Wireless Earbuds",
    "price": 59.99,
    "category": "Electronics"
}

To update a product:

PUT /products/123
Content-Type: application/json

{
    "price": 49.99
}

To delete a product:

DELETE /products/123

3. Advantages of REST

Simplicity and Standardization: REST’s reliance on HTTP methods and structured URIs makes it easy to learn and implement.

Caching Support: REST supports HTTP caching, reducing server load and improving client performance.

Browser Compatibility: REST works well with browsers, as it aligns with HTTP methods and status codes, making it compatible with most web applications.

CRUD Alignment: REST’s structure naturally aligns with CRUD operations, making it ideal for managing resources and database interactions.

4. Drawbacks of REST

Over-fetching and Under-fetching: REST responses may include more data than necessary (over-fetching) or lack essential fields (under-fetching).

Multiple Requests for Nested Data: Retrieving related resources may require multiple requests, impacting performance.

Error Handling: HTTP status codes communicate request outcomes: a 200 for success, 400 for client errors, and 500 for server errors. This structure is simple but can be inconsistent, as REST lacks a specific error specification, sometimes resulting in unclear error reporting.

5. Use Cases for REST API

REST is a suitable choice when data requirements are straightforward, predictable, and well-structured. It works well when data is accessed in a consistent way across clients and when caching is a priority, such as in public-facing applications with stable endpoints and well-defined access controls. REST’s use of unique URIs and HTTP methods simplifies handling of CRUD operations, providing reliable and cacheable responses, making it ideal for environments with uniform data interactions and low data querying complexity.

GraphQL: An Overview

GraphQL, introduced by Facebook in 2012, is a query language and runtime that allows clients to request specific data fields and nest related data in a single request. Instead of multiple endpoints, GraphQL uses a single endpoint where clients specify their desired data structure and relationships.

1. Key Characteristics of GraphQL

Single Endpoint: GraphQL APIs use a single endpoint, such as /graphql, for all queries and mutations, simplifying the client-server interaction.

Flexible Query Structure: GraphQL allows clients to define the precise structure and depth of the response, eliminating over-fetching and under-fetching issues.

Strongly Typed Schema: A GraphQL schema defines the types, fields, and relationships between data, providing a structured way for clients to explore and interact with data.

2. Implementation Examples for GraphQL API

To retrieve only the names and prices of products in a GraphQL API:

query {
  products {
    name
    price
  }
}

To retrieve a specific product and nested data about its reviews:

query {
  product(id: "123") {
    name
    price
    reviews {
      rating
      comment
    }
  }
}

To add a new product:

mutation {
  addProduct(name: "Wireless Earbuds", price: 59.99, category: "Electronics") {
    id
    name
    price
  }
}

3. Advantages of GraphQL

Reduced Over-fetching and Under-fetching: GraphQL allows clients to request only the necessary data, minimizing redundant data transfers.

Single Request for Nested Data: A single query can retrieve nested and related data, ideal for applications with complex data relationships.

Real-Time Data with Subscriptions: GraphQL supports subscriptions, enabling real-time data updates—a feature beneficial for apps that need constant data refreshes.

Schema-Driven Development: GraphQL’s schema enables auto-generated documentation and provides a clear data model for developers.

Error Handling: GraphQL consolidates all errors in the response body, maintaining a `200 OK` status even when an error occurs. Errors appear within the “errors” field, distinguishing them clearly from transport errors. This method, guided by GraphQL’s error specification, offers more consistent and structured error handling.

4. Drawbacks of GraphQL

Caching Challenges: GraphQL responses are custom to each request, complicating HTTP caching strategies.

Setup and Maintenance Complexity: Implementing GraphQL requires more extensive setup and maintenance, particularly with schema updates.

Learning Curve: GraphQL’s structure requires familiarity with queries, mutations, and schemas, which may pose challenges for teams new to query-based APIs.

5. Use Cases for GraphQL API

GraphQL is advantageous for applications where data demands vary widely between clients, or where efficiency and flexibility in data retrieval are needed. It is particularly useful when consolidating data from multiple sources is essential, as it allows for retrieving only the required fields in one call. This makes GraphQL a strong fit for projects with dynamic front-end requirements, complex data structures, or limited bandwidth, where minimizing the volume of data transferred is a priority.

Comparing REST and GraphQL

The following table summarizes the key features of REST and GraphQL:

Choosing the Right Approach

Deciding between REST and GraphQL depends on factors such as the application’s data needs, performance requirements, and team expertise.

Data Requirements: REST is ideal for applications where each resource has a well-defined structure, and data requirements are predictable. In contrast, GraphQL’s flexible querying is better suited to applications with complex, relational data.

Performance Concerns: REST’s HTTP caching can be advantageous for performance, especially for applications that rely on repeated data access. GraphQL reduces redundant data transfers and optimizes bandwidth but requires custom caching.

Real-Time Needs: Applications with real-time data needs, such as chat, notifications, or live sports updates, benefit from GraphQL’s subscription capability, which REST lacks.

Development Complexity: REST’s simplicity favors rapid development and easy maintenance, making it suitable for smaller applications or teams. GraphQL provides flexibility but requires schema management, adding to complexity.

API Lifecycle Management: REST’s endpoint-based design is easier to maintain long-term, while GraphQL’s schema may demand updates as requirements evolve, making it suited for adaptable, dynamic applications.

REST API Development with ExpressJS and MongoDB

These are the steps to set up a REST API:

Initialize the Project: Create a new directory for the project, initialize npm, and install Express.

mkdir rest-api && cd rest-api
npm init -y
npm

Set Up MongoDB Connection: Connect ExpressJS to MongoDB using Mongoose, an Object Data Modeling (ODM) library for MongoDB. The 'index.js' file should look something like this:

require('dotenv').config();
const express = require('express');
const mongoose = require('mongoose');

const app = express();

app.use(express.json());

const mongoString = process.env.DATABASE_URL;
mongoose.connect(mongoString);
const database = mongoose.connection;

database.on('error', (error) => {
    console.log(error)
})

database.once('connected', () => {
    console.log('Database Connected');
})

app.listen(3000, () => {
    console.log(`Server Started at ${3000}`)
})

Define Models and Routes: Create a simple schema for data, such as a User model, and add CRUD operations. Create a file named ‘models/User.js’ and add the following code:

const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
    username: {
        required: true,
        type: String
    },
    email: {
        required: true,
        type: String
    },
    age: {
        required: true,
        type: Number
    }
})

module.exports = mongoose.model('User', userSchema)

Implement API Endpoints: Set up routes in Express for the CRUD operations in ‘routes/routes.js’.

const express = require('express');
const User = require('../models/User');
const router = express.Router()

//Post Method
router.post('/post', async (req, res) => {
    const data = new User({
        username: req.body.username,
        email: req.body.email,
        age: req.body.age
    })

    try {
        const dataToSave = await data.save();
        res.status(200).json(dataToSave)
    }
    catch (error) {
        res.status(400).json({message: error.message})
    }
})

//Get all Method
router.get('/getAll', async (req, res) => {
    try{
        const data = await User.find();
        res.json(data)
    }
    catch(error){
        res.status(500).json({message: error.message})
    }
})

//Get by ID Method
router.get('/getOne/:id', async (req, res) => {
    try{
        const data = await User.findById(req.params.id);
        res.json(data)
    }
    catch(error){
        res.status(500).json({message: error.message})
    }
})

//Update by ID Method
router.patch('/update/:id', async (req, res) => {
    try {
        const id = req.params.id;
        const updatedData = req.body;
        const options = { new: true };

        const result = await User.findByIdAndUpdate(
            id, updatedData, options
        )

        res.send(result)
    }
    catch (error) {
        res.status(400).json({ message: error.message })
    }
})

//Delete by ID Method
router.delete('/delete/:id', async (req, res) => {
    try {
        const id = req.params.id;
        const data = await User.findByIdAndDelete(id)
        res.send(`User with username ${data.username} has been deleted..`)
    }
    catch (error) {
        res.status(400).json({ message: error.message })
    }
})

module.exports = router;

Update ‘index.js’ to use the routes:

const routes = require('./routes/routes');

app.use('/api', routes)

Finally, Test the API using tools like Postman or curl. Here are some example requests:

  • POST: http://localhost:3000/api/post

    Body: { "username": "johndoe", "email": "john@example.com", "age": 30 }

  • GET all: http://localhost:3000/api/getAll

  • GET one: http://localhost:3000/api/getOne/:id

  • PATCH: http://localhost:3000/api/update/:id

    Body: { "username": "janedoe", "email": "jane@example.com", "age": 28 }

  • DELETE: http://localhost:3000/api/delete/:id

This setup provides a basic REST API for managing user data in a MongoDB database.

GraphQL API Development with ExpressJS and MongoDB

These are the steps to set up a GraphQL API:

Initialize the Project: Set up Express with additional dependencies for GraphQL.

mkdir graphql-api && cd graphql-api
npm init -y
npm

Set Up MongoDB and Schema: Define models similarly with Mongoose and establish a MongoDB connection as shown in the REST example.

Define the GraphQL Schema: Create GraphQL schemas for accessing the data in ‘schema.js’.

const { GraphQLObjectType, GraphQLString, GraphQLInt, GraphQLList, GraphQLSchema } = require('graphql');
const User = require('./models/User');

const UserType = new GraphQLObjectType({
    name: 'User',
    fields: () => ({
        id: { type: GraphQLString },
        username: { type: GraphQLString },
        email: { type: GraphQLString },
        age: { type: GraphQLInt }
    })
});

const RootQuery = new GraphQLObjectType({
    name: 'RootQueryType',
    fields: {
        user: {
            type: UserType,
            args: { id: { type: GraphQLString } },
            resolve(parent, args) {
                return User.findById(args.id);
            }
        },
        users: {
            type: new GraphQLList(UserType),
            resolve(parent, args) {
                return User.find({});
            }
        }
    }
});

module.exports = new GraphQLSchema({
    query: RootQuery
});

Set up Resolvers: Create a file named ‘resolvers.js’ to define how to resolve queries and mutations:

const User = require('./models/User');

const resolvers = {
    Query: {
        user: async (parent, args) => await User.findById(args.id),  // Fetch a specific user by ID
        users: async () => await User.find({}),  // Fetch all users
    },
    Mutation: {
        createUser: async (parent, { username, email, age }) => {
            const user = new User({ username, email, age });
            await user.save();
            return user;
        },
        updateUser: async (parent, { id, username, email, age }) => {
            const updatedUser = await User.findByIdAndUpdate(id, { username, email, age }, { new: true });
            return updatedUser;
        },
        deleteUser: async (parent, { id }) => {
            const deletedUser = await User.findByIdAndDelete(id);
            return deletedUser;
        }
    }
};

module.exports = resolvers;

Integrate GraphQL with Express: Create a file named ‘server.js’. Use graphql-http to connect the GraphQL schema.

const express = require('express');
const { graphqlHTTP } = require('graphql-http');
const mongoose = require('mongoose');
const schema = require('./schema');
const resolvers = require('./resolvers');

const app = express();

mongoose.connect('mongodb://localhost:27017/userDB', {
  useNewUrlParser: true,
  useUnifiedTopology: true
});

app.use('/graphql', graphqlHTTP({
  schema,
  rootValue: resolvers,
  graphiql: true
}));

app.listen(4000, () => {
  console.log('Server is running on port 4000');
});

Test using GraphiQL to run GraphQL queries against the API, or using other more traditional methods like Postman. After starting the server with node server.js, you can access GraphiQL at http://localhost:4000/graphql. Note that the the /graphql endpoint will be used for all queries.

Best Frameworks for REST and GraphQL

When developing APIs, choosing the right framework is critical for maintaining efficiency and performance. This section will explore some of the best frameworks for both REST and GraphQL approaches.

1. REST Frameworks

Express.js: A minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications.

Django REST Framework: A powerful and flexible toolkit for building Web APIs, based on Django.

Flask-RESTful: An extension for Flask that adds support for quickly building REST APIs.

Spring Boot: Offers a fast way to build applications with Java, providing auto-configuration for application functionality.

ASP.NET Core: Microsoft's framework for building modern, cloud-based, and internet-connected applications.

2. GraphQL Frameworks

express-graphql: A Create GraphQL HTTP server middleware for Express.js. Note that this framework is being deprecated and will no longer be maintained.

graphql-http: A lightweight, HTTP-focused GraphQL server implementation.

Apollo Server: A comprehensive, production-ready GraphQL server that supports various Node.js HTTP frameworks.

Hasura: An open-source engine that connects to your databases & microservices and auto-generates a production-ready GraphQL backend.

Grafbase: A serverless GraphQL platform with real-time capabilities, suitable for modern data applications.

GraphQL Yoga: A fully-featured GraphQL server with focus on easy setup, performance and great developer experience.

Dgraph: A native GraphQL database with a graph backend, designed to be distributed and production-ready.

GraphQL.js: The reference implementation of the GraphQL specification, ideal for building JavaScript-based GraphQL schemas.

3. Summary Table

Here is a comparison table summarizing the key features of these frameworks. Note that express-graphql was omitted because of its discontinued support.

Setting Up Apollo Server with Express and MongoDB

Using Apollo Server with Express provides flexibility and the full power of GraphQL while offering a straightforward setup for handling complex data interactions.

1. Project Initialization

First, create and initialize a new Node.js project:

mkdir graphql-apollo-project
cd graphql-apollo-project
npm init -y

2. Install Dependencies

Install the necessary packages, including Apollo Server, Express, GraphQL, and Mongoose (for MongoDB interaction):

npm

3. Define the GraphQL Schema

Create a ‘typeDefs.js’ model to define your GraphQL types and queries. This example schema includes a User type and CRUD operations:

// models/typeDefs.js
const gql = require("graphql-tag");


// Define the GraphQL schema using SDL (Schema Definition Language)
const typeDefs = gql`
  type User {
    id: ID!
    username: String!
    email: String!
    age: Int
  }

  type Query {
    users: [User]!
    user(id: ID!): User
  }

  type Mutation {
    createUser(username: String!, email: String!, age: Int): User!
    updateUser(id: ID!, username: String, email: String, age: Int): User
    deleteUser(id: ID!): User
  }
`;

module.exports = { typeDefs };

4. Set Up Resolvers

In ‘resolvers.js’, define the resolver functions that will interact with MongoDB. This example fetches data from MongoDB through the Mongoose model:

// resolvers.js
const { User } = require("./models/User.js");

const resolvers = {
  Query: {
    // Resolver to fetch all users
    users: async () => await User.find({}),
    // Resolver to fetch a single user by ID
    user: async (parent, args) => await User.findById(args.id),
  },
  Mutation: {
    // Resolver to create a new user
    createUser: async (parent, args) => {
      const { username, email, age } = args;
      const newUser = new User({
        username,
        email,
        age,
      });
      await newUser.save();
      return newUser;
    },
    // Resolver to update an existing user
    updateUser: async (parent, args) => {
      const { id } = args;
      // Find the user by ID and update, returning the updated document
      const updatedUser = await User.findByIdAndUpdate(id, args, { new: true });
      if (!updatedUser) {
        throw new Error(`User with ID ${id} not found`);
      }
      return updatedUser;
    },
    // Resolver to delete a user
    deleteUser: async (parent, args) => {
      const { id } = args;
      // Find the user by ID and delete
      const deletedUser = await User.findByIdAndDelete(id);
      if (!deletedUser) {
        throw new Error(`User with ID ${id} not found`);
      }
      return deletedUser;
    },
  },
};

module.exports = { resolvers };

5. Configure Apollo Server with Express

In your ‘index.js’ file, configure Apollo Server with Express and MongoDB connection logic:

// index.js
const { ApolloServer } = require("@apollo/server");
const { startStandaloneServer } = require("@apollo/server/standalone");
const mongoose = require("mongoose");
const { resolvers } = require("./resolvers.js");
const { typeDefs } = require("./models/typeDefs.js");

// MongoDB connection URI
const MONGO_URI = "mongodb://localhost:27017/user-graphql-api";

// Connect to MongoDB
mongoose
  .connect(MONGO_URI, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  })
  .then(() => {
    console.log(`DB Connected`);
  })
  .catch(err => {
    console.log(err.message);
  });

// Create a new Apollo Server instance
const server = new ApolloServer({ typeDefs, resolvers });

// Start the server
startStandaloneServer(server, {
  listen: { port: 4000 },
}).then(({ url }) => {
  console.log(`Server ready at ${url}`);
});

6. Create User Model

To store and manage user data in MongoDB, we need to define a User model using Mongoose:

// models/User.js
const mongoose = require("mongoose");

// Define the User schema and model for storing user information in MongoDB
const User = mongoose.model("User", {
  username: String,
  email: String,
  age: Number,
});

module.exports = { User };

7. Test the API

Start your server with node index.js and access the GraphQL playground at http://localhost:4000/graphql. Once the server is running, you can use these example queries and mutations in the GraphQL Playground, calling only the fields you require:

Query All Users:

query {
  users {
    id
    username
    email
  }
}

Query Single User by ID:

query {
  user(id: "user-id-here") {
    id
    username
    email
    age
  }
}

Create a New User:

mutation {
  createUser(username: "Alice", email: "alice@example.com", age: 30) {
    id
    username
    email
    age
  }
}

Update a User:

mutation {
  updateUser(
    id: "user-id-here"
    username: "newUsername"
    email: "newemail@example.com"
    age: 30
  ) {
    id
    username
    email
    age
  }
}

Delete a User:

mutation {
  deleteUser(id: "user-id-here") {
    id
    username
    email
    age
  }
}

These examples illustrate basic Create, Read, Update, Delete operations, which form the foundation of most GraphQL APIs. Using Apollo Server with Express and MongoDB offers a streamlined approach for applications that need flexible, structured data access, as well as powerful querying capabilities. 

While Apollo Server is one of the most widely used frameworks for GraphQL APIs, other frameworks also offer unique benefits. Apollo Server is advantageous for enterprise-grade applications due to its rich features and compatibility with popular databases like MongoDB, while Hasura is more suited for real-time apps needing a lightweight setup​. It is worth exploring all the capabilities of the frameworks discussed previously in detail.

Setting up REST API Integration in Flutter with Stacked

1. Set up Dependencies

First, add necessary dependencies to 'pubspec.yaml':

dependencies:
  flutter:
    sdk: flutter
  stacked: ^3.0.0
  dio

Run flutter pub get to install them.

2. Create a REST API Service

Define a UserService that uses the Dio package to handle HTTP requests:

// services/user_service.dart
import 'package:dio/dio.dart';
import '../models/user.dart';

class UserService {
  final Dio _dio = Dio(BaseOptions(baseUrl: 'https://api.example.com'));

  Future<List<User>> fetchUsers() async {
    final response = await _dio.get('/users');
    return (response.data as List).map((json) => User.fromJson(json)).toList();
  }
}

3. Set Up a ViewModel

Create UserViewModel that uses UserService to retrieve data. This ViewModel will also handle UI updates through notifyListeners():

// viewmodels/user_viewmodel.dart
import 'package:stacked/stacked.dart';
import '../models/user.dart';
import '../services/user_service.dart';

class UserViewModel extends BaseViewModel {
  final UserService _userService;
  List<User> users = [];

  UserViewModel(this._userService);

  Future<void> loadUsers() async {
    users = await _userService.fetchUsers();
    notifyListeners();
  }
}

4. Create a User Interface

In the UI, use ViewModelBuilder to connect the UserViewModel to a widget:

// views/user_view.dart
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import '../viewmodels/user_viewmodel.dart';

class UserView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ViewModelBuilder<UserViewModel>.reactive(
      viewModelBuilder: () => UserViewModel(UserService()),
      onModelReady: (viewModel) => viewModel.loadUsers(),
      builder: (context, viewModel, child) {
        return Scaffold(
          appBar: AppBar(title: Text('Users')),
          body: viewModel.users.isEmpty
              ? Center(child: CircularProgressIndicator())
              : ListView.builder(
                  itemCount: viewModel.users.length,
                  itemBuilder: (context, index) {
                    final user = viewModel.users[index];
                    return ListTile(
                      title: Text(user.name),
                      subtitle: Text(user.email),
                    );
                  },
                ),
        );
      },
    );
  }
}

Setting up GraphQL API Integration in Flutter with Stacked

1. Add GraphQL Dependencies

In 'pubspec.yaml', add graphql_flutter:

dependencies:
  graphql_flutter

Run flutter pub get.

2. Create a GraphQL Service

Define a GraphQLService that connects to your GraphQL endpoint:

// services/graphql_service.dart
import 'package:graphql_flutter/graphql_flutter.dart';

class GraphQLService {
  final GraphQLClient client;

  GraphQLService()
      : client = GraphQLClient(
          link: HttpLink('https://graphql.example.com'),
          cache: GraphQLCache(),
        );

  Future<List<dynamic>> fetchUsers() async {
    const query = '''
      query {
        users {
          id
          name
          email
        }
      }
    ''';

    final result = await client.query(QueryOptions(document: gql(query)));
    return result.data?['users'] ?? [];
  }
}

3. Define ViewModel with GraphQL Query

Similar to the REST setup, create UserGraphQLViewModel that uses GraphQLService:

// viewmodels/user_graphql_viewmodel.dart
import 'package:stacked/stacked.dart';
import '../services/graphql_service.dart';

class UserGraphQLViewModel extends BaseViewModel {
  final GraphQLService _graphQLService;
  List<dynamic> users = [];

  UserGraphQLViewModel(this._graphQLService);

  Future<void> loadUsers() async {
    users = await _graphQLService.fetchUsers();
    notifyListeners();
  }
}

4. Connect GraphQL ViewModel to UI

In the UI, create UserGraphQLView to display data from the GraphQL service:

// views/user_graphql_view.dart
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import '../viewmodels/user_graphql_viewmodel.dart';

class UserGraphQLView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ViewModelBuilder<UserGraphQLViewModel>.reactive(
      viewModelBuilder: () => UserGraphQLViewModel(GraphQLService()),
      onModelReady: (viewModel) => viewModel.loadUsers(),
      builder: (context, viewModel, child) {
        return Scaffold(
          appBar: AppBar(title: Text('GraphQL Users')),
          body: viewModel.users.isEmpty
              ? Center(child: CircularProgressIndicator())
              : ListView.builder(
                  itemCount: viewModel.users.length,
                  itemBuilder: (context, index) {
                    final user = viewModel.users[index];
                    return ListTile(
                      title: Text(user['name']),
                      subtitle: Text(user['email']),
                    );
                  },
                ),
        );
      },
    );
  }
}

These setups enable REST and GraphQL APIs to work within a Flutter app’s Stacked architecture, making it modular, easy to maintain, and scalable.

Best Flutter/Dart Libraries

For effectively managing REST and GraphQL API integration in a Flutter app, various Dart libraries can streamline the process, each suited to distinct API needs and offering varying levels of abstraction, functionality, and performance optimization.

1. Recommended Libraries for REST APIs

http (Official Dart package): The 'http' package is the official Dart library for making HTTP requests. It provides a basic and lightweight solution that supports all standard HTTP methods. Its simplicity makes it an excellent choice for straightforward API integrations. 

Dio: Dio is a powerful HTTP client for Dart, which supports interceptors, global configuration, FormData, and more. It offers a rich set of features beyond basic HTTP requests, making it suitable for complex API interactions. Dio is particularly useful when developers need more advanced functionality than what the standard 'http' package provides. 

Chopper: Chopper is a Dart library inspired by Retrofit for Android. It generates API clients at build time, supporting interceptors and converters. This library is particularly appealing to developers familiar with Android development patterns, as it brings similar concepts to the Flutter ecosystem. 

Retrofit: Retrofit for Dart is a type-safe HTTP client that uses annotation processing to generate API clients automatically. It is inspired by Chopper and Retrofit and is a port of the popular Retrofit library from Android, offering a familiar approach to API integration for developers coming from an Android background. 

2. Recommended Libraries for GraphQL APIs

Graphql_flutter: Graphql_flutter is the official GraphQL client for Flutter. It provides a set of widgets that make it easy to integrate GraphQL into Flutter applications. The library supports caching and offline persistence, making it a comprehensive solution for GraphQL-based apps. 

Ferry: Ferry is a powerful GraphQL client that focuses on cache manipulation. It supports code generation for type-safe queries and offers features like offline support and optimistic updates. Ferry is ideal for developers who need fine-grained control over their GraphQL data management.

Artemis: Artemis is a build-time code generator for GraphQL queries. It generates type-safe query classes, which can be used with various GraphQL clients. This library is particularly useful for ensuring type safety in GraphQL operations and integrates well with other GraphQL solutions.

Hasura Connect: Designed specifically for Hasura users, this library offers automatic cache invalidation and real-time updates through subscriptions. It is especially beneficial for applications requiring real-time interaction, such as collaborative tools or apps where live updates are crucial.

Gql: The gql package provides low-level GraphQL utilities for Dart. It offers building blocks for creating custom GraphQL clients, giving developers the flexibility to implement their own GraphQL solutions. This library is suitable for those who need granular control over their GraphQL implementations.

Recommendations to Structure GraphQL Queries in Flutter

When working with GraphQL in Flutter applications, it is important to maintain a clean and maintainable codebase. While most developers are familiar with implementing RESTful APIs, GraphQL has its own set of challenges to overcome. One common pitfall is hardcoding queries directly into your Dart files, which can lead to several issues such as reduced readability, difficulty in maintenance, and potential security risks. Here is how you can avoid hardcoding queries and structure your GraphQL Flutter apps more effectively:

1. Separate GraphQL Files

Instead of embedding GraphQL queries directly in your Dart code, create separate ‘.graphql’ files for each widget or logical component. This approach allows for better organization and reusability of your queries. For example:



2. Use Fragments

Utilize GraphQL fragments to define reusable pieces of queries. Fragments allow you to split your queries into smaller, manageable parts while preventing over-fetching. In your .graphql files, define fragments that correspond to the data needs of specific widgets:



3. Code Generation with Artemis

Leverage Artemis for code generation. Artemis can generate Dart classes from your .graphql files, including type-safe query classes and serializers. Configure Artemis in your 'pubspec.yaml':

dependencies:
  artemis: ^7.0.0-beta.1

dev_dependencies:
  build_runner

4. Generate Code

Run the code generation command to create Dart classes from your GraphQL schemas and operations:

flutter pub run build_runner build

5. Use Generated Classes

In your Dart code, import and use the generated classes instead of hardcoding queries:

import 'person_card.graphql.dart';

class PersonCard extends StatelessWidget {
  final PersonCardFields person;

  // ... widget implementation
}

6. Implement Query Execution

Use the generated query classes with your GraphQL client:

final QueryResult result = await client.query(
  QueryOptions(
    document: HomePageQueryDocument(),
    variables: {
      // Add any variables here
    },
  ),
);

final data = HomePageQuery.fromJson(result.data!);

7. Consider Using GraphQL Codegen

For more advanced use cases, consider using the GraphQL Codegen package. This tool provides additional features like generating interfaces for fragments and helpers for GraphQL Client and GraphQL Flutter.

By following these practices, you can create a more maintainable and scalable GraphQL structure in your Flutter apps. This approach separates concerns, improves type safety, and makes it easier to manage and update your GraphQL operations as your app evolves. To summarize, the key is to keep your GraphQL operations separate from your Dart code, use fragments for reusability, and leverage code generation tools to create type-safe interfaces between your GraphQL schema and your Dart code.

Conclusion

REST and GraphQL each offer unique advantages for API design. REST’s simplicity, well-defined resources, and caching capabilities make it ideal for CRUD-based applications where data requirements are straightforward. GraphQL, with its flexible query capabilities and real-time data support, is particularly beneficial for applications with complex, nested data structures and dynamic data needs. By carefully considering an application’s technical requirements, development resources, and scalability goals, developers can choose the API architecture that best aligns with their product’s objectives. Before you start implementing these APIs, also consider checking out best practices for both REST and GraphQL.

References

Cfug. “GitHub - Cfug/Dio: A Powerful HTTP Client for Dart and Flutter, Which Supports Global Settings, Interceptors, FormData, Aborting and Canceling a Request, Files Uploading and Downloading, Requests Timeout, Custom Adapters, Etc.” GitHub, github.com/cfug/dio.

China, Chrystal R. “GraphQL vs REST API.” IBM, 1 Aug. 2024, www.ibm.com/think/topics/graphql-vs-rest-api.

Christensen, Christian Budde. “Structure Your Flutter GraphQL Apps - Christian Budde Christensen - Medium.” Medium, 6 Jan. 2022, budde377.medium.com/structure-your-flutter-graphql-apps-717ab9e46a5d.

freeCodeCamp. “How to Build a RESTful API Using Node, Express, and MongoDB.” freeCodeCamp.org, 21 Feb. 2022, www.freecodecamp.org/news/build-a-restful-api-using-node-express-and-mongodb.

GeeksforGeeks. “Top 10 Python REST API Frameworks in 2025.” GeeksforGeeks, 17 Oct. 2024, www.geeksforgeeks.org/top-python-rest-api-frameworks.

GraphQL Vs REST - a Comparison. www.howtographql.com/basics/1-graphql-is-the-better-rest.

Hombergs, Tom. Build CRUD APIs Using Apollo Server(Graphql), MongoDB and Node.Js. 21 Mar. 2023, reflectoring.io/tutorial-graphql-apollo-server-nodejs-mongodb.

“How to Implement GraphQL With Flutter + GraphQL Example | Codemagic Blog.” Codemagic Blog, 10 Apr. 2022, blog.codemagic.io/flutter-graphql.

Kaur, Preet. “10 Most Popular Frameworks for Building RESTful APIs.” 10 Most Popular Frameworks for Building RESTful APIs | Moesif Blog, 2 July 2023, www.moesif.com/blog/api-product-management/api-analytics/10-Most-Popular-Frameworks-For-Building-RESTful-APIs.

Mahugu, Geoffrey. “How to Build a GraphQL API Backend With Express and MongoDB.” Medium, 7 Jan. 2022, javascript.plainenglish.io/graphql-express-mongo-backend-d41625f728bf.

MongoDB Partner Ecosystem Catalog | MongoDB. cloud.mongodb.com/ecosystem.

“Npm: Express-graphql.” Npm, www.npmjs.com/package/express-graphql.

“Npm: Graphiql.” Npm, www.npmjs.com/package/graphiql.

“Npm: Graphql-http.” Npm, www.npmjs.com/package/graphql-http.

Retrofit - Dart API Docs. mings.in/retrofit.dart.

Steininger, Ethan. GraphQL  | MongoDB. www.mongodb.com/developer/technologies/graphql.

“Tools and Libraries | GraphQL.” GraphQL, graphql.org/community/tools-and-libraries.

Top 10 Backend Frameworks [2024]. 2 July 2024, daily.dev/blog/top-10-backend-frameworks-2024.

“Top Packages.” Dart Packages, pub.dev/packages.

“Working With REST APIs — Flutter 💙 | Codemagic Blog.” Codemagic Blog, 23 Feb. 2022, blog.codemagic.io/rest-api-in-flutter.

Other Insights

Got an app?

We build and deliver stunning mobile products that scale

Got an app?

We build and deliver stunning mobile products that scale

Got an app?

We build and deliver stunning mobile products that scale

Got an app?

We build and deliver stunning mobile products that scale

Got an app?

We build and deliver stunning mobile products that scale

Our mission is to harness the power of technology to make this world a better place. We provide thoughtful software solutions and consultancy that enhance growth and productivity.

The Jacx Office: 16-120

2807 Jackson Ave

Queens NY 11101, United States

Book an onsite meeting or request a services?

© Walturn LLC • All Rights Reserved 2024

Our mission is to harness the power of technology to make this world a better place. We provide thoughtful software solutions and consultancy that enhance growth and productivity.

The Jacx Office: 16-120

2807 Jackson Ave

Queens NY 11101, United States

Book an onsite meeting or request a services?

© Walturn LLC • All Rights Reserved 2024

Our mission is to harness the power of technology to make this world a better place. We provide thoughtful software solutions and consultancy that enhance growth and productivity.

The Jacx Office: 16-120

2807 Jackson Ave

Queens NY 11101, United States

Book an onsite meeting or request a services?

© Walturn LLC • All Rights Reserved 2024

Our mission is to harness the power of technology to make this world a better place. We provide thoughtful software solutions and consultancy that enhance growth and productivity.

The Jacx Office: 16-120

2807 Jackson Ave

Queens NY 11101, United States

Book an onsite meeting or request a services?

© Walturn LLC • All Rights Reserved 2024

Our mission is to harness the power of technology to make this world a better place. We provide thoughtful software solutions and consultancy that enhance growth and productivity.

The Jacx Office: 16-120

2807 Jackson Ave

Queens NY 11101, United States

Book an onsite meeting or request a services?

© Walturn LLC • All Rights Reserved 2024