Créez votre première application serverless avec Angular, NestJS et Azure

CLOUD & INFRA
Temps de lecture : 12 minutes
Yohan LASORSA
Cloud Developer Advocate chez Microsoft

Après avoir travaillé pendant plus de 10 ans comme ingénieur logiciel et architecte du cloud, il se concentre aujourd’hui sur les plateformes web et les projets open source. Il aime partager des bouts de JavaScript partout où il peut, tout en partageant sa passion avec les autres.

Créez votre première application serverless avec Angular, NestJS et Azure

Avez-vous déjà eu envie de sauter le pas et de voir comment créer une application serverless de A à Z ? Eh bien allons‑y !

Dans cet article, nous allons passer en revue toutes les étapes nécessaires pour amorcer, développer et déployer une application complète à l’aide de la stack Nitro : Angular pour le front-end, NestJS pour le back-end et la plateforme Azure Serverless pour le déploiement.

L’application que nous allons créer permettra d’afficher une anecdote de chat aléatoire, en allant chercher une anecdote intéressante sur les chats via une API pour l’afficher ensuite sur une page Web, comme ceci :

TL;DR Notions clés à retenir

  • ◾ Si vous aimez Angular, jetez un œil à NestJS : il utilise les mêmes concepts, fonctionnalités et architecture, mais pour le développement back-end avec Node.js
  • ◾ La préparation d’une application NestJS pour un déploiement serverless avec Azure Functions peut se faire simplement avec nest add @nestjs/azure-func-http et ne nécessite aucune modification de la structure existante
  • ◾ La stack Nitro (= Angular + NestJS + Azure Serverless) vous permet de créer des applications TypeScript de bout en bout de manière cohérente, robuste et économique

Voici le code source du projet final sur GitHub.

Qu’allez-vous apprendre ici ?

Dans cet article, vous verrez comment :

◾ Créer une API Node.js serverless de A à Z à l’aide de NestJS
◾ Créer une application Angular et la connecter à votre back-end pour un développement à la fois local et distant
◾ Utiliser Azure Functions pour déployer votre API de manière économique et évolutive
◾ Utiliser l’hébergement de sites Web statiques d’Azure Storage pour déployer votre application Angular avec ng deploy

Liens de référence pour tout ce que nous utilisons

Angular avec @azure/ng-deploy pour le front-end
NestJS avec @nestjs/azure-func-http pour le back-end
Azure Functions pour le déploiement d’API
Azure Storage pour l’hébergement du site Web
Azure CLI pour gérer les ressources
Azure Functions Core Tools pour tester et déployer votre back-end

Planifiez votre migration vers le SaaS en fonction des besoins de vos clients

Dans ce rapport de Forrester, apprenez comment vous pouvez aider vos clients à identifier et mettre en œuvre des solutions qui répondent à leurs besoins prioritaires pour des expériences cohérentes et

Télécharger

Prérequis

◾ Un environnement Node.js opérationnel
Azure CLI pour créer des ressources sur Azure. Si vous ne souhaitez pas l’installer localement, vous pouvez utiliser https://shell.azure.com.

Il vous faut aussi un compte Azure pour créer des ressources et déployer l’application. Si vous n’avez pas de compte, vous pouvez en créer un gratuitement via ce lien (comprend des crédits gratuits, largement suffisant pour les besoins d’utilisation de cet article).

Initialiser le projet

Créons un nouveau dossier dans lequel nous mettrons le code front-end et back-end.

$ mkdir catfacts
$ cd catfacts

Nous commencerons par créer l’API permettant de récupérer une anecdote de chat, puis nous créerons le front-end qui utilise cette API et affiche l’anecdote sur une page Web.

Créer le back-end

Le back-end de notre application sera créé avec NestJS.

Pour celles et ceux qui ne connaissent pas encore NestJS, c’est un framework Node.js basé sur TypeScript qui ressemble beaucoup à Angular et vous aide à créer des applications Node.js de qualité professionnelle de manière efficace et évolutive.

Installer la CLI NestJS et générer le serveur

Utilisez les commandes suivantes pour installer NestJS CLI et créer une nouvelle application serveur :

$ npm install -g @nestjs/cli
$ nest new catfacts-server
$ cd catfacts-server

Créer le point de terminaison des anecdotes

Puis, nous utilisons à nouveau NestJS CLI pour créer un nouveau contrôleur :

$ nest generate controller facts

Ouvrez le fichier src/facts/facts.controller.ts et ajoutez cette liste d’anecdotes sur les chats après les imports :

// Quelques anecdotes fournies par https://catfact.ninja
const catFacts = [
"Cats have supersonic hearing"
"On average, cats spend 2/3 of every day sleeping. That means a nine-year-old cat has been awake 
for only three years of its life."
"A cat uses its whiskers for measuring distances. The whiskers of a cat are capable of registering 
very small changes in air pressure."
"A healthy cat has a temperature between 38 and 39 degrees Celcius."
"A cat’s jaw can’t move sideways, so a cat can’t chew large chunks of food."
"Jaguars are the only big cats that don't roar.",  "Cats have 'nine lives' thanks 
to a flexible spine and powerful leg and back muscles"
"The cat's tail is used to maintain balance."
"The technical term for a cat’s hairball is a 'bezoar.'"
"The first cat show was organized in 1871 in London. Cat shows later became a worldwide craze."
"A happy cat holds her tail high and steady." 
"A cat can jump 5 times as high as it is tall."
];

Puis ajoutez une nouvelle méthode à la classe FactsController existante, comme ceci :

@Controller('facts')
export class FactsController {   

@Get('random')
getRandomFact(): string { 
 return catFacts[Math.floor(Math.random() * catFacts.length)];
  } 
}

Revenons maintenant en détails sur ce que nous venons de faire :

  • ◾ L’annotation @Controller() spécifie que cette classe traitera les requêtes entrantes et renverra les réponses au client. L’argument facultatif “facts” utilisé ici servira de préfixe de route de base pour tous les gestionnaires définis dans cette classe.
  • ◾ L’annotation @Get() définit un nouveau gestionnaire de requêtes HTTP GET, créant un nouveau point de terminaison. L’argument facultatif “random” définit la route pour ce point de terminaison.

En combinant le préfixe de route du contrôleur et la route du gestionnaire de requêtes, NestJS créera le point de terminaison HTTP GET /facts/random.

Ce point de terminaison renverra un code d’état 200 et la réponse associée, dans notre cas une simple chaîne de caractères.

Vous pouvez consulter la documentation d’un contrôleur NestJS pour obtenir la liste de toutes les annotations et options qui peuvent être utilisées pour définir des points de terminaison.

Ajouter un préfixe d’API global

Une bonne pratique courante est de définir un préfixe de route global pour tous vos points de terminaison. Vous pourrez ainsi facilement gérer différentes versions de votre API ou l’exposer avec des ressources statiques.

Pour cela, éditez le fichier src/main.ts dans NestJS et appelez la méthode setGlobalPrefix() après la création de l’application :

const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('api');

Après cette modification, notre point de terminaison HTTP sera GET /api/facts/random.

Démarrer le serveur

Notre serveur est maintenant prêt pour les tests locaux, vous pouvez le démarrer avec la commande :

$ npm start

Une fois que le serveur a démarré, vous pouvez vérifier si notre API répond correctement avec curl :

$ curl http://localhost:3000/api/facts/random

Vous devriez voir apparaître une anecdote aléatoire dans la console à chaque fois que vous lancez cette commande. Vous avez maintenant une API qui fonctionne ! 🎉

Laissons le serveur s’exécuter, ouvrons un nouveau terminal et passons au front-end.

Créer le front-end

Il est temps à présent de créer notre application Web pour afficher les anecdotes de chats ! 🐈

Nous allons utiliser Angular pour créer le front-end de cette application, et explorer certaines des options de la CLI qui simplifieront notre travail avec notre serveur local durant le développement.

Installer la CLI Angular et générer le client

Installons la CLI Angular et utilisons-la pour créer l’application cliente à l’aide de ces commandes :

# Attention de revenir à la racine du projet d’abord!
$ npm install @angular/cli
$ ng new catfacts-client --defaults
$ cd catfacts-client

Préparer la configuration de votre application

Tout d’abord, ajoutons une nouvelle propriété apiUrl à notre configuration d’environnement dans src/environments/environment.ts, pour indiquer le chemin de notre API pendant le développement (nous nous occuperons de l’URL de production une fois notre serveur déployé) :

export const environment = {  
production: false,  
apiUrl: '/api'
};

En spécifiant le chemin relatif /api, nous indiquons que les appels API se feront sur le même domaine que le client. Nous devons donc définir un proxy afin que le serveur de développement intégré à la CLI Angular transmette ces appels à notre serveur d’API local.

Pour ce faire, nous allons créer un nouveau fichier proxy.local.js à la racine de notre projet client :

/* 
* Ceci permet de transmettre les requêtes HTTP comme `http.get('/api/stuff')` vers un autre serveur/port. 
* C’est particulièrement utile Durant le développement pour éviter des problèmes CORS dus à l’utilisation d’un serveur local. 
* Pour plus de détails et options, voir: https://angular.io/guide/build#proxying-to-a-backend-server 
*/
module.exports = { 
 '/api': {
    target: 'http://localhost:3000',
    secure: false  
}
};

Ensuite, nous ajouterons un nouveau script NPM dans le fichier package.json pour l’utiliser :

"scripts": {  
"start:local": "ng serve --proxy-config proxy.local.js",  
...
}

Nous pouvons à présent exécuter la commande npm run start:local. La CLI Angular lancera un serveur de développement avec un proxy pour permettant d’atteindre l’API de notre back-end NestJS local.

Récupérer une anecdote et l’afficher

Pour récupérer des données depuis notre API, nous allons utiliser le service HttpClient intégré à Angular. Commençons par importer le module dans src/app/app.module.ts :

import { HttpClientModule } from '@angular/common/http';

Puis ajoutez le module à la déclaration des imports dans AppModule :

imports: [
  BrowserModule,
  HttpClientModule
],

Ensuite, ouvrez src/app/app.component.ts et modifiez-le pour faire l’appel d’API :

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http'
import { Observable } from 'rxjs';
import { environment } from '../environments/environment';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {

 fact$: Observable<string>;

   constructor(http: HttpClient) {
    this.fact$ = http.get(
      `${environment.apiUrl}/facts/random`,
      { responseType: 'text' }
    );
  }
 }

La partie importante est ce code :

this.fact$ = http.get(
  `${environment.apiUrl}/facts/random`, 
    { responseType: 'text' }
);

Ici, nous utilisons le client HTTP d’Angular injecté dans le constructeur pour lancer une requête HTTP GET à notre API, en utilisant le préfixe apiUrl que nous avons défini dans la configuration d’environnement. Le résultat de notre requête étant du texte brut et non un objet JSON, nous avons aussi explicitement défini le type de réponse sur “text” de sorte qu’Angular n’essaie pas de la convertir en objet.

Notez que cette ligne créera l’observable fact$, mais aucun appel HTTP ne sera effectué tant que la méthode subscribe de cet observable ne sera pas appelée !
Pour cela, nous utiliserons AsyncPipe dans notre template HTML, qui s’abonnera/se désabonnera automatiquement à cet observable.

Éditez ensuite src/app/app.component.html et remplacez tout le HTML présent pour afficher à la place notre anecdote :

<article> 
 <p>{{ fact$ | async }}</p>
  <p>😸</p>
</article>

Nous avons utilisé ici le pipe async qui fait tout le nécessaire : il s’abonne à l’observable fact$, attend que des données soient reçues pour les afficher, et prend soin du nettoyage en se désabonnant une fois le composant détruit.

Tester le résultat localement

Vérifiez que votre serveur d’API est toujours en cours d’exécution, et lancez le serveur de développement pour le front-end avec la commande npm run start:local si ce n’est pas déjà fait.

Ouvrez l’URL http://localhost:4200 dans votre navigateur. Vous devriez voir quelque chose qui ressemble à ceci :

Votre application fonctionne (localement) !

Un peu de cosmétique (facultatif)

Ajoutons une touche de CSS pour rendre votre application plus jolie ! 🦄

Ouvrez src/styles.css pour ajouter un micro CSS reset et un dégradé d’arrière-plan :

html, body {  
margin: 0;  
height: 100%; 
 background: linear-gradient(120deg, #f6d365, #fda085);
}

Maintenant ouvrez src/app/app.component.css pour styliser notre anecdote de chat, en changeant la police et en ajoutant quelques marges :

@import url('https://fonts.googleapis.com/css?family=Caveat&display=swap');
 p { 
 font-family: 'Caveat', cursive;  
font-size: 2.5rem;  
margin: 2rem;
}

Conseil : allez jeter un œil à https://fonts.google.com et choisissez votre police préférée !

Ajoutons maintenant un centrage vertical du bloc article et un centrage horizontal pour le texte :

article { 
 text-align: center; 
 position: absolute; 
 top: 50%; 
 transform: translateY(-50%); 
 width: 100%;
}

Le rendu devrait être un peu plus joli, comme ceci :

Déployer votre application

À ce stade, votre application complète est capable de s’exécuter localement. Il est temps à présent de la déployer dans le cloud.

Passer au serverless

Pourquoi le mode serverless ? Pourquoi ne pas déployer notre serveur sur un conteneur ou un Azure App Service par exemple ? Il y a 2 raisons principales :

◾ Vous ne payez qu’à l’utilisation, pas pour l’affectation des ressources (et ce n’est vraiment pas cher !)
◾ Le passage à l’échelle est automatique, sans rien à configurer

Tout d’abord, mettons à jour notre application NestJS pour la changer en application serverless, afin de pouvoir la déployer sur Azure Functions.

Ouvrez un terminal à l’emplacement de du serveur, et exécutez cette commande :

$ nest add @nestjs/azure-func-http

Grâce aux générateurs intégrés, le code de votre serveur est maintenant prêt à être déployé sur Azure Functions ! Rien n’a changé dans votre code, il peut toujours être exécuté localement comme auparavant.

 

Si vous regardez de plus près, voici ce qui a été ajouté :

  • ◾ main : un dossier contenant la configuration de déclenchement et le point d’entrée Azure Functions
  • src/main.azure.ts : un point d’entrée alternatif pour votre serveur qui sera uniquement utilisé sur Azure Functions (laissant ainsi le point d’entrée normal intact).
  • ◾ Quelques fichiers de configuration à la racine du projet. Inutile de nous en occuper pour l’instant.
  • ◾ Un nouveau script NPM start:azure dans votre package.json, permettant d’exécuter votre API localement mais cette fois avec le simulateur Azure Functions.

Pour pouvoir utiliser la commande npm run start:azure, vous devez installer les Azure Functions Core Tools. Cela installera la CLI func que vous pourrez utiliser pour tester vos fonctions et les déployer sur Azure.

Une fois la CLI func installée, testons notre API exécutée sur une fonction :

$ npm run start:azure

Si tout fonctionne bien, vous devriez voir après quelques instants ceci dans la console :

Now listening on: http://0.0.0.0:7071
Application started. Press Ctrl+C to shut down.

Http Functions:

main:  http://localhost:7071/api/{*segments}

Cela signifie que votre API devrait fonctionner sur le port 7071. Testons-la à nouveau avec curl :

$ curl http://localhost:7071/api/facts/random

Tirez-vous parti de l’opportunité que représente le modèle SaaS ?

Migration vers un modèle SaaS : une opportunité à forte valeur ajoutée pour les ISV.

Télécharger

Créer les ressources et déployer sur Azure Functions

Maintenant que notre API est prête à être exécutée sur Azure Functions, déployons-la pour de vrai !

Tout d’abord, nous devons créer quelques ressources Azure. Pour cela, nous allons utiliser les commandes de la CLI Azure :

# Crée un nouveau groupe de ressources
$ az group create --name catfacts --location northeurope

# Crée le compte de stockage
# Ce nom doit être unique au monde, alors personnalisez-le
$ az storage account create --name catfacts \ 
                           --resource-group catfacts \ 
                           --kind StorageV2 
# Crée la function app
# Ce nom doit être unique au monde, alors personnalisez-le
$ az functionapp create --name catfacts-api \
                        --resource-group catfacts \    
                        --consumption-plan-location northeurope \
                       --storage-account catfacts 

Maintenant que les ressources sont créées sur Azure, nous pouvons utiliser la CLI func pour déployer notre API :

# Construit votre application
$ npm run build 

# Nettoie le dossier node_modules pour ne garder que les dépendances de production
$ npm prune --production 
# Crée une archive depuis vos fichiers locaux et publiez-la
# N'oubliez pas de remplacer le nom par celui que vous avez utilisé précédemment
$ func azure functionapp publish catfacts-api

Après publication, vous devriez voir dans la console l’URL que vous devez utiliser pour appeler la fonction, comme ceci :

Functions in catfacts-api: 
   main - [httpTrigger]    
    Invoke url: https://catfacts-api.azurewebsites.net/api/{*segments}

Nous pouvons à nouveau lancer curl pour vérifier le déploiement avec l’URL précédente :

curl https://catfacts-api.azurewebsites.net/api/facts/random

Le déploiement du serveur est terminé. ✔️

Préparer le front-end pour la production

Maintenant que notre serveur est déployé, nous devons ajouter la propriété apiUrl à notre configuration d’environnement de production dans src/environments/environment.prod.ts, comme nous l’avons fait précédemment pour le développement :

 

export const environment = {
  production: true, 
 // Utilise l’URL de la function app obtenue à l’étape précédente 
 apiUrl: 'https://catfacts-api.azurewebsites.net/api'
};

Déployer le front-end

La dernière étape consiste à déployer notre application Angular sur Azure. Pour cela, nous allons utiliser la CLI Angular avec le paquet @azure/ng-deploy :

# Veillez à utiliser le nom du compte de stockage créé précédemment
$ ng add @azure/ng-deploy --resourceGroup catfacts --account catfacts

Vous serez invité à vous connecter à votre compte Azure, puis vous devrez choisir le même abonnement utilisé précédemment pour créer les ressources.

Comme nous voulons réutiliser le même compte de stockage que pour le back-end, nous devons activer l’hébergement de sites Web statiques sur ce compte avec cette commande.

# N'oubliez pas de remplacer le nom du compte par celui que vous avez utilisé précédemment
$ az storage blob service-properties update \    
--account-name catfacts \    
--static-website \   
--404-document index.html \    
--index-document index.html

Ensuite, vous pouvez déployer directement votre application depuis la CLI Angular à l’aide de la commande :

$ ng deploy

Votre application sera créée pour l’environnement de production puis déployée sur votre conteneur de sites Web dans Azure Storage.

Une fois le déploiement terminé, ouvrez l’URL renvoyée par l’outil, qui ressemble à https://catfacts.z16.web.core.windows.net/ et voilà.

Mais attendez, ça ne marche pas ! Rien ne s’affiche ? 😱

Si vous ouvrez la console de développement de votre navigateur, vous devriez voir une erreur qui ressemble à cela :

Résoudre les problèmes de CORS

Cette erreur se produit pour des raisons de sécurité, car les navigateurs bloquent les requêtes HTTP des scripts vers des domaines Web différents de celui de la page Web en cours.

Pour contourner cette restriction, votre serveur Web doit définir des en-têtes HTTP spécifiques pour autoriser la requête. Ce mécanisme est appelé Cross-Origin Resource Sharing (CORS).

CORS est déjà activé par défaut sur Azure Functions, mais vous devez ajouter votre domaine de site Web à la liste des origines autorisées à l’aide de cette commande :

# N'oubliez pas de remplacer le nom et l’URL par les vôtres
$ az functionapp cors add \    
--name catfacts-api \   
--resource-group catfacts \    
--allowed-origins https://catfacts.z16.web.core.windows.net

Si vous actualisez votre page Web, vous devriez maintenant voir l’anecdote s’afficher correctement.

Conclusion

Félicitations, vous venez de terminer le déploiement de votre première application complète serverless ! ⚡️💪

Résumons rapidement ce que nous venons de réaliser :

◾ Nous avons généré un nouveau serveur NestJS, créé une API et préparé le déploiement serverless.
◾ Nous avons créé une nouvelle application Web Angular pour consommer votre API et afficher les données, avec diverses configurations d’environnement pour le développement et la production.
◾ Nous avons configuré des ressources Azure et déployé notre application en mode serverless.

Ce billet était un peu long, mais vous avez maintenant une expérience de bout en bout de tout le processus, du démarrage au déploiement d’une application complète serverless. 🚀

Tout le code source de l’application que nous venons de créer se trouve ici sur GithHub.

Pour aller plus loin

Nous n’avons fait qu’effleurer les possibilités sur cet exemple, mais vous avez pu voir à quel point il est facile et rapide de créer et de déployer une application complète avec Angular, NestJS et Azure.

Notre application est très basique pour l’instant, mais il serait assez facile de l’étendre en ajoutant des opérations CRUD sur une base de données, de l’upload de fichiers, etc.

Pour aller plus loin, voici quelques suggestions d’articles à lire :

Getting started with NestJS (Démarrer avec NestJS)
Introducing NoSQL Azure Table Storage for NestJS (Introduction à NoSQL Azure Table Storage pour NestJS)
Azure Storage module for NestJS (Module Azure Storage pour NestJS)
Is Serverless really as cheap as everyone claims ? (Les applications serverless sont-elles aussi économiques qu’on le dit ?)

Pour voir un exemple d’application plus complet avec upload de fichiers et connexion à une base de données Azure Table Storage, vous pouvez regarder le code source de démonstration Nitro Cats.

 

Rapport Magic Quadrant 2019 de Gartner – systèmes de bases de données

Lisez les dernières études pour faire des choix informés sur les solutions de systèmes de gestion de base de données sur ce marché de plus en plus concurrentiel.

Télécharger