DEV Community

Cover image for How to Connect REST APIs in Flutter: A Practical Guide for Beginners
Tech Tales
Tech Tales

Posted on

How to Connect REST APIs in Flutter: A Practical Guide for Beginners

How to Connect REST APIs in Flutter: A Practical Guide for Beginners

Modern mobile applications rarely work in isolation. Whether you're building an e-commerce app, social platform, task manager, or job portal, your Flutter application usually needs to communicate with a backend server.

This is where REST APIs come in.

In this guide, we'll explore how REST APIs work, how to make API requests in Flutter, how to parse JSON responses, and how to structure your networking code for a scalable application.

If you're new to Flutter API integration, this article will give you a solid foundation.

What Is a REST API?

A REST API (Representational State Transfer Application Programming Interface) allows different applications to communicate over the internet using HTTP.

For example, a Flutter application might request a list of users from a backend:

GET https://jsonplaceholder.typicode.com/users
Enter fullscreen mode Exit fullscreen mode

The server processes the request and returns data, commonly in JSON format:

[
  {
    "id": 1,
    "name": "Leanne Graham",
    "email": "leanne@example.com"
  }
]
Enter fullscreen mode Exit fullscreen mode

The Flutter application then converts this JSON data into Dart objects and displays it in the UI.

The overall process looks like this:

Flutter App
    ↓
HTTP Request
    ↓
REST API
    ↓
Backend Server
    ↓
Database
    ↓
JSON Response
    ↓
Flutter App
Enter fullscreen mode Exit fullscreen mode

Setting Up API Integration in Flutter

One of the simplest ways to make HTTP requests in Flutter is by using the http package.

Add it to your pubspec.yaml:

dependencies:
  flutter:
    sdk: flutter
  http: ^1.2.0
Enter fullscreen mode Exit fullscreen mode

Then run:

flutter pub get
Enter fullscreen mode Exit fullscreen mode

Import the required packages:

import 'dart:convert';
import 'package:http/http.dart' as http;
Enter fullscreen mode Exit fullscreen mode

Here:

  • http is used to send HTTP requests.
  • dart:convert is used to decode and encode JSON data.

Create a Model Class

Instead of working with raw JSON or dynamic values throughout your application, create a Dart model.

For example:

class User {
  final int id;
  final String name;
  final String email;

  User({
    required this.id,
    required this.name,
    required this.email,
  });

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'],
      name: json['name'],
      email: json['email'],
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Using models provides several benefits:

  • Better type safety
  • Cleaner code
  • Easier debugging
  • Better IDE support
  • Easier maintenance as the project grows

Create an API Service

A good Flutter project should avoid putting API calls directly inside widgets.

Instead, create a separate service responsible for communicating with the backend.

import 'dart:convert';
import 'package:http/http.dart' as http;

class ApiService {
  Future<List<User>> getUsers() async {
    final url = Uri.parse(
      'https://jsonplaceholder.typicode.com/users',
    );

    final response = await http
        .get(url)
        .timeout(const Duration(seconds: 10));

    if (response.statusCode == 200) {
      final List data = jsonDecode(response.body);

      return data
          .map((json) => User.fromJson(json))
          .toList();
    }

    throw Exception(
      'Failed to load users (${response.statusCode})',
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Now the UI doesn't need to know the details of the HTTP request.

It simply calls the service and receives the required data.

Display API Data in Flutter

Once the service is ready, call it from your screen.

For example:

@override
void initState() {
  super.initState();
  loadUsers();
}
Enter fullscreen mode Exit fullscreen mode

You can then retrieve the data and update the UI:

setState(() {
  users = data;
  isLoading = false;
});
Enter fullscreen mode Exit fullscreen mode

To display the users:

ListView.builder(
  itemCount: users.length,
  itemBuilder: (context, index) {
    return ListTile(
      title: Text(users[index].name),
      subtitle: Text(users[index].email),
    );
  },
)
Enter fullscreen mode Exit fullscreen mode

This creates a scrollable list using information retrieved from the REST API.

Understanding the API Request Flow

When a Flutter application communicates with a backend, the process generally looks like this:

User Opens Screen
        ↓
Flutter Calls API Service
        ↓
HTTP Request
        ↓
Backend Processes Request
        ↓
Database Operation
        ↓
JSON Response
        ↓
JSON Decoding
        ↓
Dart Model
        ↓
UI Update
Enter fullscreen mode Exit fullscreen mode

Understanding this flow is important because it helps you identify where problems occur when an API request fails.

HTTP Methods You Should Know

REST APIs commonly use four HTTP methods.

GET — Retrieve Data

GET is used when you want to retrieve information.

final response = await http.get(url);
Enter fullscreen mode Exit fullscreen mode

Common examples include:

  • Fetching users
  • Loading products
  • Getting job listings
  • Retrieving tasks

POST — Create Data

POST is generally used to create a new resource.

final response = await http.post(
  url,
  headers: {
    'Content-Type': 'application/json',
  },
  body: jsonEncode({
    'name': 'John',
    'email': 'john@example.com',
  }),
);
Enter fullscreen mode Exit fullscreen mode

Typical use cases include:

  • User registration
  • Login requests
  • Creating tasks
  • Adding products

PUT — Update Data

PUT can be used to update an existing resource.

final response = await http.put(
  url,
  headers: {
    'Content-Type': 'application/json',
  },
  body: jsonEncode({
    'name': 'Updated Name',
  }),
);
Enter fullscreen mode Exit fullscreen mode

DELETE — Remove Data

DELETE is used to remove a resource.

final response = await http.delete(url);
Enter fullscreen mode Exit fullscreen mode

Handling Loading, Success, Empty, and Error States

A production Flutter application shouldn't assume that every API request will succeed.

You should handle at least these states:

Loading

Show a progress indicator while waiting for the API:

const CircularProgressIndicator()
Enter fullscreen mode Exit fullscreen mode

Success

Display the retrieved data.

Empty

If the API returns no records, show an appropriate message:

const Text('No users found')
Enter fullscreen mode Exit fullscreen mode

Error

If the request fails, provide a useful message instead of leaving the user with a blank screen.

For example:

try {
  final data = await apiService.getUsers();

  setState(() {
    users = data;
    isLoading = false;
  });
} catch (e) {
  setState(() {
    errorMessage = 'Unable to load users';
    isLoading = false;
  });
}
Enter fullscreen mode Exit fullscreen mode

Good state handling makes an application feel much more reliable.

Understanding HTTP Status Codes

Knowing HTTP status codes helps Flutter developers handle API responses correctly.

Status Code Meaning
200 Request successful
201 Resource created
204 Request successful with no content
400 Bad request
401 Unauthorized
403 Forbidden
404 Resource not found
500 Internal server error

For example, a 401 response may indicate that the user's authentication token is missing or invalid.

A 404 usually means that the requested resource could not be found.

Handling Authentication

Many real-world APIs require authentication.

A common approach is to send an access token in the request headers:

final response = await http.get(
  url,
  headers: {
    'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
    'Content-Type': 'application/json',
  },
);
Enter fullscreen mode Exit fullscreen mode

For production applications, avoid hardcoding sensitive credentials directly into your source code.

Depending on your application, authentication tokens may need to be securely stored and refreshed when necessary.

API Integration Best Practices

As your Flutter application becomes larger, API code can quickly become difficult to maintain if everything is placed inside widgets.

Consider following these practices:

  • Keep API calls separate from UI code.
  • Create model classes for API responses.
  • Use HTTPS for API communication.
  • Validate HTTP status codes.
  • Add request timeouts.
  • Handle loading, success, empty, and error states.
  • Avoid hardcoding sensitive credentials.
  • Keep API endpoints centralized.
  • Use repositories for larger applications.
  • Use interceptors when you need centralized authentication or logging.
  • Consider libraries such as Dio when your networking requirements become more advanced.

A Scalable Flutter Project Structure

For a small application, a simple service-based structure may be enough.

As your application grows, you can organize the project like this:

lib/
│
├── core/
│
├── models/
│
├── services/
│
├── repositories/
│
├── providers/
│
├── screens/
│
├── widgets/
│
└── main.dart
Enter fullscreen mode Exit fullscreen mode

This separation makes it easier to maintain and test individual parts of the application.

For larger projects, you can introduce additional layers such as:

UI
 ↓
State Management
 ↓
Repository
 ↓
API Service
 ↓
REST API
Enter fullscreen mode Exit fullscreen mode

This approach helps keep business logic, networking, and presentation responsibilities separate.

http Package vs Dio

The http package is a great starting point for beginners and smaller applications.

However, larger applications may benefit from Dio, which provides additional networking capabilities such as:

  • Interceptors
  • Request cancellation
  • Form-data support
  • File uploads
  • Request configuration
  • Centralized error handling

You don't need to use Dio for every project. Start with the http package and move to a more feature-rich networking solution when your project requirements demand it.

Common Mistakes Beginners Should Avoid

When integrating APIs in Flutter, beginners often run into a few common problems.

Putting API Calls Directly in Widgets

This can make your UI code difficult to maintain.

Better approach: move networking logic into services or repositories.

Ignoring API Errors

Don't assume that every response will have a 200 status code.

Always check the response and handle possible failures.

Using dynamic Everywhere

Using models provides better type safety and makes your code easier to understand.

Not Adding Timeouts

A server may take too long to respond.

Using a timeout prevents your application from waiting indefinitely.

Hardcoding Sensitive Information

Avoid placing API keys, secrets, or credentials directly in your source code.

Final Thoughts

REST API integration is one of the most important skills for Flutter developers.

Once you understand how to:

  • Send HTTP requests
  • Work with JSON
  • Create Dart models
  • Handle API responses
  • Manage loading and error states
  • Separate networking from UI
  • Structure your application properly

you'll have a strong foundation for building real-world Flutter applications.

Start with simple APIs, practice GET, POST, PUT, and DELETE requests, and gradually explore authentication, repositories, state management, and advanced networking libraries.

The more you practice API integration, the easier it becomes to connect your Flutter applications with real backend systems.

Have you integrated REST APIs in a Flutter project? Share your experience or challenges in the comments.

Top comments (0)