Le Framework JavaScript Serveur Évolutif
Au point avec HTML & CSS ?
Débutant en JavaScript ?
Réalisez rapidement des sites vitrines multilingues sans effort avec l'utilisation de routes, vues ou variations.
Expert en JavaScript client ?
Prêt à embrasser Node.js ?
Améliorer progressivement votre base à mesure de vos besoins en utilisant des contrôleurs, modèles ou modules.
Déjà vos habitudes Front-end ?
Habitué(e) du Data Binding ?
Du léger Vanilla au simple jQuery en passant par Vue, Angular ou React : utiliser vos bibliothèques clientes favorites !
NodeAtlas offre également tout un système de fonctionnalités de développement et de packaging à travers son sytème de configuration. Voyons cela.
assetsRelativePath
Pour afficher une page personnalisée quand une ressource n'est pas trouvée il faut :
pageNotFound
avec comme value
la key
de la page 404 préparée.Voyez l'exemple ci-dessous :
{
"pageNotFound": "/pages-inexistantes/",
"routes": {
"/liste-des-membres/": {
"view": "members.htm"
},
"/": {
"view": "index.htm"
},
"/pages-inexistantes/": {
"view": "error.htm",
"statusCode": 404
}
}
}
vous pourrez accéder à :
Il vous suffit de créer une nouvelle route finissant par *
dans la langue souhaitée.
Voyez l'exemple ci-dessous :
{
"pageNotFound": "/pages-inexistantes/",
"languageCode": "fr-fr",
"routes": {
"/liste-des-membres/": {
"view": "members.htm",
"variation": "members.json"
},
"/": {
"view": "index.htm",
"variation": "index.json"
},
"/pages-inexistantes/": {
"view": "error.htm",
"variation": "error.json",
"statusCode": 404
},
"/english/list-of-members/": {
"view": "members.htm",
"languageCode": "en-gb",
"variation": "members.json"
},
"/english/": {
"view": "index.htm",
"languageCode": "en-gb",
"variation": "index.json"
},
"/english/*": {
"view": "error.htm",
"languageCode": "en-gb",
"variation": "error.json",
"statusCode": 404
}
}
}
Bien que vous puissiez paramétrer des URL statiques, vous pouvez également paramétrer une écoute d'URL dynamiques !
Il est possible de récupérer des paramètres de l'URL pour afficher un contenu différent en fonctions de ces paramètres.
Avec la configuration suivante :
{
"routes": {
"/liste-des-membres/:member/:action/": {
"view": "members.htm",
"controller": "members.js"
},
"/liste-des-membres/:member/:action": {
"view": "members.htm",
"controller": "members.js"
},
"/liste-des-membres/:member/": {
"view": "members.htm",
"controller": "members.js"
},
"/liste-des-membres/:member": {
"view": "members.htm",
"controller": "members.js"
},
"/liste-des-membres/": {
"view": "members.htm",
"controller": "members.js"
},
"/liste-des-membres": {
"view": "members.htm",
"controller": "members.js"
},
"/": {
"view": "index.htm"
}
}
}
vous pourrez accéder à :
test=This+is+a+test
)et récupérer les valeurs de :member
, :action
, example
et test
dans le changeVariations
(commun et specifique).
exports.changeVariations = function (next, locals, request, response) {
console.log("param request:", request.params.member);
// $ undefined, 'toto', 'bob-eponge99', 'node-atlas' or 'etc'.
console.log("param locals:", locals.params.member);
// $ undefined, 'toto', 'bob-eponge99', 'node-atlas' or 'etc'.
console.log("param request", request.params.action);
// $ undefined, 'show' or 'lolol'.
console.log("param locals", locals.params.action);
// $ undefined, 'show' or 'lolol'.
console.log("query request", request.query.example);
// $ undefined or 'test'
console.log("query locals", locals.query.example);
// $ undefined or 'test'
console.log("body request", request.body.test);
// $ undefined or 'This is a test'.
console.log("body locals", locals.body.test);
// $ undefined or 'This is a test'.
next();
};
Nous voyons que nous utilisons une configuration identique pour trois routes dans l'exemple précédent. Vous pouvez également vous aider d'expressions régulières pour définir ce qui peut varier pour accéder à votre URL ou préciser quels sont les paramètres valides dans l'URL. Ce sytème est simplifié étant donné que beaucoup de caractères ne se trouvent pas dans l'URL. Il n'est donc pas nécéssaire par exemple d'échaper les /
par exemple.
Avec la configuration suivante :
{
"routes": {
"/liste-des-membres/?(:member([-a-zA-Z0-9]+)/?(:action(afficher|editer)/?)?)?": {
"view": "members.htm"
},
"/": {
"view": "index.htm"
}
}
}
vous pourrez accéder à :
test=This+is+a+test
)et récupérer les valeurs de :member
, :action
, example
et test
dans une vue.
<!DOCTYPE html>
<html lang="fr-fr">
<head>
<meta charset="utf-8">
<title>URL Rewriting Test</title>
</head>
<body>
Member: <strong><?- params.member ?></strong><br>
Action: <strong><?- params.action ?></strong><br>
Example: <strong><?- query.example ?></strong><br>
Test: <strong><?- body.test ?></strong>
</body>
</html>
vous ne pourrez pas accéder à :
Vous pouvez également activer les expressions régulières pour un chemin précis avec regExp
. Si celui-ci vaut true
, le précédent mode ne fonctionne plus et vous passez en mode expression régulière. Si regExp
est une chaine de caractères, celle-ci fait office de flag (g, i, m ou y).
Voyez la configuration suivante :
{
"routes": {
"/liste-des-membres/([-a-z0-9]+)/?": {
"view": "members.htm",
"regExp": "i"
},
"/liste-des-membres/?": {
"view": "members.htm",
"regExp": true
},
"/": {
"view": "index.htm"
}
}
}
vous pourrez accéder à :
et récupérer les valeurs de ([-a-z0-9]+)
dans le changeVariations
(commun et spécifique).
exports.changeVariation = function (next, locals) {
if (locals.params && locals.params[0]) { locals.params.member = locals.params[0]; }
// locals.params[1] pour le deuxième match, etc...
console.log(locals.params.member);
// $ 'toto', 'bob-eponge99', 'node-atlas' ou 'etc'.
next();
}
Les règles de création d'URL dynamiques avec regExp
sont celles des RegExp JavaScript.
Nous avons pu voir qu'avec setRoutes
il était possible d'injecter dynamiquement des routes. Cependant, l'injection de route ne se fait qu'à la fin car NA.webconfig.routes
est un objet. Il n'y a donc pas de moyen d'ordonner les routes, ce qui est génant car les routes sont résolue dans l'ordre dans lesquels elles ont été injectées.
Nous allons résoudre ça en changeant la manière de créer les routes de routes: { <key>: { ... } }
à routes: [{ "key": <key>, ... }]
.
Voici l'ensemble de fichier suivant :
├─ controllers/
│ └─ common.js
├─ views/
│ ├─ index.htm
│ ├─ content.htm
│ └─ error.htm
└─ webconfig.json
Avec le webconfig.json
initialement comme ceci avec routes: <Object>
:
{
"controller": "common.js",
"routes": {
"/doc/index.html": {
"view": "index.htm"
},
"/doc/*": {
"view": "error.htm",
"statusCode": 404
}
}
}
se transformant en cela avec routes: <Array>
:
{
"controller": "common.js",
"routes": [{
"url": "/doc/index.html",
"view": "index.htm"
}, {
"url": "/doc/*",
"view": "error.htm",
"statusCode": 404
}]
}
Avec le fichier common.js
nous pouvons maintenant injecter les routes à des positions précises. Nous allons les ajouter au début.
// On intervient au niveau des routes pendant qu'elles sont ajoutées.
// Ce code sera exécuté au lancement de NodeAtlas.
exports.setRoutes = function (next) {
// On récupère l'instance de NodeAtlas en cours.
var NA = this,
// Et nous récupérons les routes en provenance du webconfig...
route = NA.webconfig.routes;
// ...pour ajouter la route `/content.html` au débuts de nos routes.
route.unshift({
"url": "/doc/content.html",
"view": "content.htm"
});
// On redonne la main à NodeAtlas pour la suite.
next();
};
De cette manière l'adresse http://localhost/doc/content.html
renverra la vue content.htm
et non la vue error.htm
en 404.
Pour aller à une autre adresse (redirection 301 ou 302) quand vous arrivez à une url il faut utiliser le paramètre redirect
.
Note : si vous ne précisez pas un statusCode
, la redirection ne se fera pas. Le statusCode
est obligatoire.
Voyez l'exemple ci-dessous :
{
"routes": {
"/liste-des-membres/": {
"view": "members.htm"
},
"/liste-des-membres": {
"redirect": "/liste-des-membres/",
"statusCode": 301
},
"/aller-sur-node-atlas/": {
"redirect": "https://node-atlas.js.org/",
"statusCode": 302
},
"/": {
"view": "index.htm"
}
}
}
Vous serez redirigé :
http://localhost/liste-des-membres/
quand vous accéderez à http://localhost/liste-des-membres
avec une entête redirection permanente.https://node-atlas.js.org/
quand vous accéderez à http://localhost/aller-sur-node-atlas/
avec une entête redirection temporaire.Voyez l'exemple ci-dessous :
{
"routes": {
"/liste-des-membres/:member/": {
"view": "members.htm"
},
"/liste-des-membres/:member": {
"redirect": "/liste-des-membres/:member/",
"statusCode": 301
},
"/": {
"view": "index.htm"
}
}
}
Vous serez redirigé sur http://localhost/liste-des-membres/machinisteweb/
quand vous accéderez à http://localhost/liste-des-membres/machinisteweb
avec une entête redirection permanente.
Voyez l'exemple ci-dessous :
{
"routes": {
"/membres/([-a-z0-9]+)/": {
"view": "members.htm",
"regExp": true
},
"/liste-des-membres/([-a-z0-9]+)/": {
"redirect": "/membres/$0/",
"statusCode": 301,
"regExp": true
},
"/liste-des-membres/": {
"view": "members.htm"
},
"/": {
"view": "index.htm"
}
}
}
Vous serez redirigé sur http://localhost/membres/machinisteweb/
quand vous accéderez à http://localhost/liste-des-membres/machinisteweb/
avec une entête redirection permanente.
Pour le second match utilisez $1, pour le troisième $2, etc.
Par défaut, les entêtes HTTP envoyées par NodeAtlas sont les suivantes : Content-Type:text/html; charset=utf-8
avec un statusCode
à 200.
Il est tout à fait possible de modifier ces valeurs pour une entrée de route (pour des APIs local au site).
{
"mimeType": "application/json",
"charset": "utf-16",
"routes": {
"/": {
"view": "index.htm",
"mimeType": "text/html"
},
"/api/articles": {
"view": "display-json.htm",
"controller": "blog/list-of-articles.js",
"charset": "utf-8",
"statusCode": 203
}
}
}
Il est également possible de modifier complètement les entêtes, ce qui écrase toutes les autres valeurs de headers (à l'exception du statusCode
donc). Mettre une valeur à false
retire le Headers précédemment mis en place.
{
"headers": {
"Content-Type": "application/json; charset=utf-8",
"Access-Control-Allow-Origin": "*"
},
"routes": {
"/api/articles": {
"view": "display-json.htm",
"controller": "blog/list-of-articles.js",
"statusCode": 203,
"headers": {
"Access-Control-Allow-Origin": false
}
}
}
}
Plutôt que d'utiliser plusieurs configurations statiques .json
, il est tout a fait possible d'utiliser des configurations dynamiques .js
. Dans ce cas, ce que votre fichier .js
devra retourner avec module.exports
sera un objet JSON valide.
Nous pouvons ainsi aisément remplacer les six fichiers suivants :
webconfig.json
{
"languageCode": "fr-fr",
"statics": "statics.fr-fr.json",
"routes": {
"/": "index.htm"
}
}
webconfig.prod.json
{
"cache": true,
"languageCode": "fr-fr",
"statics": "statics.fr-fr.json",
"routes": {
"/": "index.htm"
}
}
webconfig.en-us.json
{
"languageCode": "fr-fr",
"statics": "statics.fr-fr.json",
"routes": {
"/": "index.htm"
}
}
webconfig.en-us.prod.json
{
"cache": true,
"languageCode": "en-us",
"statics": "statics.en-us.json",
"routes": {
"/": "index.htm"
}
}
statics.fr-fr.json
{
"/variations/": "variations/fr-fr/",
}
statics.en-us.json
{
"/variations/": "variations/en-us/",
}
par les deux fichiers suivants :
webconfig.js
module.export = (function () {
var webconfig = {
"cache": false,
"languageCode": "fr-fr",
"statics": "statics.json"
"routes": {
"/": "index.htm"
}
};
if (process.env.NODE_ENV === 'production') {
webconfig["cache"] = true;
}
if (process.env.LANG) {
webconfig["languageCode"] = process.env.LANG;
}
return webconfig;
}());
statics.js
module.export = (function () {
var NA = this.NA,
languageCode = NA.webconfig.languageCode
return {
"/variations/": "variations/" + languageCode + "/",
};
}());
en supposant les variables d'environnements suivantes pour les quatre environnements suivants :
Local FR
NODE_ENV=DEVELOPMENT
LANG=fr-fr
Local EN
NODE_ENV=DEVELOPMENT
LANG=en-us
Prod FR
NODE_ENV=PRODUCTION
LANG=fr-fr
Prod EN
NODE_ENV=PRODUCTION
LANG=en-us
Il est très simple de faire tourner une instance de NodeAtlas avec le protocol HTTPs. Pour cela il suffit de créer, par exemple un dossier security
dans lequel vous allez placer vos fichiers server.key
et server.crt
afin d'alimenter le protocol.
Il ne vous reste plus qu'à utiliser la configuration suivante :
{
"httpSecure": true,
"httpSecureKeyRelativePath": "security/server.key",
"httpSecureCertificateRelativePath": "security/server.crt",
"routes": {
"/": {
"view": "index.htm"
}
}
}
Vous pouvez également, si —comme c'est le cas ici— vos deux fichiers .key
et .crt
portent le même nom, utiliser cette configuration :
{
"httpSecure": "security/server",
"routes": {
"/": {
"view": "index.htm"
}
}
}
Pour finir, il est également possible de seulement laisser la valeur de httpSecure
à true
pour obtenir un https
dans vos chemins comme urlBasePath
ou urlBase
. Cependant le serveur ce lancera en HTTP, il vous faudra un proxy qui gère pour vous la lecture du certificat.
{
"httpSecure": true,
"routes": {
"/": {
"view": "index.htm"
}
}
}
Note : en production, si vous redirigez un proxy vers votre instance de NodeAtlas, n'oubliez pas qu'en HTTPs ce n'est pas urlPort: 80
mais urlPort: 443
Vous pouvez également manager la manière dont le serveur va répondre aux demandes GET/POST pour une page donnée. Par exemple, nous allons autoriser l'accès aux pages uniquement en GET pour tout le site et autoriser un POST pour une page seulement (et même lui interdire le GET).
{
"get": true,
"post": false,
"routes": {
"/": {
"view": "index.htm"
},
"/liste-des-membres/": {
"view": "members.htm"
},
"/rediger-commentaire/": {
"view": "write-com.htm"
},
"/commentaire-sauvegarde/": {
"view": "save-com.htm",
"get": false,
"post": true
}
}
}
Note : si rien n'est précisé, get
et post
sont à true
au niveau global et par page.
Fonctionnant exactement de la même manière que get
et post
, les deux actions HTTP PUT et DELETE qui part défaut ne sont pas activé peuvent être activé avec put
et delete
.
{
"get": false,
"post": false,
"put": true,
"routes": {
"/read-all-entry/": {
"view": "display-json.htm",
"variation": "all-entry.json",
"get": true,
"put": false
},
"/read-entry/:id/": {
"view": "display-json.htm",
"variation": "entry.json",
"get": true,
"put": false
},
"/create-entry/:id/": {
"view": "display-json.htm",
"variation": "entry.json",
"post": true,
"put": false
},
"/update-entry/:id/": {
"view": "display-json.htm",
"variation": "entry.json"
},
"/delete-entry/:id/": {
"view": "display-json.htm",
"variation": "entry.json",
"delete": true,
"put": false
}
}
}
Avec la configuration ci-dessus, seulement une action HTTP n'est possible par entrée, cela permet de faire des APIs REST facilement avec NodeAtlas.
Par défaut, les requêtes pré-vérifiées ("preflighted requests") ne sont pas activées. Vous allez en avoir besoin pour, par exemple, effectuer des requêtes Cross-Domain ou CORS. Les requêtes pré-vérifiées se font avec la méthode HTTP OPTIONS.
Pour activer OPTIONS sur une route, utilisez la propriété options
sur une route dans le webconfig. Pour activer OPTIONS sur toutes les routes, utilisez alors la propriété options du webconfig sur la configuration global.
{
"options": true,
"routes": {
"/read-all-entry/": {
"view": "display-json.htm",
"variation": "all-entry.json",
"options": false
},
"/create-entry/:id/": {
"view": "display-json.htm",
"variation": "entry.json",
"post": true
},
"/delete-entry/:id/": {
"view": "display-json.htm",
"variation": "entry.json",
"delete": true
}
}
}
Si vous souhaitez authoriser une ressource du serveur NodeAtlas au requête en provenance de www.domain-a.com
pour une page précise, vous pouvez le faire ainsi :
{
"routes": {
"/api/random-quote": {
"controller": "get-quote.js",
"headers": {
"Access-Control-Allow-Origin": "http://www.domain-a.com"
}
}
}
}
Ainsi vous pourrez par exemple accepter la requête suivante qui a pour Origin
, http://www.domain-a.com
qui est donc une valeur de Access-Control-Allow-Origin
:
GET /api/random-quote HTTP/1.1
Host: www.domain-a.com
...
Origin: http://www.domain-a.com
...
Demande cross-domain avec jeton
Si vous souhaitez authoriser des ressources du serveur NodeAtlas aux requêtes en provenance de n'importe quel domaine externe pour la page /api/random-quote
et une page qui attend un jeton d'authentification pour la page /api/protected/random-quote
, vous pouvez le faire ainsi :
{
"mimeType": "application/json",
"headers": {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "Authorization"
},
"routes": {
"/api/random-quote": {
"controller": "get-quote.js"
},
"/api/protected/random-quote": {
"controller": "get-quote.js",
"middlewares": "is-authenticated.js",
"options": true
}
}
}
Faire lire un jeton à NodeAtlas depuis un domaine externe nécessite de lui passer l'en-tête HTML Authorization
. Pour que celle-ci soit acceptée par NodeAtlas il faut le définir avec Access-Control-Allow-Headers
acceptant Authorization
. L'envoi d'un jeton nécessitant une requête pré-vérifiée, il faut également mettre options
à true
pour autoriser les requêtes HTTP avec la méthode OPTIONS.
Ainsi vous pourrez par exemple accepter la requête suivante qui passe un jeton d'authentification à notre serveur pour la ressource /api/protected/random-quote
:
GET /api/protected/random-quote HTTP/1.1
Host: localhost:1337
...
Origin: http://localhost
Authorization: Bearer CODE_DU_JETON
...
Autre demande cross-domain
Toutes les entêtes prévues pour faire fonctionner CORS sont acceptées via le mécanisme d'ajout d'en-tête de NodeAtlas.
NodeAtlas gère lui-même les sessions stockées sur le serveur avec comme paramètres initiaux :
nodeatlas.sid
1234567890bépo
qui permettent à un client de rester connecté à travers les pages à un même ensemble de variables personnelles côtés serveur.
Il est possible de modifier ces paramètres par défaut (et même obligatoire pour des sites en productions) avec les paramètres de webconfig.json
suivant :
{
"sessionKey": "clé personnelle",
"sessionSecret": "secret personnel"
}
NodeAtlas utilise également un objet de stockage mémoire (MemoryStore) qui stocke les informations dans la RAM du serveur.
Il est possible de changer l'intégralité des paramètres des sessions (sauf le MemoryStore) en utilisant la configuration de webconfig.json
suivante :
{
"session": {
"key": "clé personnelle",
"secret": "secret personnel",
"cookie": {
"path": "/",
"httpOnly": true,
"secure": false,
"maxAge": null
},
...,
...,
...
}
}
L'intégralité de la configuration possible se trouve sur la documentation du module express-session.
Par défaut, c'est NodeAtlas qui stocke les sessions serveurs dans la RAM du serveur par application. Cela ne permet pas de partager des sessions utilisateurs à travers plusieurs applications NodeAtlas (ou autre) et efface toutes les sessions en cours pour une application en cas de redémarrage de celle-ci.
Pour résoudre ce souci, il convient de prendre en charge l'enregistrement des sessions via une base No SQL tel que Redis
ou MongoBD
.
Pour cela il suffit d'utiliser la fonction setSessions
dans le fichier de controller
commun.
Implémentez le code suivant dans le controller
commun pour stocker vos sessions dans Redis en local.
exports.setModules = function () {
var NA = this;
NA.modules.RedisStore = require("connect-redis");
};
exports.setSessions = function (njsext) {
var NA = this,
session = NA.modules.session,
RedisStore = NA.modules.RedisStore(session);
NA.sessionStore = new RedisStore();
next();
};
Plus d'informations sur connect-redis.
Implémentez le code suivant dans controllers/common.js
pour stocker vos sessions dans la database sessions
d'une MongoDB locale.
exports.setModules = function () {
var NA = this;
NA.modules.MongoStore = require("connect-mongo");
};
exports.setSessions = function (next) {
var NA = this,
session = NA.modules.session,
MongoStore = NA.modules.MongoStore(session);
NA.sessionStore = new MongoStore({
db: "sessions"
});
next();
};
Plus d'informations sur connect-mongo.
Il est possible de générer une URL de visite différente des paramètres d'écoutes demandés avec urlHostname
et urlPort
. Par exemple on écoute la boucle local sur le port 80 car un script fait du Reverse Proxy depuis le port 7777 sur le 80 avec le module « http-proxy » comme ci-dessous :
{
"httpPort": 7777,
"httpHostname": "127.0.0.1",
"urlPort": 80,
"urlHostname": "localhost",
"routes": {
"/": {
"view": "index.htm"
}
}
}
Il est possible que les chemins créés à partir de votre URL soient interprétés comme des sous-dossiers qui n'ont en réalité aucunes existences réelles. Cela a pour conséquence de rendre l'adresse media/images/example.jpg
initialement accessible depuis un template affiché à http://localhost impossible à récupérer quand le template est affiché à http://localhost/sub-directory/ (puisqu'il faudrait alors que notre chemin soit plutôt ../media/images/example.jpg
).
Pour ne plus avoir à se soucier de l'accès aux ressources, peu importe l'URL qui est demandée, il suffit de transformer toutes les URL relatives telles que :
<link rel="stylesheet" type="text/css" href="stylesheets/common.css" />
<!-- ... -->
<img src="media/images/example.jpg" />
<!-- ... -->
<script type="text/javascript" src="javascripts/common.js"></script>
en URL absolues avec la variable urlBasePath
comme ci-dessous :
<link rel="stylesheet" type="text/css" href="<?= urlBasePath ?>stylesheets/common.css" />
<!-- ... -->
<img src="<?= urlBasePath ?>media/images/example.jpg" />
<!-- ... -->
<script type="text/javascript" src="<?= urlBasePath ?>javascripts/common.js"></script>
À noter que dans le cas de la configuration suivante :
{
"routes": {
"/": {
"view": "index.htm"
}
}
}
urlBasePath
retourne http://localhost/
alors que dans celle-ci :
{
"httpPort": 7777,
"urlRelativeSubPath": "sub/folder",
"routes": {
"/": {
"view": "index.htm"
}
}
}
urlBasePath
retourne http://localhost:7777/sub/folder/
.
En utilisant le webconfig suivant :
{
"routes": {
"/index.html": {
"view": "index.htm"
},
"/contact.html": {
"view": "contact.htm"
}
}
}
ainsi que la view index.htm
correspondante
<!-- ... -->
<a href="http://localhost/index.html">Lien vers l'accueil</a>
<a href="http://localhost/contact.html">Lien pour nous contacter</a>
<!-- ... -->
je serais obligé de changer mon lien dans le template si je change le port d'écoute ou si je change le chemin de l'URL. Le changement de configuration suivant :
{
"httpPort": 7777,
"routes": {
"/home.html": {
"view": "index.htm"
},
"/contact-us.html": {
"view": "contact.htm"
}
}
}
me contraindrait à modifier le template précédent comme suit :
<!-- ... -->
<a href="http://localhost:7777/home.html">Lien vers l'accueil</a>
<a href="http://localhost:7777/contact-us.html">Lien pour nous contacter</a>
<!-- ... -->
Il est possible de solutionner ce problème en donnant une clé à un chemin précis et en déportant son chemin dans la propriété url
.
Avec le webconfig suivant :
{
"routes": {
"index": {
"url": "/index.html",
"view": "index.htm"
},
"contact": {
"url": "/contact.html",
"view": "contact.htm"
}
}
}
je peux à présent écrire le lien dans le template de manière dynamique :
comme suit
<!-- ... -->
<a href="<?= urlBasePath ?><?= webconfig.routes.home.url.slice(1) ?>">Lien vers l'accueil</a>
<a href="<?= urlBasePath ?><?= webconfig.routes.contact.url.slice(1) ?>">Lien pour nous contacter</a>
<!-- ... -->
Note : .slice(1)
permet de supprimer facilement le double /
pour une URL fonctionnelle.
ou comme suit
<!-- ... -->
<a href="<?= urlBasePath ?>.<?= webconfig.routes.home.url ?>">Lien vers l'accueil</a>
<a href="<?= urlBasePath ?>.<?= webconfig.routes.contact.url ?>">Lien pour nous contacter</a>
<!-- ... -->
Note : Cela donnerait par exemple http://localhost/./home.html
, ce qui est une URL fonctionnelle.
ou comme suit
<!-- ... -->
<a href="<?= urlBasePathSlice + webconfig.routes.home.url ?>">Lien vers l'accueil</a>
<a href="<?= urlBasePathSlice + webconfig.routes.contact.url ?>">Lien pour nous contacter</a>
<!-- ... -->
Note : urlBasePathSlice
renvoyant http://localhost
au lieu de http://localhost/
ou encore http://localhost:7777/sub/folder
au lieu de http://localhost:7777/sub/folder/
.
Il est parfois utile de connaître la clé utilisée pour la page courante afin de trouver une équivalence dans une autre langue par exemple.
Avec le webconfig suivant :
{
"languageCode": "fr-fr",
"routes": {
"index_fr-fr": {
"url": "/",
"view": "/index.htm"
},
"index_en-us": {
"url": "/english/",
"view": "index.htm",
"languageCode": "en-us"
},
"cv_fr-fr": {
"url": "/cv/",
"view": "cv.htm"
},
"cv_en-us": {
"url": "/english/resume/",
"view": "index.htm",
"languageCode": "en-us"
}
}
}
et les fichiers de variation commun suivant en fr :
{
"language": [{
"name": "Anglais",
"code": "en-us"
}, {
"name": "Français",
"code": "fr-fr"
}]
}
en en
:
{
"language": [{
"name": "English",
"code": "en-us"
}, {
"name": "French",
"code": "fr-fr"
}]
}
on peut alors créer un lien entre chaque page multilingue comme ceci :
<ul>
<? for (var i = 0; i < common.language.length; i++) { ?>
<li><a href="<?= urlBasePath + webconfig.routes[routeKey.split('_')[0] + '_' + common.language[i].code].url ?>"><?- common.language[i].name ?></a></li>
<? } ?>
</ul>
Il est possible de laisser l'implémentation de Express prendre la main sur le moteur de rendu des vues. Pour cela il faut utiliser le paramètre engine
. Un exemple en utilisant le moteur Handlebars :
Tout d'abord, ajouter le middleware Express Handlebars à vos modules :
npm install express-handlebars
puis utiliser engine
avec la valeur arbitraire hbs
{
"engine": "hbs",
"controller": "common.js",
"variation": "common.json",
"routes": {
"/": {
"view": "index.hbs",
"variation": "index.json"
}
}
}
puis expliquer au moteur Express de NodeAtlas comment rendre les vues :
exports.setModules = function () {
var NA = this;
NA.modules.exphbs = require("express-handlebars");
};
exports.setConfigurations = function (next) {
var NA = this,
exphbs = NA.modules.exphbs;
NA.express.engine("hbs", exphbs());
next();
};
enfin voyons rapidement ce que le fichier index.hbs
pourrait contenir :
<!DOCTYPE html>
<html lang="fr-fr">
<head>
<meta charset="utf-8">
<title>{{specific.titlePage}}</title>
<link rel="stylesheet" href="stylesheets/{{common.classCssCommon}}.css" media="all">
<link rel="stylesheet" href="stylesheets/{{specific.classPage}}.css" media="all">
</head>
<body class="{{specific.classPage}}">
<div>
<h1>{{specific.titlePage}}</h1>
{{{specific.content}}}
</div>
<script async="true" type="text/javascript" src="javascripts/{{common.classJsCommon}}.js"></script>
</body>
</html>
Ce que fait engine
, c'est abandonner le système de NodeAtlas et passer par celui d'Express. Comme Express a besoin d'un objet response
pour rendre une vue, il est impossible d'utiliser ce mécanisme via l'utilisation de la fonction NA.view
de l'API NodeAtlas, celle-ci ne supportant que le moteur NodeAtlas, EJS et Pug.
engine
, templateEngineDelimiter
et pug
Il est tout a fait possible de passer par Express pour rendre EJS et Pug. Dans ce cas, puisque node-atlas
embarque les modules ejs
et pug
en tant que dépendance, il n'est pas nécéssaire de passer par le controller
commun et l'utilisation de npm
pour les mettre en place. Il suffit juste d'utiliser engine: "ejs"
ou engine: "pug"
.
Cependant, faire cela retire les bénéfices apportés par NodeAtlas pour l'utilisation de ces deux moteurs comme par exemple le support des inclusions dynamiques pour Pug dans la view
avec #{routeParameters.view}
.
Il est possible de ne pas utiliser de vue et de seulement faire appel au contrôleur. Dans ce cas le point d'ancrage changeVariations
est inutile. Il va falloir alimenter vous même locals.dom
dans le point d'ancrage changeDom
.
webconfig.json
{
"routes": {
"/(:member/)?": {
"controller": "index.js",
"mimeType": "application/json"
}
}
}
controllers/index.js
exports.changeDom = function (next, locals) {
locals.dom = `{
"params": ${locals.params.member},
"query": ${locals.query.member},
"body": ${locals.body.member}
}`;
next();
};
Ainsi à l'adresse http://localhost/riri/?query=fifi
demandé en POST avec le body member=loulou
vous obtiendrez la sortie :
{
"params": "riri",
"query": "fifi",
"body": "loulou"
}
Pas un seul webconfig présenté dans la documentation ne se passe du paramètre routes
. Pourtant il est facultatif au même titre que tous les autres. Aussi avec le webconfig suivant :
webconfig.json
{
"controller": "common.js"
}
et le contrôleur suivant :
controllers/common.js
exports.setRoutes = function (next) {
var NA = this,
route = NA.webconfig.routes = {};
route["/"] = {
"mimeType": "text/plain"
};
next();
};
exports.changeDom = function (next, locals) {
locals.dom = "Hello World";
next();
};
Il est tout à fait possible d'obtenir à l'adresse http://localhost/
le simple message « Hello World ».
C'est une bonne chose de ne pas reservir des fichiers qui n'ont pas bougé pour la production. Vous pouvez mettre à true
la valeur du webconfig cache
pour ça:
{
"cache": true,
"route": {
"/": "index.htm"
}
}
Vous pouvez également lancer node-atlas
avec l'option --cache
:
node-atlas --cache
ou mettre votre variable d'environnement NODE_ENV
à production
:
si vous êtes sous Unix / MacOS
export NODE_ENV=production
ou si vous êtes sur Windows
SET NODE_ENV=production
ou vous pouvez démarrer NodeAtlas comme suit
NODE_ENV=production node-atlas
ou vous pouvez la définir dans votre fichier JavaScript :
process.env.NODE_ENV = "production";
Nous allons voir à présent comment utiliser des informations venant d'une base de données. Pour cela nous allons utiliser une base MySQL comme exemple. Le module npm mysql
va donc nous être utile. Il va également nous falloir installer un serveur MySQL.
Donc, depuis le dossier du webconfig.json
, utilisez :
npm install mysql
Tout d'abord, nous allons alimenter la base de données avec la base demo
:
CREATE DATABASE demo;
et la sélectionner :
USE demo
puis créer la table user
:
CREATE TABLE user
(
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
lastname VARCHAR(100),
firstname VARCHAR(100),
email VARCHAR(255),
birthdate DATE,
gender TINYINT(1),
country VARCHAR(255),
town VARCHAR(255),
zipcode VARCHAR(5),
address VARCHAR(255)
);
et la remplir avec un jeu de données :
INSERT INTO user (
lastname,
firstname,
email,
birthdate,
gender,
country,
town,
zipcode,
address
) VALUES (
"Elric",
"Edward",
"edward.elric@fma.br",
"2006/01/01",
true,
"Amestris",
"Resembool",
00000,
"The Elric's house"
);
INSERT INTO user (
lastname,
firstname,
email,
birthdate,
gender,
country,
town,
zipcode,
address
) VALUES (
"Elric",
"Alphonse",
"alphonse.elric@fma.br",
"2008/01/01",
true,
"Amestris",
"Resembool",
00000,
"The Elric's house"
);
Voyons à présent l'architecture de site que nous allons arbitrairement créer pour présenter notre exemple :
├─ controllers/
│ ├─ common.js
│ └─ index.js
├─ models/
│ ├─ objects/
│ │ └─ user.js
│ └─ connectors/
│ └─ user.js
├─ views/
│ └─ index.htm
├─ variations/
│ ├─ common.json
│ └─ index.json
└─ webconfig.json
Nous allons utiliser le webconfig.json
suivant avec une variable custom _mysqlConfig
qui contiendra toutes les informations pour se connecter à la base de données :
{
"controller": "common.js",
"variation": "common.json",
"statics": {
"/models": "models/objects"
},
"routes": {
"/": {
"view": "index.htm",
"variation": "index.json",
"controller": "index.js"
}
},
"_mysqlConfig": {
"host": "localhost",
"user": "root",
"password": "root",
"database": "demo"
}
}
Nous allons ensuite nous connecter à la base de données avec le contrôleur globale controllers/common.js
:
exports.setModules = function () {
var NA = this;
// Import du module `mysql`.
NA.modules.mysql = require('mysql');
// Création de la collection de modèle...
NA.models = {};
// ...et récupération du modèle User avec accès à Mysql.
NA.models.User = require('../models/connectors/user.js');
};
exports.setConfigurations = function (next) {
var NA = this,
path = NA.modules.path,
mysql = NA.modules.mysql;
// Créer un pool de connexion à MySQL.
NA.mySql = mysql.createPool(NA.webconfig._mysqlConfig);
next();
};
Et afficher les résultats via le contrôleur spécifique controllers/index.js
:
exports.changeVariations = function (next, locals) {
var NA = this,
user = new NA.models.User(),
user2 = new NA.models.User(),
user3 = new NA.models.User(),
user4 = new NA.models.User();
NA.mySql.getConnection(function(err, connection) {
if (err) {
throw err;
}
// Exemple de lecture.
user
.setConnection(connection)
.lastname("Elric")
.read(function (allUsers) {
locals.user = user;
locals.users = allUsers;
// Exemple de création.
user2
.setConnection(connection)
.firstname("Winry")
.lastname("Rockbell")
.email("winry.rockbell@fma.br")
.gender(true)
.create(function (infos) {
locals.insertId = infos.insertId;
locals.user2 = user2;
// Exemple de modification.
user3
.gender(false)
.birthdate("2008-01-01")
.country("Amestris")
.town("Resembool")
.zipcode("99999")
.address("The Rockbell's house");
user2.update(user3, function (infos) {
locals.affectedRows = infos.affectedRows;
locals.user2 = user2;
// Exemple de suppression.
user4
.setConnection(connection)
.gender(false)
.delete(function (infos) {
locals.deletedRows = infos.affectedRows;
next();
});
});
});
});
});
};
en utilisant le modèle user
via le fichier de connexion à la base de données models/connectors/user.js
:
var user = require('../objects/user.js');
function User(connection) {
var privates = {},
publics = this;
user.call(publics);
privates.connection = connection;
publics.setConnection = function (connection) {
privates.connection = connection;
return publics;
};
publics.read = function (callback) {
var select = `SELECT
id,
lastname,
firstname,
email,
birthdate,
gender,
country,
town,
zipcode,
address
FROM user`,
where = "";
if (publics.id()) { where += ' && `id` = ' + publics.id(); }
if (publics.lastname()) { where += ' && `lastname` = "' + publics.lastname() + '"'; }
if (publics.firstname()) { where += ' && `firstname` = "' + publics.firstname() + '"'; }
if (publics.email()) { where += ' && `email` = "' + publics.email() + '"'; }
if (publics.birthdate()) { where += ' && `birthdate` = "' + publics.birthdate() + '"'; }
if (typeof publics.gender() === "boolean") { where += ' && `gender` = ' + (publics.gender() ? 1 : 0); }
if (publics.country()) { where += ' && `country` = "' + publics.country() + '"'; }
if (publics.town()) { where += ' && `town` = "' + publics.town() + '"'; }
if (publics.zipcode()) { where += ' && `zipcode` = "' + publics.zipcode() + '"'; }
if (publics.address()) { where += ' && `address` = "' + publics.address() + '"'; }
where = where.replace("&&", "WHERE");
privates.connection.query(select + where, function (err, rows) {
var users = [],
user;
if (err) {
throw err;
}
if (rows[0]) {
publics.id(rows[0].id);
publics.lastname(rows[0].lastname);
publics.firstname(rows[0].firstname);
publics.email(rows[0].email);
publics.birthdate(rows[0].birthdate);
publics.gender((rows[0].gender) ? true : false);
publics.country(rows[0].country);
publics.town(rows[0].town);
publics.zipcode(rows[0].zipcode);
publics.address(rows[0].address);
}
for (var i = 0; i < rows.length; i++) {
user = new User();
user.id(rows[i].id);
user.lastname(rows[i].lastname);
user.firstname(rows[i].firstname);
user.email(rows[i].email);
user.birthdate(rows[i].birthdate);
user.gender((rows[i].gender) ? true : false);
user.country(rows[i].country);
user.town(rows[i].town);
user.zipcode(rows[i].zipcode);
user.address(rows[i].address);
users.push(user);
}
if (callback) {
callback(users);
}
});
return publics;
};
publics.create = function (callback) {
var insert = "INSERT INTO user (",
values = ") VALUES (";
if (publics.id()) {
insert += "`id`, ";
values += publics.id() + ', ';
}
if (publics.lastname()) {
insert += "`lastname`, ";
values += '"' + publics.lastname() + '", ';
}
if (publics.firstname()) {
insert += "`firstname`, ";
values += '"' + publics.firstname() + '", ';
}
if (publics.email()) {
insert += "`email`, ";
values += '"' + publics.email() + '", ';
}
if (publics.birthdate()) {
insert += "`birthdate`, ";
values += '"' + publics.birthdate() + '", ';
}
if (typeof publics.gender() === "boolean") {
insert += "`gender`, ";
values += (publics.gender() ? 1 : 0) + ', ';
}
if (publics.country()) {
insert += "`country`, ";
values += '"' + publics.country() + '", ';
}
if (publics.town()) {
insert += "`town`, ";
values += '"' + publics.town() + '", ';
}
if (publics.zipcode()) {
insert += "`zipcode`, ";
values += '"' + publics.zipcode() + '", ';
}
if (publics.address()) {
insert += "`address`, ";
values += '"' + publics.address() + '", ';
}
insert = insert.replace(/, $/g, "");
values = values.replace(/, $/g, ")");
privates.connection.query(insert + values, function (err, infos) {
if (err) {
throw err;
}
publics.id(infos.insertId);
if (callback) {
callback(infos);
}
});
return publics;
};
publics.update = function (user, callback) {
var update = "UPDATE user SET",
where = "";
if (user.id()) { update += '`id` = ' + user.id() + ', '; }
if (user.lastname()) { update += '`lastname` = "' + user.lastname() + '", '; }
if (user.firstname()) { update += '`firstname` = "' + user.firstname() + '", '; }
if (user.email()) { update += '`email` = "' + user.email() + '", '; }
if (user.birthdate()) { update += '`birthdate` = "' + user.birthdate() + '", '; }
if (typeof user.gender() === "boolean") { update += '`gender` = ' + (user.gender() ? 1 : 0) + ', '; }
if (user.country()) { update += '`country` = "' + user.country() + '", '; }
if (user.town()) { update += '`town` = "' + user.town() + '", '; }
if (user.zipcode()) { update += '`zipcode` = "' + user.zipcode() + '", '; }
if (user.address()) { update += '`address` = "' + user.address() + '", '; }
update = update.replace(/, $/g, "");
if (publics.id()) { where += ' && `id` = ' + publics.id(); }
if (publics.lastname()) { where += ' && `lastname` = "' + publics.lastname() + '"'; }
if (publics.firstname()) { where += ' && `firstname` = "' + publics.firstname() + '"'; }
if (publics.email()) { where += ' && `email` = "' + publics.email() + '"'; }
if (publics.birthdate()) { where += ' && `birthdate` = "' + publics.birthdate() + '"'; }
if (typeof publics.gender() === "boolean") { where += ' && `gender` = ' + (publics.gender() ? 1 : 0); }
if (publics.country()) { where += ' && `country` = "' + publics.country() + '"'; }
if (publics.town()) { where += ' && `town` = "' + publics.town() + '"'; }
if (publics.zipcode()) { where += ' && `zipcode` = "' + publics.zipcode() + '"'; }
if (publics.address()) { where += ' && `address` = "' + publics.address() + '"'; }
where = where.replace("&&", "WHERE");
privates.connection.query(update + where, function (err, infos) {
if (err) {
throw err;
}
if (user.id()) { publics.id(user.id()); }
if (user.lastname()) { publics.lastname(user.lastname()); }
if (user.firstname()) { publics.firstname(user.firstname()); }
if (user.email()) { publics.email(user.email()); }
if (user.birthdate()) { publics.birthdate(user.birthdate()); }
if (typeof publics.gender() === "boolean") { publics.gender(user.gender()); }
if (user.country()) { publics.country(user.country()); }
if (user.town()) { publics.town(user.town()); }
if (user.zipcode()) { publics.zipcode(user.zipcode()); }
if (user.address()) { publics.address(user.address()); }
if (callback) {
callback(infos);
}
});
return publics;
};
publics.delete = function (callback) {
var del = "DELETE FROM user",
where = "";
if (publics.id()) { where += ' && `id` = ' + publics.id(); }
if (publics.lastname()) { where += ' && `lastname` = "' + publics.lastname() + '"'; }
if (publics.firstname()) { where += ' && `firstname` = "' + publics.firstname() + '"'; }
if (publics.email()) { where += ' && `email` = "' + publics.email() + '"'; }
if (publics.birthdate()) { where += ' && `birthdate` = "' + publics.birthdate() + '"'; }
if (typeof publics.gender() === "boolean") { where += ' && `gender` = ' + (publics.gender() ? 1 : 0); }
if (publics.country()) { where += ' && `country` = "' + publics.country() + '"'; }
if (publics.town()) { where += ' && `town` = "' + publics.town() + '"'; }
if (publics.zipcode()) { where += ' && `zipcode` = "' + publics.zipcode() + '"'; }
if (publics.address()) { where += ' && `address` = "' + publics.address() + '"'; }
where = where.replace("&&", "WHERE");
privates.connection.query(del + where, function (err, infos) {
if (err) {
throw err;
}
if (publics.id()) { publics.id(undefined); }
if (publics.lastname()) { publics.lastname(undefined); }
if (publics.firstname()) { publics.firstname(undefined); }
if (publics.email()) { publics.email(undefined); }
if (publics.birthdate()) { publics.birthdate(undefined); }
if (typeof publics.gender() === "boolean") { publics.gender(undefined); }
if (publics.country()) { publics.country(undefined); }
if (publics.town()) { publics.town(undefined); }
if (publics.zipcode()) { publics.zipcode(undefined); }
if (publics.address()) { publics.address(undefined); }
if (callback) {
callback(infos);
}
});
return publics;
};
}
User.prototype = Object.create(user.prototype);
User.prototype.constructor = User;
module.exports = User;
basé sur une classe user
partagée entre la partie cliente et serveur models/objects/user.js
:
(function (expose, factory) {
if (typeof module !== 'undefined' && module.exports) {
module.exports = factory;
} else {
expose.User = factory;
}
}(this, function User() {
var privates = {},
publics = this;
publics.id = function (id) {
if (typeof id === 'undefined') {
return privates.id;
} else {
privates.id = id;
return publics;
}
};
publics.lastname = function (lastname) {
if (typeof lastname === 'undefined') {
return privates.lastname;
} else {
privates.lastname = lastname;
return publics;
}
};
publics.firstname = function (firstname) {
if (typeof firstname === 'undefined') {
return privates.firstname;
} else {
privates.firstname = firstname;
return publics;
}
};
publics.email = function (email) {
if (typeof email === 'undefined') {
return privates.email;
} else {
privates.email = email;
return publics;
}
};
publics.birthdate = function (birthdate) {
if (typeof birthdate === 'undefined') {
return privates.birthdate;
} else {
privates.birthdate = birthdate;
return publics;
}
};
publics.gender = function (gender) {
if (typeof gender === 'undefined') {
return privates.gender;
} else {
privates.gender = gender;
return publics;
}
};
publics.country = function (country) {
if (typeof country === 'undefined') {
return privates.country;
} else {
privates.country = country;
return publics;
}
};
publics.town = function (town) {
if (typeof town === 'undefined') {
return privates.town;
} else {
privates.town = town;
return publics;
}
};
publics.zipcode = function (zipcode) {
if (typeof zipcode === 'undefined') {
return privates.zipcode;
} else {
privates.zipcode = zipcode;
return publics;
}
};
publics.address = function (address) {
if (typeof address === 'undefined') {
return privates.address;
} else {
privates.address = address;
return publics;
}
};
}));
Avec les fichiers suivant pour afficher la page :
views/index.htm
<!DOCTYPE html>
<html lang="fr-fr">
<head>
<meta charset="utf-8" />
<title><?- common.titleWebsite ?></title>
</head>
<body>
<div class="title"><?- common.titleWebsite ?></div>
<div>
<h1><?- specific.titlePage ?></h1>
<div class="first">
<?- specific.content ?>
<ul>
<li>Id: <strong><?- user.id() ?></strong></li>
<li>Lastname: <strong><?- user.lastname() ?></strong></li>
<li>Firstname: <strong><?- user.firstname() ?></strong></li>
<li>Email: <strong><?- user.email() ?></strong></li>
<li>Birthdate: <strong><?- user.birthdate() ?></strong></li>
<li>Gender: <strong><?- user.gender() ?></strong></li>
<li>Country: <strong><?- user.country() ?></strong></li>
<li>Town: <strong><?- user.town() ?></strong></li>
<li>Zipcode: <strong><?- user.zipcode() ?></strong></li>
<li>Address: <strong><?- user.address() ?></strong></li>
</ul>
</div>
<div class="all">
<?- specific.contents ?>
<? for (var i = 0; i < users.length; i++) { ?>
<ul>
<li>Id: <strong><?- users[i].id() ?></strong></li>
<li>Lastname: <strong><?- users[i].lastname() ?></strong></li>
<li>Firstname: <strong><?- users[i].firstname() ?></strong></li>
<li>Email: <strong><?- users[i].email() ?></strong></li>
<li>Birthdate: <strong><?- users[i].birthdate() ?></strong></li>
<li>Gender: <strong><?- users[i].gender() ?></strong></li>
<li>Country: <strong><?- users[i].country() ?></strong></li>
<li>Town: <strong><?- users[i].town() ?></strong></li>
<li>Zipcode: <strong><?- users[i].zipcode() ?></strong></li>
<li>Address: <strong><?- users[i].address() ?></strong></li>
</ul>
<? } ?>
</div>
<div class="last">
<?- specific.contentInsert ?>
<p>insertId: <?- insertId ?></p>
<p>numberUpdate: <?- affectedRows ?></p>
<ul>
<li>Id: <strong><?- user2.id() ?></strong></li>
<li>Lastname: <strong><?- user2.lastname() ?></strong></li>
<li>Firstname: <strong><?- user2.firstname() ?></strong></li>
<li>Email: <strong><?- user2.email() ?></strong></li>
<li>Birthdate: <strong><?- user2.birthdate() ?></strong></li>
<li>Gender: <strong><?- user2.gender() ?></strong></li>
<li>Country: <strong><?- user2.country() ?></strong></li>
<li>Town: <strong><?- user2.town() ?></strong></li>
<li>Zipcode: <strong><?- user2.zipcode() ?></strong></li>
<li>Address: <strong><?- user2.address() ?></strong></li>
</ul>
<p>numberDelete: <?- deletedRows ?></p>
</div>
</div>
</body>
</html>
variations/common.json
{
"titleWebsite": "Exemple MySql",
"male": "Homme",
"female": "Femme"
}
variations/index.json
{
"titlePage": "Table User",
"content": "<p>Détail de la première entrée.</p>",
"contents": "<p>Détail de toutes les entrées.</p>",
"contentInsert": "<p>Détail de l'utilisateur ajouté puis modifié.</p>"
}
Vous obtiendrez la sortie suivante :
<!DOCTYPE html>
<html lang="fr-fr">
<head>
<meta charset="utf-8" />
<title>MySql Exemple</title>
</head>
<body>
<div class="title">MySql Exemple</div>
<div>
<h1>Table User</h1>
<div class="first">
<p>Détail de la première entrée.</p>
<ul>
<li>Id: <strong>1</strong></li>
<li>Lastname: <strong>Elric</strong></li>
<li>Firstname: <strong>Edward</strong></li>
<li>Email: <strong>edward.elric@fma.br</strong></li>
<li>Birthdate: <strong>Sun Jan 01 2006 00:00:00 GMT+0100 (Paris, Madrid)</strong></li>
<li>Gender: <strong>true</strong></li>
<li>Country: <strong>Amestris</strong></li>
<li>Town: <strong>Resembool</strong></li>
<li>Zipcode: <strong>0</strong></li>
<li>Address: <strong>The Elric's house</strong></li>
</ul>
</div>
<div class="all">
<p>Détail de toutes les entrées.</p>
<ul>
<li>Id: <strong>1</strong></li>
<li>Lastname: <strong>Elric</strong></li>
<li>Firstname: <strong>Edward</strong></li>
<li>Email: <strong>edward.elric@fma.br</strong></li>
<li>Birthdate: <strong>Sun Jan 01 2006 00:00:00 GMT+0100 (Paris, Madrid)</strong></li>
<li>Gender: <strong>true</strong></li>
<li>Country: <strong>Amestris</strong></li>
<li>Town: <strong>Resembool</strong></li>
<li>Zipcode: <strong>0</strong></li>
<li>Address: <strong>The Elric's house</strong></li>
</ul>
<ul>
<li>Id: <strong>2</strong></li>
<li>Lastname: <strong>Elric</strong></li>
<li>Firstname: <strong>Alphonse</strong></li>
<li>Email: <strong>alphonse.elric@fma.br</strong></li>
<li>Birthdate: <strong>Tue Jan 01 2008 00:00:00 GMT+0100 (Paris, Madrid)</strong></li>
<li>Gender: <strong>true</strong></li>
<li>Country: <strong>Amestris</strong></li>
<li>Town: <strong>Resembool</strong></li>
<li>Zipcode: <strong>0</strong></li>
<li>Address: <strong>The Elric's house</strong></li>
</ul>
</div>
<div class="last">
<p>Détail de l'utilisateur ajouté puis modifié.</p>
<p>insertId: 3</p>
<p>numberUpdate: 1</p>
<ul>
<li>Id: <strong>3</strong></li>
<li>Lastname: <strong>Rockbell</strong></li>
<li>Firstname: <strong>Winry</strong></li>
<li>Email: <strong>winry.rockbell@fma.br</strong></li>
<li>Birthdate: <strong>2008-01-01</strong></li>
<li>Gender: <strong>false</strong></li>
<li>Country: <strong>Amestris</strong></li>
<li>Town: <strong>Resembool</strong></li>
<li>Zipcode: <strong>99999</strong></li>
<li>Address: <strong>The Rockbell's house</strong></li>
</ul>
<p>numberDelete: 1</p>
</div>
</div>
</body>
</html>
Nous allons voir à présent comment utiliser des informations venant d'une base de données non sql. Pour cela nous allons utiliser le module npm mongoose
. Il va également nous falloir installer un serveur MongoDB.
Donc, depuis le dossier du webconfig.json
, utilisez :
npm install mongoose
Tout d'abord, nous allons alimenter la base de données avec la base demo
et la sélectionner :
use demo
puis créer la collection user
:
db.createCollection("user")
et la remplir avec un document :
db.user.insert({
email: "john.doe@unknown.com",
identity: {
lastname: "Doe",
firstname: "John",
gender: true,
birthdate : new Date("1970/01/01")
},
location: {
country: "Unknown",
town: "Unknown",
zipcode: "00000",
address: "42 unknown"
}
})
Avec le jeu de fichier suivant :
├─ controllers/
│ ├─ common.js
│ └─ index.js
├─ models/
│ └─ user.js
├─ views/
│ └─ index.htm
├─ variations/
│ ├─ common.json
│ └─ index.json
└─ webconfig.json
Nous allons utiliser le webconfig.json
suivant avec une variable custom _mongodbConfig
qui contiendra toutes les informations pour se connecter à la base de données :
{
"controller": "common.js",
"variation": "common.json",
"statics": {
"/models": "models"
},
"routes": {
"/": {
"view": "index.htm",
"variation": "index.json",
"controller": "index.js"
}
},
"_mongodbConfig": {
"host": "localhost",
"port": "27017",
"database": "demo"
}
}
Avec les fichiers suivant pour afficher la page :
views/index.htm
<!DOCTYPE html>
<html lang="<?- languageCode ?>">
<head>
<meta charset="utf-8" />
<title><?- common.titleWebsite ?></title>
</head>
<body>
<div class="title"><?- common.titleWebsite ?></div>
<div>
<h1><?- specific.titlePage ?></h1>
<?- specific.content ?>
<ul>
<li>Id: <strong><?- id ?></strong></li>
<li>Lastname: <strong><?- lastname ?></strong></li>
<li>Firstname: <strong><?- firstname ?></strong></li>
<li>Email: <strong><?- email ?></strong></li>
<li>Birthdate: <strong><?- birthdate ?></strong></li>
<li>Gender: <strong><?- gender ?></strong></li>
<li>Country: <strong><?- country ?></strong></li>
<li>Town: <strong><?- town ?></strong></li>
<li>Zipcode: <strong><?- zipcode ?></strong></li>
<li>Address: <strong><?- address ?></strong></li>
</ul>
</div>
</body>
</html>
variations/common.json
{
"titleWebsite": "MongoDB Exemple",
"male": "Homme",
"female": "Femme"
}
variations/index.json
{
"titlePage": "Collection User",
"content": "<p>Détail du document `{ \"identity.firstname\": \"John\" }`.</p>"
}
Enfin nous allons nous connecter à la base de données avec le contrôleur globale controllers/common.js
:
exports.setModules = function () {
var NA = this,
path = NA.modules.path;
NA.modules.mongoose = require('mongoose');
NA.models = {};
NA.models.User = require('../models/user.js');
};
exports.setConfigurations = function (next) {
var NA = this,
mongoose = NA.modules.mongoose,
config = NA.webconfig._mongodbConfig;
mongoose.Promise = global.Promise;
mongoose.model("user", NA.models.User, "user");
mongoose.connect("mongodb://" + config.host + ":" + config.port + "/" + config.database, function (error) {
next();
});
};
Et afficher les résultats via le contrôleur spécifique controllers/index.js
:
exports.changeVariations = function (next, locals) {
var NA = this,
mongoose = NA.modules.mongoose,
User = mongoose.model('user');
User
.findOne({ "identity.firstname": "John" })
.exec(function (err, user) {
locals.id = user._id;
locals.lastname = user.identity.lastname;
locals.firstname = user.identity.firstname;
locals.birthdate = user.identity.birthdate;
locals.email = user.email;
locals.gender = (user.identity.gender) ? locals.common.male : locals.common.female;
locals.country = user.location.country;
locals.town = user.location.town;
locals.zipcode = user.location.zipcode;
locals.address = user.location.address;
next();
});
};
en utilisant sur une classe user
partagée entre la partie cliente et la partie serveur models/user.js
:
var mongoose;
if (typeof module !== 'undefined' && module.exports) {
mongoose = require('mongoose');
}
(function (expose, factory) {
if (mongoose) {
module.exports = factory;
} else {
expose.User = factory;
}
}(this, new mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
email: { type : String, match: /^\S+@\S+$/ },
identity: {
lastname: String,
firstname: String,
gender: Boolean,
birthdate : { type : Date, default : Date.now }
},
location: {
country: String,
town: String,
zipcode: String,
address: String
}
})));
Vous obtiendrez la sortie suivante :
<!DOCTYPE html>
<html lang="fr-fr">
<head>
<meta charset="utf-8" />
<title>Exemple MongoDB</title>
</head>
<body>
<div class="title">Exemple MongoDB</div>
<div>
<h1>Collection User</h1>
<p>Détail de l'entrée `{ "identity.firstname": "John" }`.</p>
<ul>
<li>Id: <strong>5804d4d530788ee2e52ea1c7</strong></li>
<li>Lastname: <strong>Doe</strong></li>
<li>Firstname: <strong>John</strong></li>
<li>Email: <strong>john.doe@unknown.com</strong></li>
<li>Birthdate: <strong>Mon Jan 01 1970 00:00:00 GMT+0200 (Paris, Madrid (heure d’été))</strong></li>
<li>Gender: <strong>Homme</strong></li>
<li>Country: <strong>Unknown</strong></li>
<li>Town: <strong>Unknown</strong></li>
<li>Zipcode: <strong>00000</strong></li>
<li>Address: <strong>42 unknown</strong></li>
</ul>
</div>
</body>
</html>
Une application isomorphique est une application dont le code JavaScript est en grande partie le même qu'il soit exécuté côté client ou exécuté côté serveur. NodeAtlas propose un exemple d'application isomorphique dans son template dédié à Vue.js.
Pour tester cela il vous suffit :
de créer un dossier de test
mkdir hello-vue
cd hello-vue
d'y placer les fichier de hello-vue
node-atlas --create hello-vue
d'installer les dépendances
npm install
et de lancer le site en français
node-atlas --browse
ou en version internationale
node-atlas --browse --webconfig webconfig.en-us.json
Vous trouverrez tout ce qu'il faut pour appréhender la partie serveur du constrollers/common.js
sur https://ssr.vuejs.org/ et seul la partie cliente du assets/javascripts/common.js
sur https://vuejs.org/.