Problem
Du willst eine REST-API mit Node.js und Typescript bauen und API-Konsument*innen eine OpenAPI-Dokumentation aka Swagger bereitstellen? Du hast aber keine Lust, alles doppelt in Code und Doku zu pflegen?
Lösung
Es gibt ein npm-Paket namens "tsoa" (ein Akronym für TypeScript OpenAPI), das aus deinem Typescript-Code automatisch eine passende OpenAPI-Spezifikation generiert. Auf Wunsch kümmert sich tsoa auch um das Routing – also für jeden Request die richtige TypeScript-Funktion aufzurufen. Und es übernimmt auch die Validierung und stellt so sicher, dass die Request-Daten der generierten Spezifikation entsprechen. Weil damit "fast alles" abgedeckt ist, eignet sich tsoa insbesondere für neue Projekte.
Um den TypeScript-Code zur single source of truth zu machen, setzt tsoa auf eine Kombination aus TypeScript-Types (zur Beschreibung des Datenformats), Decorators (für Metadaten wie HTTP-Methods und -Statuscodes) und Doc-Comments (für reine Dokumentation und für Dinge, die sich in TypeScript nicht abbilden lassen, wie z. B. Integers).
Beispiel
import { Body, Controller, Example, Get, Path, Post,
Query, Route, Security, SuccessResponse } from "tsoa";
import { userService } from "my-user-service";
/** @isInt @minimum 0 */
export type UserId = number;
export type User = { id: UserId; name: string };
export type UserWithoutId = Omit<User, "id">;
@Route("users")
export class UsersController extends Controller {
@Example<User>({
id: 1,
name: "Benjamin Hogl",
})
@Get("{userId}")
public async getUser(
@Path() userId: UserId,
@Query() name?: string
): Promise<User> {
return await userService.get(userId, name);
}
@SuccessResponse("201", "Created")
@Security("jwt", ["ADMIN_ONLY_SCOPE"])
@Post()
public async createUser(
@Body() requestBody: UserWithoutId
): Promise<void> {
await userService.create(requestBody);
this.setStatus(201);
return;
}
}
Weitere Aspekte
- https://tsoa-community.github.io/docs/
- Die Dokumentation ist leider etwas dürftig, teilweise muss man sich die nötigen Infos aus ein paar Beispielen rausziehen.
- tsoa bringt Integrationen für die üblichen Node-Server-Frameworks (Express, Koa, Hapi) mit, weitere lassen sich selbst implementieren.
- Die Logik für Authentication/Authorization muss man in einer zentralen Funktion selbst implementieren. tsoa ruft diese Funktion dann für jeden Request mit den im Security-Decorator angegebenen Parametern auf (siehe Doku).
- Für die Entwicklung empfiehlt es sich, den Build-Step (=Generierung der OpenAPI-Spezifikation und des Routing-Codes) per nodemon bei jeder Codeänderung neu zu starten (siehe Doku). Das geht zum Glück sehr schnell.
- Das npm-Paket swagger-ui-express sorgt für die interaktive Anzeige der OpenAPI-Spezifikation (siehe Screenshot).
- Eine saubere Typisierung macht Refactorings sehr einfach, und mit tsoa meckert der Typechecker sogar über veraltete Beispiele in der API-Doku. Das ist insbesondere praktisch, wenn sich die Anforderungen des Kunden dauernd ändern.
---
Autor: Benjamin Hogl / Software Engineer / Standort Stuttgart
Lust, das nächste ToiletPaper zu schreiben? Jetzt bei jambit bewerben!