Nous avons récemment vu les bases de TypeScript, mais qu'en est-il d'un cas concret ?
Aujourd'hui nous allons construire une simple API REST à l'aide d'Express et TypeScript.
Avant de continuer ce tuto, je vous recommande vivement d'être à l'aise avec :
- Les codes HTTP et les méthodes HTTP
- JavaScript
- TypeScript (si ce n'est pas le cas, je connais un super article, lien plus haut 😉)
- Node.js et NPM (des bases suffisent)
- Express (recommandé mais pas obligatoire)
Un peu de contexte
Premièrement, c'est quoi une API REST ?
Concrètement, une API (Application Programming Interface) consiste à fournir une interface (du code, des adresses HTTP, etc.) utilisable dans un autre contexte.
Un exemple simple sûrement parlant à un grand nombre : JQuery. Avec JQuery nous n'avons accès qu'à ce qui nous est offert. Nous utilisons l'API de JQuery.
<body>
<h1 id="title"></h1>
</body>
<script>
$('#title')
.text('Mon titre')
</script>
Une API REST (REpresentational State Transfer) nous permet de récupérer des données (souvent JSON) depuis une adresse HTTP.
Par exemple comme nous l'avons vu dans Wordpress, il nous suffirait de faire un appel HTTP sur l'adresse https://wordpress.example.com/wp-json/wp/v2/posts
pour récupérer tous les posts du site au format JSON.
Express
Express est un framework pour Node.js, qui nous propose une API nous facilitant le travail pour créer des serveurs WEB.
Nous nous en servirons pour développer une API REST dans ce tuto mais Express peut tout à fait être utilisé pour un simple serveur HTTP renvoyant des pages HTML.
Installation du projet
Avant toute chose, il nous faut installer tout ce dont nous avons besoin pour développer.
Premièrement, nous créerons un dossier pour notre projet, pour ce tuto je l'appellerai api-express
.
Dans ce dossier, nous allons créer deux sous-dossiers :
src
qui contiendra notre code sourcetypes
qui contiendra nos types TypeScript
Deuxièmement, nous allons instancier un projet Node.js, avec la commande suivante (libre à vous de changer les informations de package.json
).
npm init -y
Passons maintenant à l'installation des dépendances.
Les dépendances de développement
npm install -D @types/express @types/node @types/cors nodemon ts-node tsconfig-paths tsup typescript
@types/xxx
, ces modules nous permettent simplement d'avoir accès aux types TS de nos dépendances. Sans ça, TS nous criera probablement dessus 😉nodemon
, nous permet de relancer le serveur à chaque changement dans les fichiers sources.ts-node
, compile nos fichiers TS en développement et nous permet d'exécuter nos fichiers à la volée. EX:ts-node mon_fichier.ts
===node mon_fichier.js
tsconfig-paths
, nous permet de donner des chemins relatifs personnalisables àts-node
. EX:~/mon_fichier.ts
===/src/mon_fichier.ts
. Cela est notamment pratique dans de gros projets.tsup
, nous permettra de compiler nos fichiers TS en un seul fichier JS prêt pour la production.typescript
, besoin d'explications ? 😄
Les dépendances de développement et de production
Ici c'est plus léger, nous n'aurons besoin que d'express
et cors
.
cors
permet d'autoriser uniquement certains domaines (ou tous) à envoyer des requêtes à notre API.
npm install express cors
Nous allons ensuite définir des "scripts" dans notre package.json
pour lancer des commandes rapidement.
"scripts": {
"dev": "nodemon --watch src -e js,ts,json --exec \"ts-node src/index.ts\"",
"build": "tsup src/index.ts --format cjs --clean",
"start": "node dist/index.js"
}
dev
, lancenodemon
en lui disant d'observer le dossiersrc
pour les changements. On lui demande de vérifier les fichiers JS, TS et JSON, enfin, à chaque changement il relance le serveur avects-node
.build
, compile le projet avec tsup et génère notre fichier de destination.start
, une fois le projet compilé, nous permettra de lancer le serveur en production.
Enfin, il ne nous manque plus que notre fichier tsconfig.json
à la racine du projet qui configure la façon dont TypeScript fonctionne.
{
"compilerOptions": {
"baseUrl": ".",
"target": "es2017",
"module": "CommonJS",
"lib": ["esnext"],
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,
"strictNullChecks": true,
"resolveJsonModule": true,
"skipDefaultLibCheck": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"outDir": "./dist",
"strictPropertyInitialization": false,
"paths": {
"~/*": ["./src/*"],
"~~/*": ["./*"]
}
},
"ts-node": {
"require": ["tsconfig-paths/register"]
}
}
Les options importantes pour ce projet sont :
baseUrl
, explicite à TS que l'entrée du projet est le dossier courant.target
, vers quelle version de JavaScript nous allons compiler.module
, quels types de modules JavaScript nous allons utiliser.CommonJS
(require('un-module')
) est la version encore la plus utilisée avec Node.js.esModuleInterop
, nous pourrons utiliser la syntaxeimport x from 'y'
avec les modulesCommonJS
. Merci TypeScript 👍 !paths
, on définit les chemins personnalisés du projet.ts-node
, on demande àts-node
d'utiliser nos chemins personnalisés.
Passons au code
Vu le type de projet, placer des snippets de code tout au long du tuto n'est pas vraiment viable.
Je vous propose donc d'utiliser un projet CodeSandbox et de vous expliquer quel fichier fait quoi (en plus des commentaires dans le code).
Vous pouvez ignorer la page de prévisualisation navigateur, elle ne nous servira pas
Explications
Le dossier types
C'est dans ce dossier que nous définirons nos types TypeScript, ici pour les animaux et les erreurs personnalisées.
Le dossier src
index.ts
est notre fichier d'entrée, c'est dans ce fichier que nous instancierons le serveur ainsi que les routes (chemins) HTTP.
config.ts
est un simple fichier exportant un objet contenant notre configuration, ici le port sur lequel le serveur tourne.
data.ts
est un simple fichier exportant un tableau d'animaux, il nous servira de fausse base de données.
Le dossier src/utils
C'est ici que nous définirons les différentes "utilités" dont nous aurons besoin un peu partout.
exceptions.ts
contient des classes JavaScript nous permettant de créer des erreurs HTTP plus simplement. Ici nous n'aurons besoin que des erreurs 404 et 400.
Exemple
throw new NotFoundException('Mince une erreur') // Notre route nous retournera une 404 avec ce message d'erreur dans le JSON
Le dossier src/resources
Ce dossier contiendra toute la logique pour chaque resource. Ici, notre seule ressource est pets
, dans un vrai projet nous aurions par exemple users
, articles
, products
.
Chaque sous-dossier contient :
- Un
controller
, qui défini les routes ainsi que le contenu à renvoyer pour chaque route. Ce fichier est généralement petit, et ne sert qu'à appeler le service ou traiter les paramètres de la requête. - Un
service
, c'est lui qui s'occupe de toute la logique. Par exemple, c'est lui qui traite la base de données, les requêtes etc.
Le dossier src/middlewares
Dans ce dossier nous créerons des middlewares Express, des hooks en quelques sortes. Ils nous permettent d'ajouter de la logique a des moments précis lors de chaque requête.
exceptions.handler.ts
, c'est lui qui va gérer toutes les erreurs (les nôtres avec les 404 et 400, mais aussi celles d'Express).
unknownRoutes.handler.ts
, c'est lui qui va gérer toutes les routes demandées qui n'existent pas. Par exemple, si la route /dogs
n'existe pas, il nous renverra une 404
avec un message d'erreur.
C'est bien beau, mais comment peut-on tester ?
Pour tester chaque route, je conseille Postman ou Insomnia qui permettent d'envoyer des requêtes HTTP facilement.
Conclusion
Nous avons non seulement vu comment préparer un projet Node.js avec TypeScript mais également comment en faire une API REST avec Express.
Le processus n'est, en soi, pas compliqué. Cependant, vous avez peut-être remarqué le nombre de dépendance et les étapes préalables avant de pouvoir commencer.
En effet, Express n'est pas opinionated, c'est à dire qu'il ne force en rien une structure de code, ni de façons de faire. C'est donc à vous de vous débrouiller et de commencer à zéro.
Si cette approche ne vous convient pas, des frameworks basés sur Express existent, comme le plus connu Nest.js qui utilise TypeScript par défaut !
Sur ce, je vous dis à bientôt dans un autre tuto 👋
Commenter
Vous devez être inscrit et identifié pour utiliser cette fonction.
Connectez-vous (déjà inscrit)
Pas encore inscrit ? C'est très simple et gratuit.