REST-APIs mit Node.js und Swagger

Erstellen einer eigenen API

Der Inhalt der generierten Datei swagger.yaml lässt sich nach Belieben ändern, wodurch Entwickler eine eigene API-Beschreibung definieren können. Im Folgenden soll nun gezeigt werden, wie sich mit Swagger eine einfache API für das Verwalten von Büchern definieren lässt. Die API soll dabei über folgende Routen verfügen:

* "/books":
* "GET": Liefert alle Bücher.
* "POST": Speichert ein neues Buch.
* "/books/{id}"
* "GET": Liefert ein bestimmtes Buch zu einer ID.
* "PUT": Aktualisiert ein Buch.
* "DELETE": Löscht ein Buch.

Der Befehl swagger project create bookstore erzeugt das Projekt. Anschließend ist wie zuvor in einem Kommandozeilen-Tab der Befehl swagger project create zum Starten sowie in einem zweiten Tab swagger project edit zum Editieren des Projekts auszuführen.

Allgemeiner Aufbau

Der Aufbau einer API-Spezifikation in Swagger ist relativ einfach zu verstehen – ein Beispiel im YAML-Format ist im folgenden zu sehen:

swagger: "2.0"
info:
version: "0.0.1"
title: Bookstore
host: localhost:10010
basePath: /
#
schemes:
- http
- https
consumes:
- application/json
produces:
- application/json
paths:
/books:
x-swagger-router-controller: books
get:
description: Returns a list of all books
operationId: books
responses:
"200":
description: Success
schema:
$ref: "#/definitions/Books"
default:
description: Error
schema:
$ref: "#/definitions/ErrorResponse"
/swagger:
x-swagger-pipe: swagger_raw

Zu Beginn stehen unter anderem die verwendete Swagger-Version, allgemeine Informationen zur API wie Name und Version, Angaben zum Server, auf dem die API läuft, und darüber, welchen Content-Typ sie standardmäßig konsumiert und produziert.

Die Routen sind im Abschnitt paths definiert. Auf oberster Ebene steht jeweils der Pfad (im Beispiel /books), unterhalb dessen die unterstützten HTTP-Methoden (im Codeauszug momentan nur get) und darunter wiederum Informationen zum Aufbau des Requests (in späteren Beispielen zu sehen) sowie zum Aufbau der Response. Requests und Responses gleicht Swagger automatisch gegen die API-Spezifikation ab, um deren Gültigkeit festzustellen. Entsprechen sie nicht den dortigen Angaben, wird zur Laufzeit ein Fehler ausgegeben.

Um doppelte Definitionen innerhalb von Responses oder Requests zu vermeiden, lassen sich wiederkehrende Definitionen beziehungsweise Typen im Abschnitt definitions ablegen und anschließend über die Eigenschaft $ref referenzieren. Im folgenden Codebeispiel sind beispielsweise unterschiedliche Typen wie der Typ Books definiert, der dann innerhalb der Response-Definition (siehe vorheriger Code) referenziert wird (und selbst wiederum den Typ Book referenziert).

definitions:
Book:
properties:
id:
type: string
description: Unique identifier representing a book
title:
type: string
description: Title of the book
author:
type: string
description: Author of the book
pages:
type: number
description: Number of pages of the book
year:
type: number
description: Year the book was released
Books:
type: array
items:
$ref: '#/definitions/Book'
Response:
type: object
properties:
success:
type: number
description:
type: string
required:
- success
- description
ErrorResponse:
required:
- message
properties:
message:
type: string

Implementierung von Controllern

Die Eigenschaft x-swagger-router-controller ist, wie der Name durch das vorangestellte "x" bereits erkennen lässt, kein Standardeigenschaft der Swagger-Spezifikation, sondern eine Erweiterung, die in dem Fall den Namen des Controllers definiert, der bei Aufruf des Endpoints die Abarbeitung des Requests übernimmt. Die Implementierung ist dabei für das Beispiel in der Datei books.js unterhalb des Verzeichnisses controller zu speichern.

Welche der von dieser Datei exportierten Funktionen aufzurufen ist, definiert wiederum die Eigenschaft operationId unterhalb der Routen-Definition. Im Beispiel wird auf die Weise für die Route GET /books die Funktion books() referenziert. Eine simple Implementierung Letzterer zeigt folgendes Listing. Der Einfachheit halber hält das Programm die Buch-Instanzen lediglich in einer Map vorn, im Produktiveinsatz würde man die Daten stattdessen dauerhaft persistieren, etwa mit der (nicht nur) im Zusammenhang mit Node.js-Anwendungen beliebten NoSQL-Datenbank MongoDB.

let BOOKS = new Map();
BOOKS.set(
'9780201485677',
{
id: '9780201485677',
title: 'Refactoring: Improving the Design of Existing Code',
author: 'Martin Fowler',
pages: 431,
year: 1999
}
);
BOOKS.set(
'9780132350884',
{
id: '9780132350884',
title: 'Clean Code: A Handbook of Agile Software Craftsmanship',
author: 'Robert C. Martin',
pages: 462,
year: 2008
}
);
BOOKS.set(
'9780321356680',
{
id: '9780321356680',
title: 'Effective Java',
author: 'Joshua Bloch',
pages: 368,
year: 2008
}
);

function books(request, response) {
response.json(Array.from(BOOKS.values()));
}

module.exports = {
books: books,
};