API-documentatie (Swagger / OpenAPI)
Finch heeft een ingebouwd OpenAPI-documentatiesysteem. Je beschrijft elke API-route met ApiDoc, en Finch genereert een machineleesbare OpenAPI 3.1 JSON-spec en een Swagger UI waarmee ontwikkelaars je API vanuit de browser kunnen verkennen en testen.
Het opzetten van API-documentatie vereist drie stappen:
- Maak een
ApiController-instantie aan. - Registreer de documentatieroutes (OpenAPI JSON-output en Swagger UI).
- Koppel
ApiDoc-objecten aan de routes die in de documentatie moeten verschijnen.
Stap 1 — Maak de ApiController aan
ApiController leest alle routes van je app en genereert de OpenAPI-spec:
final apiController = ApiController(
title: 'My App API',
app: app,
security: 'apiKey', // optioneel — naam die wordt getoond voor het bearer-beveiligingsschema, standaard 'apiKey'
);
Stap 2 — Registreer de documentatieroutes
Voeg twee routes toe aan je router: één voor de ruwe OpenAPI JSON en één voor de Swagger UI:
[
// Geeft de OpenAPI JSON-spec terug — gebruikt door Swagger UI en API-clients
FinchRoute(
key: 'root.api.docs',
path: 'api/docs',
index: apiController.indexPublic,
),
// Swagger UI-pagina — geef de URL naar de JSON-spec door
FinchRoute(
key: 'root.swagger',
path: 'swagger',
index: () => apiController.swagger(rq.url('api/docs')),
),
];
Open /swagger in je browser om de interactieve documentatie te zien.
Standaard is de Swagger UI (en het gewone
index()JSON-eindpunt) alleen toegankelijk in local debug-modus (isLocalDebug: true).indexPublic()is een snelkoppeling voorindex(showPublic: true), dus de JSON-specroute hierboven is met opzet openbaar — om ookapiController.swagger(...)openbaar te maken, ongeacht de debug-modus, geef je er explicietshowPublic: trueaan door.
Alleen routes waarvan het pad begint met /api worden opgenomen in de gegenereerde spec, en alleen als ze een apiDoc hebben gekoppeld.
Stap 3 — Definieer ApiDoc voor routes
Elke route kan een ApiDoc hebben die beschrijft wat de route doet, welke parameters/body hij accepteert en welke responses hij teruggeeft. Koppel het aan een FinchRoute via de apiDoc-eigenschap.
FinchRoute(
key: 'api.books.list',
path: 'api/books',
methods: Methods.ONLY_GET,
index: booksController.list,
apiDoc: ApiDoc(
get: ApiDoc(
description: 'Returns a paginated list of books.',
parameters: [
ApiParameter<int>(
'page',
isRequired: false,
paramIn: ParamIn.query,
def: 1,
),
ApiParameter<int>(
'limit',
isRequired: false,
paramIn: ParamIn.query,
def: 20,
),
],
response: {
'200': [
ApiResponse<int>('count', def: 0),
ApiResponse<List>('rows', def: []),
],
'404': ApiDocuments.r404,
},
),
),
),
ApiDoc-eigenschappen
ApiDoc kan op routeniveau of per HTTP-methode worden gebruikt. Nest een innerlijke ApiDoc voor elke methode — een parameters/body/response op routeniveau geldt voor elke methode, en die per methode (get/post/put/delete) wordt daar bovenop samengevoegd wanneer Finch de spec opbouwt:
ApiDoc(
get: ApiDoc(description: '...', parameters: [...], response: {...}),
post: ApiDoc(description: '...', parameters: [...], body: [...], response: {...}),
put: ApiDoc(description: '...', parameters: [...], body: [...], response: {...}),
delete: ApiDoc(description: '...', parameters: [...], response: {...}),
)
ApiParameter
ApiParameter<T> beschrijft één invoerparameter die geen deel uitmaakt van de requestbody — padsegmenten, query-strings, headers of cookies:
| Eigenschap | Type | Beschrijving |
|---|---|---|
| Eerste argument | String |
Parameternaam |
isRequired |
bool |
Of het veld verplicht is (standaard false) |
paramIn |
ParamIn |
Waar de waarde vandaan komt (standaard ParamIn.query) |
def |
T? |
Standaard-/voorbeeldwaarde die in Swagger UI wordt getoond |
description |
String? |
Optionele beschrijving die in Swagger UI wordt getoond |
ParamIn-waarden — er bestaat geen ParamIn.body; documenteer requestbody-velden in plaats daarvan met ApiBodyField (zie hieronder):
| Waarde | Beschrijving |
|---|---|
ParamIn.path |
URL-padsegment (bijv. /books/{id}) |
ParamIn.query |
Query-string (bijv. ?page=1) |
ParamIn.header |
HTTP-requestheader |
ParamIn.cookie |
Requestcookie |
ApiBodyField — De requestbody documenteren
Beschrijf voor POST/PUT-routes de JSON-requestbody met ApiBodyField<T> in de body-lijst van ApiDoc — niet met ApiParameter/ParamIn:
ApiDoc(
body: [
ApiBodyField<String>('title', isRequired: true, description: 'Book title'),
ApiBodyField<String>('author', isRequired: true),
],
response: {
'200': [ApiResponse<bool>('success', def: true)],
},
)
ApiResponse
ApiResponse<T> beschrijft een veld in de responsebody:
ApiResponse<String>('title', def: 'Book Title'),
ApiResponse<int>('id', def: 0),
ApiResponse<bool>('success', def: true),
ApiResponse<Map<String, dynamic>>('data', def: {}),
Herbruikbare responsvormen
Finch levert geen vooraf gedefinieerde r_401/r_404/r_500-achtige constanten — die naamgeving is slechts een conventie uit het voorbeeldproject, geen framework-functie. Definieer je eigen List<ApiResponse>-constanten één keer en hergebruik ze over routes heen om veelvoorkomende foutvormen niet steeds opnieuw te hoeven schrijven:
class ApiDocuments {
static final r404 = [
ApiResponse<int>('timestamp_start', def: 0),
ApiResponse<String>('message', def: 'Not found'),
ApiResponse<bool>('success', def: false),
ApiResponse<int>('status', def: 404),
];
static final r403 = [
ApiResponse<int>('timestamp_start', def: 0),
ApiResponse<String>('message', def: 'Please login'),
ApiResponse<bool>('success', def: false),
ApiResponse<int>('status', def: 403),
];
}
// Gebruik in je response-map:
response: {
'200': [...],
'403': ApiDocuments.r403,
'404': ApiDocuments.r404,
}
Volledig routevoorbeeld
FinchRoute(
key: 'api.books.one',
path: 'api/books/{id}',
methods: Methods.GET_POST,
index: booksController.one,
apiDoc: ApiDoc(
get: ApiDoc(
description: 'Get a single book by ID.',
parameters: [
ApiParameter<String>('id', isRequired: true, paramIn: ParamIn.path),
],
response: {
'200': [
ApiResponse<int>('id', def: 0),
ApiResponse<String>('title', def: ''),
ApiResponse<String>('author', def: ''),
],
'404': ApiDocuments.r404,
},
),
post: ApiDoc(
description: 'Update a book by ID.',
parameters: [
ApiParameter<String>('id', isRequired: true, paramIn: ParamIn.path),
],
body: [
ApiBodyField<String>('title', isRequired: false),
ApiBodyField<String>('author', isRequired: false),
],
response: {
'200': [ApiResponse<bool>('success', def: true)],
'404': ApiDocuments.r404,
},
),
),
),