TSOA le framework qui va propulser vos API

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 ?

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:
Remarquez qu'il a même ajouté notre règle de sécurité:
Voilà, vous l'avez remarqué, je suis conquis !
Bon code 😉

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.
Poursuivre la lecture dans la rubrique Dev