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
Homebrew (macOS)
Manual download
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
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
Run the generation script
From the root of the repository, run the generation script: ./generate-openapi-client.sh
This will:
Generate Dart code from api/openapi.json
Output to the openapi/ directory
Run build_runner to generate serialization code
Verify the generated client
Check that the client was generated successfully: 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:
Update API endpoints
Modify the NestJS controllers and DTOs in the api/src/ directory.
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 ) {
// ...
}
}
Regenerate the client
Run the generation script: ./generate-openapi-client.sh
Update Flutter app
Update the Flutter app to use the new client methods and models.
Test changes
Run Flutter tests to ensure compatibility:
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
Regenerate after API changes - Always regenerate the client after modifying API endpoints
Commit generated code - Include the generated client in version control
Use type-safe models - Leverage Dart’s type system with the generated models
Keep in sync - Ensure the Flutter app uses the latest client version
Document custom changes - If you manually modify generated code, document why (though this is discouraged)