Dans ce tutoriel, nous allons aborder des technologies très en vogue : MEAN Stack. Derrière cet acronyme vous découvrirez des outils très complémentaires qui permettent de lancer un projet du front jusqu'au back en passant par la base de données en quelques heures.
Les prérequis pour ce tuto sont :
- être curieux
- avoir envie d'apprendre
- ne pas avoir peur d'essayer, quitte à se tromper et à réessayer
- savoir chercher de l'information en autonomie (Google, ses collègues ...)
Connaître un peu de HTML et de Javascript pourra aussi vous aider.
L'acronyme MEAN signifie "MongoDB Express.js AngularJS Node.js" et représente un groupe de technologies qui vont bien ensemble. Le principal avantage de la Stack MEAN est qu'il est extrêmement rapide de prototyper avec. Node.js va nous permettre d'utiliser Javascript dans le backend, et Angular de même pour le frontend. Cela va nous économiser du temps d'apprentissage d'autres langages et/ou bibliothèrques externes. D'autre part, la nature NoSQL de MongoDB va nous permettre de modifier rapidement et fréquemment notre modèle de données sans avoir à se soucier des migrations.
Si vous voulez tester un projet, ces technos feront le job. Attention, prototyper ne veut pas dire coder comme un cochon. Toujours de la rigueur, des commentaires et une architecture qui évolue avec l'application.
Avant de poursuivre, il est important de présenter le Pattern MVC.
Le patron d'architecture logicielle modèle-vue-contrôleur (en abrégé MVC, en anglais model-view-controller) est un modèle destiné à répondre aux besoins des applications interactives en séparant les problématiques liées aux différents composants au sein de leur architecture respective.
Ce paradigme regroupe les fonctions nécessaires en trois catégories :
- un modèle (modèle de données) ;
- une vue (présentation, interface utilisateur) ;
- un contrôleur (logique de contrôle, gestion des événements, synchronisation).
Le modèle représente le cœur (algorithmique) de l'application : traitements des données, interactions avec la base de données, etc. Il décrit les données manipulées par l'application. Il regroupe la gestion de ces données et est responsable de leur intégrité. La base de données sera l'un de ses composants.
Ce avec quoi l'utilisateur interagit se nomme précisément la vue. Sa première tâche est de présenter les résultats renvoyés par le modèle. Sa seconde tâche est de recevoir toute action de l'utilisateur (hover, clic de souris, sélection d'un bouton radio, cochage d'une case, entrée de texte, de mouvements, de voix, etc.). Ces différents événements sont envoyés au contrôleur. La vue n'effectue pas de traitement, elle se contente d'afficher les résultats des traitements effectués par le modèle et d'interagir avec l'utilisateur.
Le contrôleur prend en charge la gestion des événements de synchronisation pour mettre à jour la vue ou le modèle et les synchroniser. Il reçoit tous les événements de la vue et enclenche les actions à effectuer. Si une action nécessite un changement des données, le contrôleur demande la modification des données au modèle afin que les données affichées se mettent à jour.
- https://fr.wikipedia.org/wiki/Mod%C3%A8le-vue-contr%C3%B4leur
- https://openclassrooms.com/courses/concevez-votre-site-web-avec-php-et-mysql/organiser-son-code-selon-l-architecture-mvc
Angular.js est un MVW (Model-View-Whatever) open-source JavaScript web framework pour faciliter la création de single-page applications (SPA). Son slogan : “HTML enhanced for web apps!”. Il ajoute de nouveaux tags HTML et propriétés pour lier (en anglais "to bind") les évènements et les données en utilisant Javascript.
Pour faire court, Angular permet :
- Manipulation du DOM
- Compatible avec la majorité des navigateurs
- Test unitaires (Unit testing) faciltité en gardant la manipulation du DOM et la logique séparée.
- Expressions et méthodes dans des controlleurs afin de maîtriser la portée de celle-ci et de ne pas contaminer tout projet.
- Modularité : directives, filtres et data-bindings
La première étape est de référencer Angular.js. On ajoute la balise script
à la fin.
On ajoute également l'attribut ng-app
au body
. Cela indique à Angular quelle partie du code Html sera à lier avec les controlleurs.
Puis on ajoute l'attribut ng-model
à un élément input
. Cela permet de lier le modèle name
à la valeur de cet input
. Dès que le champ sera modifié, le modèle sera automatiquement mis à jour.
Enfin, les doubles curly braces {{ }}
indiquent à Angular de le remplacer par la valeur de la variable. Dans notre cas, c'est name
.
<!doctype html>
<html>
<head>
</head>
<body ng-app="simplonApp">
<div>
<label>Name:</label>
<input type="text" ng-model="name" placeholder="Enter a name here">
<hr>
<h1>Hello {{ name }}!</h1>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular.min.js"></script>
</body>
</html>
Avec ce bout de code, nous avons la version la plus basique d'Angular. Dès qu'on rentre du texte dans l'input (lié à la variable name), la variable est automatiquement mise à jour et affichée dans le h1
.
Dans les coulisses, Angular va écouter tous les changements apportés à l'input (mais surtout au modèle qu'on y a associé) et mettre à jour cette donnée à tous les endroits où elle apparaît.
Maintenant, nous allons ajouter un controlleur afin de mettre davantage d'intelligence à notre application. Pour cela, nous allons créer un fichier controller.js
que nous allons référencer dans notre page.
Notre controlleur va récupérer une liste d'objets que nous allons afficher sur notre page grâce à l'attribut ng-repeat
(qui se comporter comme une boucle 'for').
<!-- index.html -->
<!doctype html>
<html>
<head>
</head>
<body ng-app="simplonApp">
<h2>Todo</h2>
<div ng-controller="SimplonController">
<span>Il reste {{ SimplonController.remaining()}} tâche(s) sur un total de {{ SimplonController.tasks.length }} </span>
<ul>
<li ng-repeat="task in SimplonController.tasks">
<input type="checkbox" ng-model="task.done">
<span class="done-{{ task.isDone }}">{{ task.text }}</span>
</li>
</ul>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular.min.js"></script>
<script src="controller.js"></script>
</body>
</html>
Et voici le code du controlleur controller.js
.
// controller.js
angular.module('simplonApp', [])
.controller('SimplonController', function() {
var todoList = this;
// Tableau qui contient les tâches à réaliser
todoList.tasks = [
{text:'comprendre le pattern MVC', isDone:true},
{text:'faire le tuto sur Angular', isDone:false},
{text:'Se préparer à la master class', isDone:false},
];
// Fonction qui renvoie le nombre de tâches restantes (qui ne sont pas 'done')
todoList.remaining = function() {
var count = 0;
angular.forEach(todoList.tasks, function(task) {
count += task.isDone ? 0 : 1;
});
return count;
};
});
Comme vous pouvez le constater, on ajoute un controlleur (nommé 'SimplonController') à l'application Angular 'simplonApp'. A termes, on pourra ajouter plusieurs controlleurs à notre application.
On définit ensuite une variable tasks
qui est un tableau d'objets. Cest objects comportent deux propriétés : text
et isDone
.
On définit également une fonction remaining
qu'on peut appeler directement dans le Html.
Dans le code HTML, on récupère ces données via le controlleur. Tout simplement.
Nous venons de voit rapidement quelques notions fondamentales liées à Angular. Il y en a beaucoup d'autres. Avant de poursuivre, nous précisons ici deux notions qui nous semblent primordiales.
La synchronisation automatique entre la donnée et le HTML s'appelle le * Data Binding*. C'est vraiment pratique car vous n'avez pas à penser à mettre à jour le code HTML quand la donnée change (rappeler vous avec jQuery...).
Nous n'avons pas abordé cet objet $scope
. Néanmoins, ce scope (la portée en anglais) est la glue entre le controlleur Js et la vue Html. C'est lui qui s'occupe de faire en sorte que les deux communiquent et se mettent à jour en fonction des évènements de l'un ou de l'autre.
- https://angularjs.org/
- http://adrianmejia.com/blog/2014/09/28/angularjs-tutorial-for-beginners-with-nodejs-expressjs-and-mongodb/
Maintenant que la devanture est prête, nous allons nous intéresser à l'arrière boutique et à la réserve de données.
Pour mettre en place notre backend, nous allons nous baser sur Node.js (via Express.js) and MongoDB ( via mongoose).
Nous allons mettre en place une API RESTful.
Une API est une Interface de Programmation (Application Programming Interface). Pour faire simple, c'est un ensemble de fonctions qui sert de façade à travers laquelle le provider (le fournisseur) va fournir un ensemble de services. Par exemple, Twitter a mis en place une API qu'on peut interroger afin de récupérer des informations comme le fil d'un utilisateur. Concrètement, Twitter a définit ce qui suit : en allant taper sur
https://api.twitter.com/users/034582742/tweet
vous allez récupérer le dernier tweet de l'utilisateur n°034582742 (ceci est un exemple et cet URL ne correpond pas à celles définies dans Twitter API).
REST (representational state transfer) est un style d'architecture (pattern). Un système qui s'appuie dessus est appelé 'RESTful'. Typiquement, dans le dévelopmment de notre application on fera attention au Separation Of Concerns (SOC) et on séparera le client (qui consulte) au serveur (qui fournit la donnée). Un autre point important est l'aspect 'sans état' (stateless). Chaque requête du client contient toutes les informations nécessaires et ne doit pas dépendre d'une requête précédente. On s'appuyera fortement sur les différentes méthodes HTTP pour expliciter l'action que l'on souhaite. Par exemple pour récupérer la liste des stagiaires de la promotion de Simplon Toulouse, on effectuera en HTTP :
GET api.simplon.co/toulouse/stagiaires
Pour ne sélectionner qu'un seul élève (dont l'id est 98) :
GET api.simplon.co/toulouse/stagiaires/98
Pour supprimet cet élève, on utilisera la méthode HTTP DELETE.
DELETE api.simplon.co/toulouse/stagiaires/98
Dans tous les cas, les requêtes sont lisibles et parlent d'elle même : SUPPRIMER dans simplon.co, essaimage Toulouse, un des stagiaires, dont l'id est 98. Cela facilite la maintenabilité.
NB : Dans cette mini API, nous avons choisi de distinguer en premier les promotions, puis de préciser les stagiaires qui y sont inclus : /toulouse/stagiaires Mais nous aurions très bien pu avoir une approche différente qui reste correcte :
api.simplon.co/stagiaires/toulouse/
En lisant simplement cette URL, on comprend : dans simplon.co, récupères tous les stagiaires, puis ne récupèrent que ceux présents sur Toulouse. En fonction des problématiques, la première ou la seconde approche sera à privilégier. Encore une fois, ce sont des pattern. Chaque développeur l'intègre à sa convenance (enfin, surtout selon ses besoins et de ses problématiques).
- https://fr.wikipedia.org/wiki/Interface_de_programmation
- https://fr.wikipedia.org/wiki/Representational_State_Transfer
Revenons à nos moutons et installons les 3 outils que nous allons utiliser.
MongoDB est une base de données NoSQL orientée document. C'est bon pour vous ? On passe à la suite ?
Pour faire court, c'est une base de donnée qui stocke les données dans un format proche de JSON.
On va suivre les différentes étapes (pour Ubuntu 14.04) indiquées sur leur doc officielle https://docs.mongodb.org/manual/tutorial/install-mongodb-on-ubuntu/
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv EA312927
echo "deb http://repo.mongodb.org/apt/ubuntu trusty/mongodb-org/3.2 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.2.list
sudo apt-get update
sudo apt-get install -y mongodb-org
Pour démarrer MongoDB, il faut lancer la commande suivante :
sudo service mongod start
NodeJS est un environnement qui exécute des applications serveur (backend) écrites en Javascript.
Pour l'installer :
sudo apt-get update
sudo apt-get install nodejs
Il se peut que vous rencontriez une erreur du style :
sh: 1: node: not found
npm WARN This failure might be due to the use of legacy binary "node"
npm WARN For further explanations, please read /usr/share/doc/nodejs/README.Debian
sudo apt-get install nodejs-legacy
Vous connaissez déjà NPM, le package manager basé sur Node.
sudo apt-get install npm
ExpressJS est un framework web basé sur NodeJS. Il se revendique rapide et minimaliste (parfait pour mettre en place rapidement une API).
C'est une surcouche à NodeJS qui va permettre de définir des endpoints simplement à notre backend. NodeJS est le moteur avec un ensemble de piston et de câbles. ExpressJS est comme le tableau de bord. Il ajoute une couche d'abstraction en simplifiant l'accès aux fonctions de NodeJS (dans notre cas, les fonctions liées à la mise en place d'une API).
npm install -g express
Mongoose est un ODM (Object Data Manager). MongoDB et NodeJS sont deux logiciels différents écrits dans des langages différents et qui n'ont rien à voir. Leurs buts respectifs diffèrent complètement. Par contre, les associer est intéressant. Mais il faut une couche intermédiaire afin de pouvoir communiquer entre ces deux entités. C'est le but de Mongoose.
Mongoose va permettre d'utiliser une base de données MongoDB depuis un serveur NodeJS.
npm install mongoose
La suite est en cours de rédaction...
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/todoApp');
var TodoSchema = new mongoose.Schema({
name: String,
completed: Boolean,
note: String,
updated_at: { type: Date, default: Date.now },
});
var Todo = mongoose.model('Todo', TodoSchema);
var todo = new Todo({name: 'Master NodeJS', completed: false, note: 'Getting there...'});
todo.save(function(err){
if(err)
console.log(err);
else
console.log(todo);
});
// Find All
Todo.find(function (err, todos) {
if (err) return console.error(err);
console.log(todos)
});
var callback = function (err, data) {
if (err) return console.error(err);
else console.log(data);
}
// Get all completed tasks
Todo.find({completed: true }, callback);
// Get all tasks ending with `JS`
Todo.find({name: /JS$/ }, callback);
var oneYearAgo = new Date();
oneYearAgo.setYear(oneYearAgo.getFullYear() - 1);
// Get all tasks staring with `Master`, completed
Todo.find({name: /^Master/, completed: true }, callback);
// Get all tasks staring with `Master`, not completed and created from year ago to now...
Todo.find({name: /^Master/, completed: false }).where('updated_at').gt(oneYearAgo).exec(callback);
// Update
// Model.update(conditions, update, [options], [callback])
// update `multi`ple tasks from complete false to true
Todo.update({ completed: false }, { completed: true }, { multi: true }, function (err, numberAffected, raw) {
if (err) return handleError(err);
console.log('The number of updated documents was %d', numberAffected);
console.log('The raw response from Mongo was ', raw);
});
//Model.findOneAndUpdate([conditions], [update], [options], [callback])
Todo.findOneAndUpdate({name: /JS$/ }, {completed: false}, callback);
// Middleware
app.get('/todos/:id', function (req, res, next) {
Todo.findById(req.params.id, function(err, todo){
if(err) res.send(err);
res.json(todo);
});
});
Au step 1, il faut rajouter le script qui appelle controller.js
et ça marchera