Node.js mit TSOA und Authentifizierung - (Teil 2) Das Basis-Setup

In diesem Artikel gehe ich Schritt für Schritt durch die Erstellung eines Node.js-Projekts, sodass das Grundgerüst implementiert ist, um einen REST-API-Endpunkt aufzurufen. Fangen wir mit dem Basis-Setup an!

4 Minuten

Wir starten diese Artikelserie mit einem leeren Node.js-Projekt. Um alles einordnen zu können, schau gerne im Teil 1: Node.js mit TSOA und Authentifizierung – Die Zielsetzung vorbei.

Ziel dieses Artikels ist der Start eines Node.js-Backends, in dem ein Endpunkt aufgerufen werden kann, der die integrierte Authentifizierung nutzt. Dies erzeugt den folgenden Fehler, den wir in der weiteren Artikelserie aufgreifen:

Error: [AuthMiddleware] not in use - received "jwt" and scopes: ADMIN  
    at expressAuthentication 
// + StackTrace

Installation der notwendigen Pakete

Die ersten Vorkehrungen sind die Installation einiger NPM-Pakete.

npm install express tsoa
npm install -D @types/express vitest

Die TsConfig

Wir richten eine TsConfig ein. Erst später in der Serie werden wir Tests schreiben, die Konfiguration ist dafür jedoch bereits vorbereitet. Zusätzlich sind passende Compiler-Flags gesetzt. Schlussendlich noch die Werte für die absoluten Imports aus dem src-Ordner.

{  
  "include": [  
    "./src/**/*",  
    "./tests/**/*"  
  ],  
  "exclude": [  
    "./node_modules"  
  ],  
  "compilerOptions": {  
    "target": "es2022",  
    "lib": [  
      "es2022"  
    ],  
    "experimentalDecorators": true,  
    "emitDecoratorMetadata": true,  
    "module": "es2022",  
    "moduleResolution": "node",  
    "baseUrl": ".",  
    "paths": {  
      "@/*": [  
        "src/*"  
      ],  
      "tsoa": [  
        "node_modules/tsoa/dist"  
      ],  
      "tsoa/": [  
        "node_modules/tsoa/dist/"  
      ]  
    },  
    "types": [  
      "vitest/globals"  
    ],  
    "resolveJsonModule": true,  
    "allowJs": false,  
    "outDir": "dist",  
    "isolatedModules": true,  
    "allowSyntheticDefaultImports": true,  
    "esModuleInterop": true,  
    "forceConsistentCasingInFileNames": true,  
    "strict": true,  
    "noImplicitAny": true,  
    "strictNullChecks": true,  
    "strictFunctionTypes": true,  
    "strictBindCallApply": true,  
    "strictPropertyInitialization": true,  
    "noImplicitThis": true,  
    "alwaysStrict": true,  
    "noUnusedParameters": true,  
    "noImplicitReturns": true,  
    "noFallthroughCasesInSwitch": true,  
    "skipLibCheck": true  
  }  
}

Skripte für die Package.json

Die package.json ergänzen wir um drei Skripte:

  • start:dev um die Applikation zu starten und sicherzustellen, dass TSOA vorab die Routen baut. Als Watch-Befehl, um die Entwicklung und das Feedback zu beschleunigen.
  • ts zum Aufruf im Terminal, um zu prüfen, ob TypeScript kompiliert wird (und für den Git-Hook, wenn wir einen nutzen wollen).
  • tsoa:gen als Shortcut für den Terminalaufruf und als Teil des start:dev-Aufrufs, um die Routen neu zu bauen.
"scripts": {  
  "start:dev": "npm run tsoa:gen && npx tsx --watch ./src/index.ts",  
  "ts": "tsc --noEmit",  
  "tsoa:gen": "tsoa spec-and-routes"  
}

Konfiguration von TSOA

Die tsoa.json sieht wie folgt aus – wichtig ist das authenticationModule. Darauf gehe ich gleich ein.

{  
  "entryFile": "src/index.ts",  
  "noImplicitAdditionalProperties": "throw-on-extras",  
  "controllerPathGlobs": [  
    "src/**/*Controller.ts"  
  ],  
  "spec": {  
    "outputDirectory": "dist",  
    "specVersion": 3  
  },  
  "routes": {  
    "routesDir": "src",  
    "authenticationModule": "./src/services/AuthMiddleware.ts"  
  },  
  "compilerOptions": {  
    "baseUrl": ".",  
    "paths": {  
      "@/*": [  
        "src/*"  
      ]  
    }  
  }  
}

Das TSOA-Authentifizierungsmodul

In der TSOA-Konfiguration haben wir unter dem Key „routes“ Folgendes definiert: "authenticationModule": "./src/services/AuthMiddleware.ts". Damit haben wir festgelegt, wo die Implementierung der Express-Middleware liegt.

Seitens TSOA müssen wir in der unter „authenticationModule“ benannten Datei einen Export für „expressAuthentication“ bereitstellen. Die Funktionssignatur umfasst:

  • Den Express-Request, den wir absichern wollen.
  • Den Security-Namen, wie wir ihn in unsere @Scope-Deklaration schreiben. Dies ermöglicht es uns, unterschiedliche Authentifizierungen zu definieren – hier beschränken wir uns auf JWT.
  • Die Scopes, wie wir sie als Array in unsere @Scope-Deklaration schreiben. In unserem Code werden wir noch ein Enum dafür definieren, das die Namen der Scopes zentral festlegt.
import { Request } from 'express'  

export type AuthResultType = {   
  userId: string  
  scopes: string[]  
}  

const expressAuthentication = (  
  _request: Request,  
  securityName: string,  
  requiredScopes?: string[],  
): Promise<AuthResultType> => {  
  return Promise.reject(new Error(`[AuthMiddleware] not in use - received "${securityName}" and scopes: ${requiredScopes}`))  
}  

export {expressAuthentication}

Im obigen Code erzeugen wir den Fehler, den wir heute als Zielsetzung haben. Weiter geht es mit dem Controller für den Endpunkt.

Ein erster Controller

Der Controller stellt den Endpunkt bereit, der mittels npm run tsoa:gen die Datei routes.ts generiert, die wir in die Express-Applikation einbinden.

import { Controller } from '@tsoa/runtime'  
import { Get, Route, Security } from 'tsoa'  

@Route('entities')  
export class EntityController extends Controller {  
  @Get()  
  @Security('jwt', ['ADMIN'])  
  public async getEntities (): Promise<string[]> {  
    return ['entity1', 'entity2']  
  }  
}

Die initiale Express-Applikation

Zu guter Letzt fehlt noch die Express-Applikation, die die Routen einbindet und auf einem Port lauscht.

import express from 'express'  
import { RegisterRoutes } from '@/routes'  

const PORT = process.env.PORT || 4000  

const app = express()  
RegisterRoutes(app)  
app.listen(PORT, () => {  
  console.log(`Server is listening on port ${PORT}`)  
})

Fazit

Wir haben ordentlich Strecke gemacht. Das, was jetzt vorbereitet ist, können wir mit npm run start:dev aufrufen und per curl "http://localhost:4000/entities" testen. Der gewünschte Fehler wird angezeigt.

Als nächsten Schritt erstellen wir ein Nutzerkonto, mit dem wir uns anmelden können.

call to action background image

Abonniere meinen Newsletter

Erhalte einmal im Monat Nachrichten aus den Bereichen Softwareentwicklung und Kommunikation gespikt mit Buch- und Linkempfehlungen.