CLI tool for generating Dart/Flutter API clients from OpenAPI specifications
npm install dorval

CLI tool for generating type-safe Dart/Flutter API clients from OpenAPI specifications.
- šÆ Type-safe API clients - Generate strongly-typed Dart code from OpenAPI specs
- āļø Freezed models - Immutable data classes with copyWith, equality, and more
- š JSON serialization - Built-in fromJson/toJson with json_serializable
- š Multiple HTTP clients - Support for Dio (more clients coming soon)
- š Full OpenAPI 3.0 support - Handle complex schemas, references, and more
- šØ Highly configurable - Control every aspect of code generation
- ā” Fast generation - Optimized for large APIs
- š Smart header consolidation - Automatically reduces duplicate header classes
``bashInstall globally
npm install -g dorval
Quick Start
`bash
Generate from a local file
dorval generate -i ./openapi.yaml -o ./lib/apiGenerate from a URL
dorval generate -i https://petstore.swagger.io/v2/swagger.json -o ./lib/apiUsing configuration file (recommended)
dorval generate -c ./dorval.config.ts
`Configuration Guide
$3
Create a
dorval.config.ts (or .js, .json) file:`typescript
export default {
petstore: {
input: './petstore.yaml', // Local file, URL, or OpenAPI object
output: {
target: './lib/api', // Output directory
mode: 'split', // File organization: 'single' | 'split' | 'tags'
client: 'dio', // HTTP client (currently only 'dio' is supported)
override: {
generator: {
freezed: true, // Generate Freezed models (default: true)
jsonSerializable: true, // Add JSON serialization (default: true)
nullSafety: true, // Enable null safety (default: true)
partFiles: true, // Generate part files (default: true)
equatable: false // Add Equatable support (default: false)
},
methodNaming: 'operationId' // 'operationId' | 'methodPath'
}
},
hooks: {
afterAllFilesWrite: 'dart format .' // Commands to run after generation
}
}
};
`$3
`typescript
export default {
apiName: { // You can have multiple APIs in one config // INPUT OPTIONS
input: {
target: './path/to/openapi.yaml', // File path or URL
// OR provide OpenAPI spec directly:
// target: { openapi: '3.0.0', info: {...}, paths: {...} }
},
// OUTPUT OPTIONS
output: {
target: './lib/generated/api', // Output directory
mode: 'split', // File organization
// 'single' - All code in one file
// 'split' - Separate models and services (default)
// 'tags' - Group by OpenAPI tags
client: 'dio', // HTTP client library (currently only 'dio')
override: {
// Generator options
generator: {
freezed: true, // Generate Freezed models
jsonSerializable: true, // Add JSON serialization
nullSafety: true, // Enable null safety
partFiles: true, // Generate part files
equatable: false, // Add Equatable support
copyWith: true, // Generate copyWith methods
toString: true, // Generate toString methods
equality: true // Generate equality operators
},
// Method naming strategy
methodNaming: 'operationId', // How to name service methods
// 'operationId' - Use OpenAPI operationId (default)
// 'methodPath' - Generate from HTTP method + path
// Dio-specific options (future enhancement)
dio: {
baseUrl: 'https://api.example.com', // Override base URL
interceptors: ['AuthInterceptor'] // Custom interceptors
}
}
},
// POST-GENERATION HOOKS
hooks: {
afterAllFilesWrite: 'dart format .' // Commands to run after generation
// Can also be an array: ['dart format .', 'dart analyze']
}
}
};
`$3
`typescript
export default {
// User API
userApi: {
input: './specs/user-api.yaml',
output: {
target: './lib/api/user',
client: 'dio'
}
}, // Admin API with different settings
adminApi: {
input: './specs/admin-api.yaml',
output: {
target: './lib/api/admin',
client: 'dio',
override: {
methodNaming: 'methodPath',
generator: {
freezed: true,
equatable: true // Admin API uses Equatable
}
}
}
},
// Public API from URL
publicApi: {
input: 'https://api.example.com/public/openapi.json',
output: {
target: './lib/api/public',
mode: 'tags' // Group by tags
}
}
};
`$3
`bash
dorval generate [options]
`Options:
-
-i, --input - Path or URL to OpenAPI specification
- -o, --output - Output directory for generated code
- -c, --config - Path to configuration file
- --client - HTTP client type (currently only 'dio')
- -h, --help - Display help
- -V, --version - Display versionMethod Naming Strategies
Control how generated method names look with the
methodNaming option:$3
Uses the
operationId field from your OpenAPI specification:`yaml
OpenAPI spec
paths:
/pets/{id}:
get:
operationId: showPetByIdGenerated Dart method
Future showPetById(String id);
`$3
Generates descriptive names from HTTP method and path:
`yaml
OpenAPI spec
paths:
/pets/{id}:
get: ...
/users/{userId}/settings:
post: ...
/v1/locations/{locationId}/settings:
put: ...
Generated Dart methods
Future getPetsById(String id);
Future postUsersByUserIdSettings(String userId, SettingsDto body);
Future putV1LocationsLocationIdSettings(String locationId, SettingsDto body);
`Generated File Structure
`
lib/api/
āāā api_client.dart # HTTP client wrapper
āāā models/ # Data models
ā āāā user.f.dart # Freezed model
ā āāā user.f.freezed.dart # Generated Freezed code
ā āāā user.f.g.dart # Generated JSON serialization
ā āāā params/ # Request parameter models (if needed)
ā ā āāā get_users_params.f.dart
ā ā āāā index.dart
ā āāā headers/ # Header parameter models (if needed)
ā ā āāā auth_headers.f.dart
ā ā āāā index.dart
ā āāā index.dart # Barrel exports
āāā services/ # API services
āāā users_service.dart # Service implementation
āāā api_exception.dart # Error handling
āāā index.dart # Barrel exports
`Flutter/Dart Setup
After generating the API client, set up your Flutter project:
$3
`yaml
pubspec.yaml
dependencies:
dio: ^5.0.0
freezed_annotation: ^3.0.0
json_annotation: ^4.8.1dev_dependencies:
build_runner: ^2.4.0
freezed: ^3.0.0
json_serializable: ^6.7.0
`$3
`bash
flutter pub get
flutter pub run build_runner build --delete-conflicting-outputs
`$3
`dart
import 'package:dio/dio.dart';
import 'api/api_client.dart';
import 'api/services/users_service.dart';
import 'api/models/user.f.dart';void main() async {
// Initialize client
final apiClient = ApiClient(
dio: Dio(),
baseUrl: 'https://api.example.com',
);
// Create service
final usersService = UsersService(apiClient);
// Make type-safe API calls
final List users = await usersService.getUsers(
limit: 10,
offset: 0,
);
// Handle errors
try {
final user = await usersService.getUserById('123');
print('User name: ${user.name}');
} on ApiException catch (e) {
print('API Error: ${e.message}');
print('Status Code: ${e.statusCode}');
}
}
`Integration Examples
$3
`json
{
"scripts": {
"generate": "dorval generate",
"generate:watch": "dorval watch -c dorval.config.ts",
"prebuild": "npm run generate"
}
}
`$3
`yaml
GitHub Actions
- name: Generate API Client
run: |
npm install -g dorval
dorval generate -c ./dorval.config.tsGitLab CI
generate-api:
script:
- npx dorval generate -i $API_SPEC_URL -o ./lib/api
`$3
`typescript
// dorval.config.ts
export default {
api: {
input: process.env.API_SPEC_URL || './openapi.yaml',
output: {
target: './lib/api',
override: {
dio: {
baseUrl: process.env.API_BASE_URL || 'https://api.example.com'
}
}
}
}
};
`Advanced Usage
$3
`dart
// lib/interceptors/auth_interceptor.dart
class AuthInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
options.headers['Authorization'] = 'Bearer $token';
super.onRequest(options, handler);
}
}// Use in your app
final dio = Dio()..interceptors.add(AuthInterceptor());
final apiClient = ApiClient(dio: dio);
`$3
`dart
try {
final result = await service.someApiCall();
} on ApiException catch (e) {
switch (e.statusCode) {
case 401:
// Handle unauthorized
break;
case 404:
// Handle not found
break;
default:
// Handle other errors
}
}
`$3
`dart
// test/mocks/mock_users_service.dart
class MockUsersService implements UsersService {
@override
Future> getUsers({int? limit, int? offset}) async {
return [
User(id: '1', name: 'Test User'),
];
}
}
`Troubleshooting
$3
Generated methods return
Map instead of models
- Ensure your OpenAPI spec uses $ref for response schemas
- Check that models are defined in components/schemasDuplicate method names in services
- Use unique
operationId values in your OpenAPI spec
- Or switch to methodNaming: 'methodPath' for automatic unique namesImport errors in generated Dart code
- Run
flutter pub get after generation
- Run flutter pub run build_runner build
- Ensure all dependencies are in pubspec.yaml"Cannot find module '@dorval/core'" error
- Run
npm install in your project
- Ensure @dorval/core is installed as a dependency$3
Set environment variable for verbose output:
`bash
DEBUG=dorval* dorval generate -c dorval.config.ts
`Comparison with Other Tools
| Feature | dorval | OpenAPI Generator | Swagger Codegen |
|---------|--------|-------------------|-----------------|
| Dart/Flutter Focus | ā
Native | ā ļø Generic | ā ļø Generic |
| Freezed Support | ā
Built-in | ā Manual | ā Manual |
| TypeScript Config | ā
Yes | ā Java/CLI | ā Java/CLI |
| Method Naming Control | ā
Yes | ā ļø Limited | ā ļø Limited |
| NPM Package | ā
Yes | ā Docker/JAR | ā Docker/JAR |
| Bundle Size | ā
~5MB | ā ~100MB+ | ā ~100MB+ |
| Header Consolidation | ā
Smart | ā No | ā No |
Migration Guide
$3
1. Install dorval:
npm install -g dorval
2. Create dorval.config.ts with your settings
3. Run dorval generate
4. Update imports in your Dart code$3
1. Convert your config to dorval format
2. Replace JAR execution with
dorval generate`We welcome contributions! See CONTRIBUTING.md.
- š Documentation
- š Report Issues
- š¬ Discussions
MIT Ā© 2025