Building Scalable APIs with Dart and Shelf - Part 2: A Step-by-Step Guide

In this guide, we'll explore how to build scalable APIs using Dart and Shelf. We'll cover everything from setting up your development environment to deploying a production-ready API.
Building Scalable APIs with Dart and Shelf - Part 2: A Step-by-Step Guide

In the first part of this tutorial series, we looked at how to build scalable API with Dart and covered a few aspects:

  • Setting up the development environment
  • Creating your first API endpoint
  • Building and organizing your API
  • Integrating with database

In the second part, we'll look at how to test the API we have built and deploy it. We'll also highlight a few best practices.

Testing Your API

Testing your API is a critical step to ensure that it functions as expected and can handle different scenarios. In this section, we’ll explore how to test the API using both manual tools like Postman or HTTPie, as well as automated tests with Dart's built-in test package.

Manual Testing with Postman

Follow the steps below to test your API endpoint with Postman:

Install Postman:

Download and install Postman from Postman’s official website.

Send a Request

  • Open Postman and create a new request.
  • Choose the method GET and enter the endpoint URL, e.g., http://localhost:8080/user.
  • Add the required headers, such as:
{
  "Authorization": "Bearer test-token"
}
  • Send the request and observe the response.
  • Expected Response should look like this:
[{id: 1, name: Alice, email: alice@example.com, created_at: 2025-01-17 01:16:22.766942Z}, {id: 2, name: Bob, email: bob@example.com, created_at: 2025-01-17 01:16:22.766942Z}, {id: 3, name: Charlie, email: charlie@example.com, created_at: 2025-01-17 01:16:22.766942Z}]
  • GET /user without Authorization Header:
Unauthorized

Unit Testing with the Test Package

Unit testing ensures that the API endpoints, middleware, and other functionality work as intended in isolation. In this section, we’ll write and organize tests to verify the behavior of our API.

Setting Up the Test Environment

Before writing tests, ensure the following setup:

  • Add Test Dependencies: Add the test package to your pubspec.yaml:
dev_dependencies:
  test: ^1.25.14

Run dart pub get to install the package.

  • Directory for Tests: Create a test directory in the project root:
my_api/
├── test/
│   ├── user_router_test.dart
│   ├── product_router_test.dart
│   └── auth_middleware_test.dart

Writing Unit Tests

Each unit test targets a specific part of the API, such as routes, middleware, or database interactions.

  1. Testing User Router

Filename: test/user_router_test.dart

import 'package:test/test.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart';
import 'package:my_api/routers/user_router.dart';

void main() {
  group('User Router Tests', () {
    final handler = Pipeline().addHandler(userRouter);

    test('GET /user returns all users', () async {
      final request = Request('GET', Uri.parse('/user'));
      final response = await handler(request);

      expect(response.statusCode, equals(200));
      final body = await response.readAsString();
      expect(body, contains('id')); // Assuming the response includes user IDs
    });

    test('GET /user with invalid path returns 404', () async {
      final request = Request('GET', Uri.parse('/invalid'));
      final response = await handler(request);

      expect(response.statusCode, equals(404));
    });
  });
}
  1. Testing Authorization Middleware

Filename: test/auth_middleware_test.dart

import 'package:test/test.dart';
import 'package:shelf/shelf.dart';
import 'package:my_api/middleware/auth_middleware.dart';

void main() {
  group('Auth Middleware Tests', () {
    final handler = Pipeline()
        .addMiddleware(authMiddleware())
        .addHandler((Request request) => Response.ok('Authorized'));

    test('Request with valid Authorization header passes', () async {
      final request = Request(
        'GET',
        Uri.parse('/'),
        headers: {'Authorization': 'Bearer test-token'},
      );
      final response = await handler(request);

      expect(response.statusCode, equals(200));
      expect(await response.readAsString(), equals('Authorized'));
    });

    test('Request without Authorization header fails', () async {
      final request = Request('GET', Uri.parse('/'));
      final response = await handler(request);

      expect(response.statusCode, equals(403));
      expect(await response.readAsString(), equals('Unauthorized'));
    });
  });
}
  1. Testing Database Access

You can mock the database or use a test database to test database-related functionality. Example file: test/user_dal_test.dart

import 'package:test/test.dart';
import 'package:mockito/mockito.dart';
import 'package:my_api/data/user_dal.dart';

class MockDatabaseConnection extends Mock implements UserDAL {}

void main() {
  group('User DAL Tests', () {
    final mockDb = MockDatabaseConnection();

    test('getAllUsers returns a list of users', () async {
      when(mockDb.getAllUsers()).thenAnswer((_) async => [
            {'id': 1, 'name': 'John Doe', 'email': 'john.doe@example.com'}
          ]);

      final users = await mockDb.getAllUsers();
      expect(users.length, equals(1));
      expect(users[0]['name'], equals('John Doe'));
    });

    test('getUserById returns a user if found', () async {
      when(mockDb.getUserById(1)).thenAnswer((_) async => {
            'id': 1,
            'name': 'John Doe',
            'email': 'john.doe@example.com'
          });

      final user = await mockDb.getUserById(1);
      expect(user?['name'], equals('John Doe'));
    });
  });
}

Running the Tests

Run all tests using the following command:

dart test

About the author
Ini Arthur

Dart Code Labs

Dart Code Labs - explore content on Dart backend development, authentication, microservices, and server frameworks with expert guides and insights!

Dart Code Labs

Great! You’ve successfully signed up.

Welcome back! You've successfully signed in.

You've successfully subscribed to Dart Code Labs.

Success! Check your email for magic link to sign-in.

Success! Your billing info has been updated.

Your billing was not updated.