Hébergez, prévisualisez et expédiez rapidement des applications SaaS mutualisées
Les technologies modernes renforcent notre expérience de codage quotidienne. Il y a quelques années, nous avions besoin de beaucoup de compétences pour mettre en place un site Web simple et l’héberger. Aujourd’hui encore, beaucoup de gens sont convaincus que pour construire et livrer une application, nous avons besoin d’une équipe multidisciplinaire.
C’est vrai dans de nombreux scénarios complexes, mais pour beaucoup de startups et de projets simples, l’approche que nous avons utilisée est radicalement modifiée. Grâce aux nouvelles technologies, un développeur ou un professionnel ayant des compétences de base en développement peut créer sa propre application.
Dans cet article, nous verrons comment tirer parti des technologies cloud pour mettre en place une application multi-tenant en utilisant simplement Vue.js et quelques lignes de configuration. En d’autres termes, nous apprendrons à créer une application SaaS de base pouvant être utilisée par plusieurs utilisateurs. De plus, cette solution peut être hébergée dans un niveau de frais Vercel et démarrer sans dépenser un centime.
Voyons comment c’est possible.
Dans cet article, nous aborderons de nombreux points qui sont importants pour comprendre la solution. Vous pouvez trouver tout le code source lié à cet exemple sur GitHub (lien en fin d’article). L’application de démonstration est un bloc-notes qui permet aux utilisateurs d’écrire des notes dans le cloud, comme dans les outils de productivité tels qu’Evernote ou One Note.
Puisqu’il est tout à fait impossible d’expliquer tout le code source ligne par ligne, je vais me concentrer sur les étapes les plus importantes :
- Comment implémenter le backend sans être un développeur backend
- Implémenter le frontend
- Comment héberger l’application gratuitement
Commençons étape par étape !
Cette application utilise Vercel pour l’hébergement du frontend et du backend. Au moment où j’écris cet article, Vercel dispose d’un niveau gratuit qui héberge n’importe quelle application frontale (voir page de prix pour voir la limite).
La partie qui était très intéressante est le support des fonctions sans serveur qui, en fait, permettent à l’application d’être entièrement fonctionnelle. Dans le niveau gratuit, nous avons des limites de 100 Go-heures (produit de la RAM utilisée pour chaque demande multipliée par le temps) mais cela peut être surmonté en achetant un forfait payant.
Vercel se connecte directement à votre dépôt git, construit le code source et le publie. Si vous voulez en découvrir plus vous pouvez lire l’article « Déployer une application avec Vercel”.
Puisque tout ce que nous devons savoir a déjà été dit dans l’article mentionné ci-dessus, je n’expliquerai que les étapes les plus importantes.
- Tout d’abord, vous devez créer un projet sur Vercel. Après cela, vous verrez l’écran suivant :
2. Vous devez cliquer sur « Se connecter » et choisir votre référentiel git. Voir l’image suivante
3. Lors du prochain push vers la branche principale, l’application sera publiée. Après cela, vous verrez l’état du projet, le lien et les détails dans le panneau du projet, comme dans l’image suivante.
Maintenant que nous avons trouvé une solution pour l’hébergement, voyons comment implémenter le backend.
L’une des parties les plus critiques est la spécialisation des développeurs. De nombreux développeurs front-end ont de bonnes idées, mais lorsqu’ils commencent à les mettre en œuvre, ils découvrent qu’il est difficile de faire quelque chose sans une solide expérience du back-end.
La plupart du temps, cet écart peut être comblé en adoptant des plates-formes low-code sans tête telles que Larme ou Contenu mais dans certains cas, il peut s’agir d’une dépendance indésirable.
Dans cet exemple, je vais vous montrer comment obtenir un bon compromis en adoptant CrudIt une bibliothèque légère qui encapsule une base de données par des API. En bref, cela signifie que nous avons la possibilité d’accéder à toutes les données à l’aide des API REST. En outre, CrudIt prend en charge un environnement multi-locataire natif avec un db par utilisateur politique. C’est tout ce dont nous avons besoin !
Dans les prochaines étapes, je décrirai comment l’intégrer.
- Courir
npm install --save crudit
. Cette commande installera le package dans votre projet. - Créer un fichier nommé
handler.mjs
dansapi
dossier. Le chemin dans notre local seraapi/handlre.mjs
et l’URL sur Vercel serahttp://yourpath.vercel.io/api/handler
- Ajouter à la
handler.mjs
code suivant :
import {crudy,database} from 'crudit' ;
import {IncomingMessage, ServerResponse} from 'http'
import crypto from 'crypto'crudy.config(function(config){
config.settings.roles=['owner'];
});
export default async function handler(request, response) {
return await crudy.run(request,response);
}
Avec ce code, vous avez encore un point de terminaison de repos qui vous permet d’explorer les données automatiquement. Par exemple, vous pouvez répertorier toutes les données du notes
collecte en invoquant un GET
demande sur http://yourpath.vercel.io/api/handler?collection=notes
.
Conformément à la norme REST, vous pouvez créer une publication pour insérer une nouvelle ligne, un patch pour ne mettre à jour que quelques champs, etc. C’est bien, mais qu’en est-il de la sécurité ? Voyons dans la section suivante
CrudIt implémente son mécanisme d’autorisation basé sur l’utilisateur et les rôles d’utilisateur où vous pouvez spécifier, entité par entité, quelles données peuvent voir un utilisateur. Dans cet exemple, nous voulons créer une base de données pour chaque utilisateur et lui laisser voir tous les contenus qui lui appartiennent.
Pour ce faire, la première étape consiste à implémenter la méthode d’authentification. En supposant que nous ayons une collection qui stocke les utilisateurs et les jetons d’utilisateur, nous avons quelque chose comme ceci :
crudy.authorize(async function(request){
let token=request.headers.authorization ?? request.query.authorization;
let users=await database.aggregate('global','users',[{
$lookup:
{
from: 'tokens',
localField: '_id',
foreignField: 'userId',
as: 'token',
},}, {
"$match": {
"token.token": token
}
}]);
if(users==null || users.length!=1) return null;
let user=users[0];
return {
name:user.name,
roles:['owner'],
database:user.db
};
});
Dans l’extrait de code ci-dessus, la requête prend le jeton d’authentification de la requête, l’utilise pour extraire des données de la base de données (la requête obtient l’utilisateur associé au jeton) et renvoie un objet contenant des données utilisateur. Cet objet est utilisé par le moteur CrudiIt pour permettre à l’utilisateur d’accéder ou non aux données. Bien sûr, vous pouvez remplacer ce code pour prendre en charge tous les jetons JWT ou Oauth2, mais également la connexion à l’ancienne basée sur les cookies ou BasicAuth.
Mais comment implémenter le point de terminaison de connexion et d’enregistrement ? Pour ce faire, vous pouvez implémenter un point de terminaison. Au lieu de créer un nouveau gestionnaire pour Vercel, vous pouvez enregistrer un point de terminaison dans le moteur CrudIt. Cela a aussi l’avantage d’être portable car il ne change pas par le serveur (c’est pareil sur Vercel, express ou tout autre serveur Node.js). Le point de terminaison d’enregistrement peut ressembler à ceci :
crudy.request("register", "post",false,async function(request,loggedUser, settings){
let user=request.body;
user.password= crypto.createHash('md5').update(user.password).digest('hex');
//Create a unique and valid db name escaping the username
user.db=("c_"+user.username??'sdff').normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase()
.trim()
.replace(/[^a-z0-9 ]/g, '')
.replace(/\s+/g, '-');
//save it
user= await database.insert("global","users",user);
return user;
});
L’extrait précédent lie la fonction à la POST
méthode et à l’action register, vous pourrez donc l’appeler par:
curl --location --request POST ' \
--header 'Content-Type: application/json' \
--data-raw '{
"password":"mypass",
"username":"testuser",
"name":"My Name",
}'
Maintenant que nous avons un point de terminaison qui crée un nouvel utilisateur en hachant le mot de passe, nous pouvons implémenter celui de connexion comme suit :
crudy.request("login", "post",false,async function(request,loggedUser, settings){
database.init(process.env.DBURL, {});
let hash= crypto.createHash('md5').update(request.body.password).digest('hex');
let user= await database.search("global","users",{username:request.body.username, password:hash});
if(!user || user.length!=1) throw new Error("Wrong username and password");
user=user[0];
let newToken= crypto.randomBytes(64).toString('hex');
let token={userId: user._id,token: newToken};
token=await database.insert("global","tokens",token);
return {token: newToken};
});
Le script ci-dessus valide le nom d’utilisateur et le mot de passe et si les données sont correctes, il crée un jeton et le renvoie à l’utilisateur. Ce jeton sera passé dans les en-têtes d’authentification et validé par la méthode d’authentification.
Les détails comptent : la multilocation en toute simplicité
Dans la section précédente, nous voyons comment l’utilisateur peut invoquer des API pour s’enregistrer et se connecter. Au fait, il y a un petit détail qui permet de mettre en place un système multi-tenant. Lors de la phase d’inscription, un nom de base de données est attribué à l’utilisateur :
//...
user.db=("c_"+user.username??'sdff')//userneame is used as database name
//...
return {
name:user.name,
roles:['owner'],
database:user.db
};
Ensuite, dans la phase d’authentification, ces informations sont utilisées pour définir avec la base de données l’opération CRUD qui doit être effectuée. Ceci, en conjonction avec le fait que MongoDB est capable de créer une collection et une base de données lorsque les données sont ajoutées, permet de créer un environnement entièrement multitenant avec quelques lignes de code !
Si vous n’êtes pas satisfait, nous pouvons aller plus loin et voir comment utiliser le backend pour créer une véritable application SPA dans Vue.Js ! Commencez simplement à lire la section suivante.
Dans cet exemple, j’ai utilisé une application Vue.js qui utilise Vuetify, un framework qui implémente des modèles de conception de matériaux. Il n’est pas possible de discuter de tous les détails, je me concentrerai donc uniquement sur les parties les plus pertinentes :
- créer une couche de service pour se connecter au backend
- authentifier la demande automatiquement
- implémenter les formulaires de connexion/enregistrement
- implémenter la fonctionnalité de notes
La première étape consiste à ajouter des méthodes communes à tous les composants pour interagir avec le moteur principal. Dans ce cas, je crée un MixIn, qui dans Vue inclut automatiquement les méthodes de tous les composants. Cette fonctionnalité est effectuée par le morceau de code suivant :
Vue.mixin({
methods: {
insertOrUpdate: async function(entity, data) {
//omitted for brevity
},
patch: async function (entity, data){
delete data._meta;
return this.simplifyResponse( await this.apiCall(url,'PATCH',data,{collection: entity}));},
remove: async function (entity, data){
//omitted
},
get: async function (entity, id){
//omitted
},
search: async function (entity, filter, projection){
return this.simplifyResponse(await this.apiCall(url,'GET',null,
{collection: entity,
query: JSON.stringify(filter)??'{}',
projection: JSON.stringify(projection)??'{}'
}));
},
},
});
Chaque méthode encapsule un appel HTTP au point de terminaison de l’API et nous avons toutes les opérations CRUD prêtes.
Maintenant que nous pouvons opérer sur la base de données, nous pouvons nous occuper de l’authentification. Ceci peut être réalisé globalement en ajoutant un intercepteur HTTP Axios. En utilisant le morceau de code suivant, si vous obtenez un 401
(non connecté ou non autorisé), vous serez redirigé vers la page de connexion. De plus, lorsque l’utilisateur est connecté, le jeton d’utilisateur est ajouté automatiquement aux demandes.
axios.interceptors.request.use(request => {
let userToken=localStorage.getItem('userToken');
if (userToken) {
request.headers.Authorization = `${userToken}`;
}
return request;
});axios.interceptors.response.use(function (response) {
return response;
}, function (error) {
if (error.response && 401 === error.response.status) {
document.location.href="/#/login";
} else {
return Promise.reject(error);
}});
Maintenant, nous pouvons implémenter un formulaire de connexion et un formulaire d’inscription. Les deux sont de simples composants Vue, je ne rapporterai donc ici que celui de connexion (vous pouvez trouver tout le code source sur GitHub). Le modèle que j’ai utilisé est le suivant
<v-container>
<v-row class="text-center">
<v-col>
<h1>login</h1>
<v-form>
<v-text-field v-model="username" type="text" label="login" />
<v-text-field v-model="password" type="password" label="Password"/>
<v-btn @click="save" block >Save</v-btn>
</v-form>
</v-col>
</v-row><v-row class="text-center">
<v-col>
<v-subheader> Not an user? <a href="#" @click="$router.push({name:'register'})">Register</a></v-subheader>
</v-col>
</v-row>
</v-container>
Et la partie javascript est décrite par le javascript suivant :
export default {
name: 'Login',
beforeMount:async function(){},
methods:{
save: async function(data){
let response=await this.apiCall('/api/handler','POST',{username:this.username, password:this.password},{ action:"login"});
if(response.status !=200 || response.data.hasError)
{
this.showError("login failed");
}
else
{
let user=response.data.data;
this.showSuccess("Hello "+user.name);
localStorage.setItem('userToken',user.token);
this.$router.push({name:'notes'});
}
}
},
data: () => ({
token:"",
username:"",
password:"",
})
}
Enfin, nous pouvons implémenter notre application en utilisant le backend déjà en place !
Grâce aux technologies modernes, il n’est plus nécessaire d’être un spécialiste pour créer une application multi-tenant et grâce aux services cloud, l’hébergement n’est plus pénible.
Bien sûr, lorsque les exigences de l’application deviennent difficiles, nous devrons faire face à la complexité (le coût du cloud deviendra pertinent et vous aurez toujours besoin de plus d’un membre dans l’équipe).
Cependant, pour un projet simple, un MVP ou une startup ayant un moyen simple de démarrer, c’est très important. Au fur et à mesure que le projet devient plus exigeant, nous devrons adapter l’application à une croissance durable.
Références:
- Le framework CrudIt est disponible sur GitHub et fourni par NPM. Voir la page du projet pour plus d’informations https://github.com/zeppaman/crudit
- Vous pouvez en savoir plus sur Vercel sur le site officiel
- Vous pouvez télécharger le code source de cet exemple ici.