Skip to main content

Overview

The Aero Flutter app uses an auto-generated Dart API client that is created from the NestJS backend’s OpenAPI specification. This ensures type safety and consistency between the backend and mobile app.

Architecture

┌─────────────────────────────────────────┐
│         NestJS API Server               │
│  (Swagger decorators generate spec)     │
└─────────────┬───────────────────────────┘

              │ openapi.json

┌─────────────────────────────────────────┐
│    OpenAPI Generator (dart-dio)         │
│  Generates Dart client from spec        │
└─────────────┬───────────────────────────┘


┌─────────────────────────────────────────┐
│      openapi/ directory                 │
│  (Dart package with API models)         │
│  • API endpoints                        │
│  • Request/response models              │
│  • Serialization/deserialization        │
└─────────────────────────────────────────┘

Prerequisites

Before generating the API client, ensure you have:
  • OpenAPI Generator CLI installed globally
  • Dart SDK 2.18.0 or higher
  • NestJS API server running to generate the OpenAPI spec

Install OpenAPI Generator

npm install -g @openapitools/openapi-generator-cli

Generation script

Aero includes a convenient shell script to automate the client generation process.

Script location

The generation script is located at the root of the repository:
./generate-openapi-client.sh

Script contents

#!/bin/bash

set -e

# Generate the OpenAPI client
echo "Generating the OpenAPI client..."
openapi-generator generate -i ./api/openapi.json -g dart-dio -o ./openapi --skip-validate-spec

# Build the client
echo "Building the OpenAPI client..."
cd openapi
dart run build_runner build --delete-conflicting-outputs

# Done
echo "Done!"
The --skip-validate-spec flag bypasses strict OpenAPI validation, which is useful during development when the spec may not be perfectly compliant.

Generating the client

1

Generate OpenAPI specification

First, ensure the NestJS API server is running. The API automatically generates the OpenAPI specification at api/openapi.json:
cd api
pnpm run start:dev
The OpenAPI spec is available at:
  • JSON: http://localhost:3000/docs-json
  • Swagger UI: http://localhost:3000/docs
  • Scalar UI: http://localhost:3000/reference
2

Run the generation script

From the root of the repository, run the generation script:
./generate-openapi-client.sh
This will:
  1. Generate Dart code from api/openapi.json
  2. Output to the openapi/ directory
  3. Run build_runner to generate serialization code
3

Verify the generated client

Check that the client was generated successfully:
ls openapi/lib/src/
You should see:
  • api/ - API endpoint classes
  • model/ - Request/response models
  • serializers.dart - Built value serializers

Using the generated client

The generated client is used throughout the Flutter app:

Import the client

import 'package:openapi/openapi.dart';

Initialize the client

final dio = Dio();
final api = Openapi(
  dio: dio,
  basePathOverride: 'http://localhost:3000',
);

Make API calls

// Authentication
final authApi = api.getAuthenticationApi();
final loginResponse = await authApi.login(
  loginDto: LoginDto(
    email: '[email protected]',
    password: 'password123',
  ),
);

// Get flight information
final flightApi = api.getFlightApi();
final flight = await flightApi.getFlightById(
  id: 'AA100',
);

// Search airports
final airportsApi = api.getAirportsApi();
final airports = await airportsApi.searchAirports(
  query: 'JFK',
);

Client structure

The generated client follows this structure:
openapi/
├── lib/
│   ├── src/
│   │   ├── api/
│   │   │   ├── authentication_api.dart
│   │   │   ├── flight_api.dart
│   │   │   ├── flights_api.dart
│   │   │   ├── airports_api.dart
│   │   │   ├── airlines_api.dart
│   │   │   ├── profile_api.dart
│   │   │   └── app_api.dart
│   │   ├── model/
│   │   │   ├── login_dto.dart
│   │   │   ├── register_dto.dart
│   │   │   ├── flight_entity.dart
│   │   │   ├── airport_entity.dart
│   │   │   └── ... (many more models)
│   │   ├── serializers.dart
│   │   └── serializers.g.dart
│   └── openapi.dart
├── test/
│   └── ... (generated tests)
├── pubspec.yaml
└── README.md

Configuration

The client is configured through openapi/pubspec.yaml:
name: openapi
version: 1.0.0
description: OpenAPI API client

environment:
  sdk: '>=2.18.0 <4.0.0'

dependencies:
  dio: '^5.7.0'
  one_of: '>=1.5.0 <2.0.0'
  one_of_serializer: '>=1.5.0 <2.0.0'
  built_value: '>=8.4.0 <9.0.0'
  built_collection: '>=5.1.1 <6.0.0'

dev_dependencies:
  built_value_generator: '>=8.4.0 <9.0.0'
  build_runner: any
  test: '^1.16.0'

Regeneration workflow

When you make changes to the API:
1

Update API endpoints

Modify the NestJS controllers and DTOs in the api/src/ directory.
2

Verify OpenAPI annotations

Ensure your endpoints have proper Swagger decorators:
@ApiTags('flights')
@Controller('flights')
export class FlightsController {
  @Get(':id')
  @ApiOperation({ summary: 'Get flight by ID' })
  @ApiResponse({ status: 200, type: FlightEntity })
  async getFlightById(@Param('id') id: string) {
    // ...
  }
}
3

Regenerate the client

Run the generation script:
./generate-openapi-client.sh
4

Update Flutter app

Update the Flutter app to use the new client methods and models.
5

Test changes

Run Flutter tests to ensure compatibility:
cd app
flutter test
Commit the generated client code to version control so all developers have access to the latest API definitions.

Troubleshooting

Generator not found

If you see openapi-generator: command not found:
npm install -g @openapitools/openapi-generator-cli

Build runner fails

If dart run build_runner build fails:
cd openapi
dart pub get
dart run build_runner clean
dart run build_runner build --delete-conflicting-outputs

Spec validation errors

If the OpenAPI spec has validation issues, use --skip-validate-spec (already included in the script) or fix the spec by updating Swagger decorators in the API.

Conflicting outputs

The script includes --delete-conflicting-outputs to automatically resolve conflicts. If you still have issues:
cd openapi
rm -rf lib/src/model/*.g.dart
dart run build_runner build --delete-conflicting-outputs

Best practices

  1. Regenerate after API changes - Always regenerate the client after modifying API endpoints
  2. Commit generated code - Include the generated client in version control
  3. Use type-safe models - Leverage Dart’s type system with the generated models
  4. Keep in sync - Ensure the Flutter app uses the latest client version
  5. Document custom changes - If you manually modify generated code, document why (though this is discouraged)

Build docs developers (and LLMs) love