TSOA le framework qui va propulser vos API

TSOA le framework qui va propulser vos API
Alexandre P. dans Dev - mis à jour le 02-04-2025

Tsoa simplifie la création d'API avec TypeScript en générant Swagger et des routes automatiquement. Découvrez comment l'utiliser efficacement dans ce guide.

Vous connaissez Tsoa ?

comment ?

Apparemment ça se prononce so-uh... Enfin, chacun fait ce qu'il veut 😂 mais du coup, qu'est-ce que c'est ?

C'est une petite bombe de framework qui ne date pas d'hier et qui permet de générer des API via des Controllers que l'on écrit avec une syntaxe de decorator à la NestJS.

Pourquoi faire Tsoa au lieu de NestJS ?

Fondamentalement les deux projets n'ont pas grand chose en commun si ce n'est les decorators.

Alors, oui il fonctionne aussi avec des Controllers mais, il n'est pas question d'injection de dépendances ou autre.

D'ailleurs Tsoa est intégré avec OpenAPI, ne fait que de l'API et son objectif principal c'est de vous génerer le Swagger de votre API en une ligne de commande.

Il gère facilement votre couche de sécurité, vos middlewares, vos routes, bref... Un exemple ?

Je vous conseille de jeter un oeil à la documentation ici: https://tsoa-community.github.io/docs/getting-started.html

Qu'est-ce qu'on doit savoir pour manipuler Tsoa ?

Voici ce que je pense être suffisant pour pouvoir faire n'importe quel API avec cet outil:

  • Les controllers
  • La sécurité
  • Les middlewares
  • Les commandes CLI

Voici un exemple de controller Tsoa

// src/users/usersController.ts
import {
  Body,
  Controller,
  Get,
  Path,
  Post,
  Query,
  Route,
  SuccessResponse,
} from "tsoa";
import { User } from "./user";
import { UsersService, UserCreationParams } from "./usersService";

@Route("users")
export class UsersController extends Controller {
  @Get("{userId}")
  public async getUser(
    @Path() userId: number,
    @Query() name?: string
  ): Promise<User> {
    return new UsersService().get(userId, name);
  }

  @SuccessResponse("201", "Created") // Custom success response
  @Post()
  public async createUser(
    @Body() requestBody: UserCreationParams
  ): Promise<void> {
    this.setStatus(201); // set return status 201
    new UsersService().create(requestBody);
    return;
  }
}

Pour ceux qui sont déjà habitué avec NestJS ou Spring sur Java, vous ne serez pas dépaysé.

L'approche est classique, ce sont les décorators qui vont faire toute la partie déclarative que vous n'aurez pas à faire.

Et pour le passage des variables dans nos méthodes de controllers qui vont acheminer la donnée que vous voulez entre:

  • une route param
  • un query param
  • le body
  • le request object...

C'est tellement simple, mais quel plaisir !

La sécurité

Vous pouvez gérer toute la couche sécu sur vos Controllers en une seule ligne:

// ...
@Route("users")
@Security('bearer') // <--- comme ceci
export class UsersController extends Controller {
  @Get("{userId}")
  public async getUser(
    @Path() userId: number,
    // ...

Il ne faudra pas oublier d'ajouter cette règle dans votre tsoa.json:

// ...
"spec": {
    "outputDirectory": "src/build",
    "specVersion": 3,
    "securityDefinitions": {
      "bearer": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT", 
        "description": "Enter your bearer token in the format \"Bearer {token}\""
      }
    }
  },
  "routes": {
    "routesDir": "src/build",
    "authenticationModule": "src/auth.ts"
  },
  // ...

Et enfin définir ce middleware, pour ma part j'ai opté pour le fichier src/auth.ts:

import * as express from 'express'
import { verifyToken } from './services/tokenService'

export function expressAuthentication(
  request: express.Request,
  securityName: string,
  scopes?: string[]
): Promise<any> {
  if (securityName === 'bearer') {
    const token = request.headers.authorization?.split(' ')[1]

    return new Promise(async (resolve, reject) => {
      if (!token) {
        reject(new Error('No token provided'))
      }

      try {
        const user = await getUser(token)
        // .... vos règles ici
      } catch (error) {
        reject(new Error('Invalid token'))
      }
    })
  }
  return Promise.reject(new Error('Unknown security method'))
}

Les middleware

Si vous voulez ajouter des middlewares, vous pouvez !

Pour la sécurité bien que je vous recommande de rester sur le décorator @Security pour la génération du Swagger, ou encore des middlewares custom.

Voici un exemple de middleware pour gérer l'upload avec Multer, ça c'est cadeau:

// Dans src/middlewares/upload.ts

import multer from 'multer'
import path from 'path'

import Express from 'express'
import config from '@/config/getConfig'

const storage = multer.memoryStorage()
const upload = multer({ storage }).single('file')

export const uploadMiddleware = async (
  req: Express.Request,
  res: Express.Response,
  next: Express.NextFunction
) => {
  return upload(req, res, async err => {
    if (err) {
      return res.status(400).json({ error: 'Upload failed' })
    }

    if (!req.file) {
      return res.status(400).json({ error: 'No file uploaded' })
    }

    try {
      const uploadDir = config.uploadPath // <-- mettez le chemin que vous voulez ici

      const filename = `file-${Date.now()}${path.extname(req.file.originalname || '.png')}`
      const outputPath = path.join(uploadDir, filename)

      req.body.filePath = outputPath
      req.body.filename = filename

      next()
    } catch (error) {
      console.error('Error processing image:', error)
      return res.status(500).json({ error: 'Failed to process image' })
    }
  })
}

Pour utiliser le middleware ensuite dans vos controllers, il faut juste importer votre middleware et mettre un décorator:

import { Body, Post, Request, Response, Route, Security, Middlewares } from 'tsoa'
import { Tags } from 'tsoa'
import { uploadMiddleware } from '@/middlewares/upload' // <-- On importe notre middleware
// autres imports

@Route('files')
@Security('bearer')
@Tags('Files')
export class FilesController extends Controller {
  /**
   * Upload a file 
   */
  @Post('/')
  @Middlewares(uploadMiddleware)  // <-- Juste ça et c'est fait
  @Response<{ message: string; filename: string }>(201, 'File uploaded successfully')
  @Response<{ error: string }>(400, 'File upload data missing')
  @Response<{ error: string }>(500, 'Failed to upload file')
  public async uploadFile(
    @Request() req: Request,
    @Body() body: { filePath?: string; filename?: string }
  ) {
  // implémentation

Voilà, vous avez votre endpoint qui permet de faire de l'upload !

La génération des routes et du Swagger

Une fois qu'on a fini notre Controller, on peut générer nos routes et notre Swagger.

Pour compiler, dans un terminal on exécute:

tsoa spec-and-routes

Et lorsque l'on génère le swagger:

tsoa swagger

On peut profiter de notre documentation depuis notre page de doc.

J'ai opté pour redoc:

Redoc with Swagger from TSOA

Remarquez qu'il a même ajouté notre règle de sécurité:

TSOA security rule

Voilà, vous l'avez remarqué, je suis conquis !

Bon code 😉

#code#typescript#api#swagger

user picture

Alexandre P.

Développeur passionné depuis plus de 20 ans, j'ai une appétence particulière pour les défis techniques et changer de technologie ne me fait pas froid aux yeux.


Votre vie privée

Nous utilisons des cookies pour améliorer votre expérience sur notre site, analyser notre trafic et personnaliser les publicités. En cliquant sur "Accepter", vous consentez à l'utilisation de tous les cookies. Vous pouvez également choisir de refuser en cliquant sur le bouton "Refuser".