OpenAPI
Spark can automatically generate OpenAPI 3.0 specifications from your endpoints. Use annotations to document your API and export a complete spec file.
Usage
Generate an OpenAPI specification with the CLI:
spark openapiOptions:
- `--output, -o <path>` - Output file path (default: `openapi.json`)
Global Configuration
Configure API metadata using the `@OpenApi` annotation on your main function:
@OpenApi(
title: 'My API',
version: '1.0.0',
description: 'API description',
servers: ['https://api.example.com'],
securitySchemes: {
'BearerAuth': SecurityScheme.http(
scheme: 'bearer',
bearerFormat: 'JWT',
),
},
)
void main() {
// ...
}Endpoint Documentation
Document individual endpoints using the `@Endpoint` annotation:
@Endpoint(
path: '/api/users/{id}',
method: 'GET',
summary: 'Get user by ID',
description: 'Retrieves a user by their unique identifier.',
tags: ['Users'],
statusCode: 200, // Happy path status code
contentTypes: ['application/json'], // Supported content types
)
class GetUserEndpoint extends SparkEndpoint {
@override
Future<UserDto> handler(SparkRequest request) async {
// Response type (UserDto) is automatically deduced from return type
return UserDto(name: 'John Doe');
}
}The `statusCode` field specifies the HTTP status code for successful responses (defaults to 200). The `contentTypes` field specifies supported content types (defaults to `application/json`).
Automatic Response Deduction
Spark automatically deduces the response type from your handler's return type. You don't need to manually define responses in the annotation.
// Return type is automatically used for OpenAPI response schema
class GetUserEndpoint extends SparkEndpoint {
@override
Future<UserDto> handler(SparkRequest request) async {
return UserDto(name: 'John Doe'); // UserDto schema generated automatically
}
}
// Works with primitive types too
class HelloEndpoint extends SparkEndpoint {
@override
Future<String> handler(SparkRequest request) async {
return 'Hello World'; // String type in OpenAPI spec
}
}Supported return types include `String`, `int`, `double`, `bool`, `List<T>`, `Map<String, dynamic>`, and custom classes with public fields.
Automatic Error Responses
Spark automatically adds error responses to the OpenAPI specification when you throw an `ApiError` (or a subclass) in your endpoints or middleware.
For example, if you throw `ApiError(404, "Not found")`, Spark will add a 404 response to the spec. This means you don't need to manually define standard error responses in the annotation.
Automatic Request Body Deduction
Request bodies are automatically deduced when your endpoint extends `SparkEndpointWithBody<T>`. The type parameter `T` is used to generate the request body schema.
@Endpoint(
path: '/api/users',
method: 'POST',
summary: 'Create user',
tags: ['Users'],
statusCode: 201,
contentTypes: ['application/json'],
)
class CreateUserEndpoint extends SparkEndpointWithBody<CreateUserDto> {
@override
Future<UserDto> handler(SparkRequest request, CreateUserDto body) async {
// CreateUserDto is automatically documented as the request body
// UserDto is automatically documented as the response
return UserDto(name: body.name);
}
}You can also use `Map<String, dynamic>` as the body type for dynamic JSON payloads:
@Endpoint(path: '/api/details', method: 'POST')
class EchoDetailsEndpoint extends SparkEndpointWithBody<Map<String, dynamic>> {
@override
Future<Map<String, dynamic>> handler(
SparkRequest request,
Map<String, dynamic> body,
) async {
return body;
}
}Parameters
Document query, header, and path parameters using the `parameters` field:
@Endpoint(
summary: 'List users',
parameters: [
Parameter(
name: 'page',
inLocation: 'query',
description: 'Page number',
required: false,
schema: {'type': 'integer'},
),
Parameter(
name: 'limit',
inLocation: 'query',
description: 'Items per page',
schema: {'type': 'integer'},
),
Parameter(
name: 'Authorization',
inLocation: 'header',
description: 'Bearer token',
required: true,
),
],
// ...
)Valid `inLocation` values: `query`, `header`, `path`, `cookie`. Path parameters (like `{id}`) are automatically detected from the route.
Security Schemes
Define authentication methods in the global configuration:
Bearer Token
securitySchemes: {
'BearerAuth': SecurityScheme.http(
scheme: 'bearer',
bearerFormat: 'JWT',
),
}API Key
securitySchemes: {
'ApiKey': SecurityScheme.apiKey(
name: 'X-API-Key',
inLocation: 'header', // 'header', 'query', or 'cookie'
),
}OAuth2
securitySchemes: {
'OAuth2': SecurityScheme.oauth2(
flows: {
'authorizationCode': SecuritySchemeFlow(
authorizationUrl: 'https://auth.example.com/authorize',
tokenUrl: 'https://auth.example.com/token',
scopes: {
'read:users': 'Read user data',
'write:users': 'Modify user data',
},
),
},
),
}OpenID Connect
securitySchemes: {
'OpenID': SecurityScheme.openIdConnect(
openIdConnectUrl: 'https://auth.example.com/.well-known/openid-configuration',
),
}Apply security to endpoints using the `security` field:
@Endpoint(
summary: 'Get current user',
security: [{'BearerAuth': []}],
// ...
)Schema Generation
Dart types are automatically mapped to OpenAPI schemas:
- `String` → `string`
- `int` → `integer`
- `double` → `number`
- `bool` → `boolean`
- `List<T>` → `array` of T
- `Map<String, T>` → `object` with additionalProperties of T
- `DateTime` → `string` with format `date-time`
- Custom classes → `object` with properties introspected from public fields
Custom classes are automatically introspected via their public fields. No `toJson()` method is required.
// This class will generate an OpenAPI schema automatically
class UserDto {
final String name;
final int age;
final bool isActive;
UserDto({required this.name, required this.age, required this.isActive});
}Complete Example
// main.dart
@OpenApi(
title: 'User Service API',
version: '1.0.0',
description: 'Manage users and authentication',
servers: ['https://api.example.com/v1'],
securitySchemes: {
'BearerAuth': SecurityScheme.http(
scheme: 'bearer',
bearerFormat: 'JWT',
),
},
)
// import 'spark_router.g.dart';
void main() async {
final server = await createSparkServer(SparkServerConfig(port: 8080));
print('Running at http://localhost:${server.port}');
}
// DTOs - schemas are generated from public fields
class UserDto {
final String name;
UserDto({required this.name});
}
class CreateUserDto {
final String name;
final String email;
CreateUserDto({required this.name, required this.email});
}
// GET endpoint - response type deduced from return type
@Endpoint(
path: '/api/users/{id}',
method: 'GET',
summary: 'Get user by ID',
description: 'Retrieves a user by their unique identifier.',
tags: ['Users'],
security: [{'BearerAuth': []}],
statusCode: 200,
)
class GetUserEndpoint extends SparkEndpoint {
@override
List<Middleware> get middleware => [AuthMiddleware()];
@override
Future<UserDto> handler(SparkRequest request) async {
final id = request.pathParams['id'];
final user = await userService.findById(id);
if (user == null) {
throw NotFoundException('User not found'); // Adds 404 to spec
}
return UserDto(name: user.name); // UserDto schema auto-generated
}
}
// POST endpoint - request body deduced from SparkEndpointWithBody<T>
@Endpoint(
path: '/api/users',
method: 'POST',
summary: 'Create user',
tags: ['Users'],
security: [{'BearerAuth': []}],
statusCode: 201,
contentTypes: ['application/json'],
)
class CreateUserEndpoint extends SparkEndpointWithBody<CreateUserDto> {
@override
Future<UserDto> handler(SparkRequest request, CreateUserDto body) async {
// CreateUserDto auto-documented as request body
// UserDto auto-documented as response
final user = await userService.create(body);
return UserDto(name: user.name);
}
}Run `spark openapi` to generate the specification file, then use tools like Swagger UI or Redoc to view the interactive documentation.