Skip to content

Generate API clients

You will write a small script that builds the spec and saves it as openapi.json, then generate a typed client from it. Exporting from a script (rather than fetching from a running server) lets CI regenerate clients on every commit.

  • A gisila_doc API whose controllers have operationId set. Generators name client methods after operationId; gisila_doc only emits it when you set it on the verb annotation.
  • A client generator, for example OpenAPI Generator (npx @openapitools/openapi-generator-cli) or any tool that reads OpenAPI 3.1.
@Get('/{id}', summary: 'Fetch a user', operationId: 'getUser')
Future<User> getUser(String id, UserService users) => users.getById(id);

Put spec construction in a function both the server and the export script use:

lib/api_spec.dart
import 'package:gisila_doc/gisila_doc.dart';
import 'controllers/users_controller.dart';
OpenApiSpec newSpec() => OpenApiSpec(
info: const ApiInfo(title: 'Users API', version: '1.0.0'),
servers: const [ApiServer(url: 'https://api.example.com')],
);
/// Attaches every controller. Used by the server and by tool/export_openapi.dart.
void attachAll(GisilaApp app, Router router, OpenApiSpec spec, {String prefix = ''}) {
UsersController().attachToApp(app, router, spec, prefix: prefix);
}

attachToApp fills the spec as it registers routes, so you can call it on a throwaway Router without starting a server:

tool/export_openapi.dart
import 'dart:io';
import 'package:gisila_doc/gisila_doc.dart';
import 'package:users_api/api_spec.dart';
void main(List<String> args) {
final spec = newSpec();
attachAll(GisilaApp(config: AppConfig()), Router(), spec);
final out = args.isEmpty ? 'openapi.json' : args.first;
File(out).writeAsStringSync(spec.toJson());
stdout.writeln('Wrote $out');
}
Terminal window
dart run build_runner build
dart run tool/export_openapi.dart build/openapi.json
Terminal window
# TypeScript (fetch)
npx @openapitools/openapi-generator-cli generate \
-i build/openapi.json -g typescript-fetch -o clients/ts
# Dart (dio)
npx @openapitools/openapi-generator-cli generate \
-i build/openapi.json -g dart-dio -o clients/dart

Check your generator’s OpenAPI 3.1 support: gisila_doc expresses nullability as type: [T, null] (3.1 style), which some older generators do not understand.

Terminal window
curl -s https://api.example.com/openapi.json -o openapi.json

This works when docsHandler is mounted and enabled.

Terminal window
jq '[.paths[][] | .operationId] | map(select(. == null)) | length' build/openapi.json
# 0 -> every operation has an id

The generated client should expose methods such as getUser(id) returning a typed User.