The Progressive Server-side JavaScript Framework
You know HTML & CSS?
But not JavaScript?
Quickly create multilingual websites effortlessly with simple routes, views or variations.
JavaScript client-side Expert?
Ready to embrace Node.js?
Gradually improve your base as you need by using controllers, models or modules.
Already your Front-end habits?
You use Data Binding?
From Vanilla to jQuery and going through Vue, Angular or React: use your favorite tools!
NodeAtlas offers also a large set of features for development or packaging with the configuration system. We will see that.
assetsRelativePath
To display a custom page when a resource is not found you must:
pageNotFound
with the following value
: key
of the prepared 404 page.See the example below:
{
"pageNotFound": "/not-found-page/",
"routes": {
"/list-of-members/": {
"view": "members.htm"
},
"/": {
"view": "index.htm"
},
"/not-found-page/": {
"view": "error.htm",
"statusCode": 404
}
}
}
you can access to:
For this, just create a new route with *
at the end in language you want.
See below :
{
"pageNotFound": "/not-found-page/",
"languageCode": "en-gb",
"routes": {
"/list-of-members/": {
"view": "members.htm",
"variation": "members.json"
},
"/": {
"view": "index.htm",
"variation": "index.json"
},
"/not-found-page/": {
"view": "error.htm",
"variation": "error.json",
"statusCode": 404
},
"/francais/liste-des-membres/": {
"view": "members.htm",
"languageCode": "fr-fr",
"variation": "members.json"
},
"/francais/": {
"view": "index.htm",
"languageCode": "fr-fr",
"variation": "index.json"
},
"/francais/*": {
"view": "error.htm",
"languageCode": "fr-fr",
"variation": "error.json",
"statusCode": 404
}
}
}
Although you can configure static URLs, you can also set of dynamic URLs!
It is possible to get some parameters from URL to display a different content depending of slugs.
With the following configuration:
{
"routes": {
"/list-of-members/:member/:action/": {
"view": "members.htm",
"controller": "members.js"
},
"/list-of-members/:member/:action": {
"view": "members.htm",
"controller": "members.js"
},
"/list-of-members/:member/": {
"view": "members.htm",
"controller": "members.js"
},
"/list-of-members/:member": {
"view": "members.htm",
"controller": "members.js"
},
"/list-of-members/": {
"view": "members.htm",
"controller": "members.js"
},
"/list-of-members": {
"view": "members.htm",
"controller": "members.js"
},
"/": {
"view": "index.htm"
}
}
}
you can access to:
test=This+is+a+test
)and retrieve the :member
, :action
, query
and test
value in changeVariations
(common and specific).
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();
};
We can see which we use the same config for three routes in the previous example. You could also use regular expressions to define that is variable into your URL or define what are the valid parameters in your URL. This system is less complex than real RegExp because a lot of char does not exist in URL so, for example, this char /
do not need to be escaped.
With the following configuration:
{
"routes": {
"/list-of-members/?(:member([-a-zA-Z0-9]+)/?(:action(show|edit)/?)?)?": {
"view": "members.htm"
},
"/": {
"view": "index.htm"
}
}
}
you can access to:
test=This+is+a+test
)and retrieve the :member
, :action
, query
and test
value in a view.
<!DOCTYPE html>
<html lang="en-us">
<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>
you cannot access to:
You can also enable regular expressions to a specific path with regExp
. If it is true
, the previous profile no longer works and you pass in Regular Expression mode. If regExp
is a string, it acts as a flag (g, i, m or y).
See the following configuration:
{
"routes": {
"/list-of-members/([-a-z0-9]+)/?": {
"view": "members.htm",
"regExp": "i"
},
"/list-of-members/?": {
"view": "members.htm",
"regExp": true
},
"/": {
"view": "index.htm"
}
}
}
you can access:
and retrieve the ([-a-z0-9] +) value in the
changeVariations
(common and specific).
exports.changeVariations = function (next, locals) {
if (locals.params && locals.params[0]) { locals.params.member = locals.params[0]; }
// locals.params[1] for second match, etc...
console.log(locals.params.member);
// $ 'toto', 'bob-eponge99', 'node-atlas' or 'etc'.
next();
}
The rules for creating dynamic URL with regExp
are those of RegExpJavaScript.
setRoutes
allows us to dynamically inject routes. However, the route injection add a route at the end of NA.webconfig.routes
because NA.webconfig.routes
is an object. There are no possibility to order routes, but this is a problem because routes path are resolved in order of injection.
We will resolved that with new way to set routes from routes: { <key>: { ... } }
to routes: [{ "key": <key>, ... }]
.
This is all files for example:
├─ controllers/
│ └─ common.js
├─ views/
│ ├─ index.htm
│ ├─ content.htm
│ └─ error.htm
└─ webconfig.json
With the webconfig.json
originaly like this routes: <Object>
:
{
"controller": "common.js",
"routes": {
"/doc/index.html": {
"view": "index.htm"
},
"/doc/*": {
"view": "error.htm",
"statusCode": 404
}
}
}
and transformed like this routes: <Array>
:
{
"controller": "common.js",
"routes": [{
"url": "/doc/index.html",
"view": "index.htm"
}, {
"url": "/doc/*",
"view": "error.htm",
"statusCode": 404
}]
}
With the common.js
file, it's now possible to inject routes at the position we want. We will see an example at the first position.
// This code is executing while route are added.
// This code will be executed when NodeAtlas starting.
exports.setRoutes = function (next) {
// We use instance of NodeAtlas.
var NA = this,
// And we keep routes from NodeAtlas webconfig...
route = NA.webconfig.routes;
// ...to add `/content.html` route in first place.
route.unshift({
"url": "/doc/content.html",
"view": "content.htm"
});
// We update modification here.
next();
};
In this way, address http://localhost/doc/content.html
will return the content.htm
view and not the error.htm
view with 404.
To go to a different address (redirect 301 or 302) when you get to an URL you must use the redirect
parameter.
Note: if you don't set statusCode
, no redirect will be executed. The statusCode
is mandatory for redirection.
See the example below:
{
"routes": {
"/list-of-members/": {
"view": "members.htm"
},
"/list-of-members": {
"redirect": "/list-of-members/",
"statusCode": 301
},
"/go-to-node-atlas/": {
"redirect": "https://node-atlas.js.org/",
"statusCode": 302
},
"/": {
"view": "index.htm"
}
}
}
You will be redirected:
http://localhost/list-of-members/
when you access http://localhost/list-of-members
with a header permanent redirect.https://node-atlas.js.org/
when you access http://localhost/go-to-node-atlas/
with a header temporary redirect.See the example below:
{
"routes": {
"/list-of-members/:member/": {
"view": "members.htm"
},
"/list-of-members/:member": {
"redirect": "/list-of-members/:member/",
"statusCode": 301
},
"/": {
"view": "index.htm"
}
}
}
You will be redirected to http://localhost/list-of-members/machinisteweb/
when you access to http://localhost/list-of-members/machinisteweb
with a header permanent redirect.
See the example below:
{
"routes": {
"/membres/([-a-z0-9]+)/": {
"view": "members.htm",
"regExp": true
},
"/list-of-members/([-a-z0-9]+)/": {
"redirect": "/membres/$0/",
"statusCode": 301,
"regExp": true
},
"/list-of-members/": {
"view": "members.htm"
},
"/": {
"view": "index.htm"
}
}
}
You will be redirected to http://localhost/list-of-members/machinisteweb/
when you access to http://localhost/list-of-members/machinisteweb
with a header permanent redirect.
For the second match use $1, the third $2, etc.
By défault, sent headers by NodeAtlas are followings: Content-Type:text/html; charset=utf-8
with a 200 statusCode
.
It's possible to modify this values for a specific route (for local API for example).
{
"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
}
}
}
It's also possible to modify all headers values, this erase all shortcuts before (except the statusCode
). Set a value to false remove this header previously setted.
{
"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
}
}
}
}
In replacement of static .json
config files, you can use dynamic .js
config files. In this case, your .js
file can provide in module.exports
a valide JSON file.
And it is possible to replace this six following files:
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/",
}
by only this two following files:
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 + "/",
};
}());
with the following supposed set of environment variables on the four following environments:
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
It is very simple to run an instance of NodeAtlas with HTTPs protocol. You just have to create such a security
folder in which to place your server.key
and server.crt
file to supply the protocol.
Just use the following configuration:
{
"httpSecure": true,
"httpSecureKeyRelativePath": "security/server.key",
"httpSecureCertificateRelativePath": "security/server.crt",
"routes": {
"/": {
"view": "index.htm"
}
}
}
Alternatively , if your two .key
and .crt
files have the same name, use this configuration:
{
"httpSecure": "security/server",
"routes": {
"/": {
"view": "index.htm"
}
}
}
This is also possible to just set the httpSecure
value to true
to get a "https" like urlBasePath
or urlBase
in your paths variables. But the server will not run in HTTPs and you will validate certificate by your own way (with a server proxy for example).
{
"httpSecure": true,
"routes": {
"/": {
"view": "index.htm"
}
}
}
Note: in production, if you use a proxy for redirect request/response, don't forget use urlPort: 443
instead of urlPort: 80
for HTTPs.
You can also manager how the server will respond to requests GET/POST to a given page. For example, we will allow access to pages only GET for the whole site and allow a POST to one page only (and prohibited him GET).
{
"get": true,
"post": false,
"routes": {
"/": {
"view": "index.htm"
},
"/list-of-members/": {
"view": "members.htm"
},
"/write-comment/": {
"view": "write-com.htm"
},
"/save-comment/": {
"view": "save-com.htm",
"get": false,
"post": true
}
}
}
Note: if nothing is set, get
and post
are set to true
in global webconfig and by route.
They work in the same way as get
and post
. This two HTTP actions PUT and DELETE are by default not activated. To active it use put
and 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
}
}
}
With the configuration below, only one HTTP action is possible by route, this is a great way to create APIs REST easily with NodeAtlas.
By default preflighted requests are not enable. You will need its, for example, to do CORS requests. Prefilghted requests are send with OPTIONS HTTP method.
To activate OPTIONS for a route, use the options
property on a route of the webconfig. To activate OPTIONS on all routes, use in this case the property options
in the webconfig in 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
}
}
}
If you want authorize a ressource on the NodeAtlas server requested by domain www.domain-a.com
for a single page, you could do like this:
{
"routes": {
"/api/random-quote": {
"controller": "get-quote.js",
"headers": {
"Access-Control-Allow-Origin": "http://www.domain-a.com"
}
}
}
}
With that, you will be able to accept for example a request from Origin
, http://www.domain-a.com
which is a value in Access-Control-Allow-Origin
:
GET /api/random-quote HTTP/1.1
Host: www.domain-a.com
...
Origin: http://www.domain-a.com
...
Cross-Domain Request with Token
If you want authorize resources from NodeAtlas server to the request from anywhere for the /api/random-quote
page and the page /api/protected/random-quote
that claims an authentification token, you could do that like this:
{
"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
}
}
}
NodeAtlas will parse a token from the external domain if this token is sent by Authorization
headers in the request. For allows NodeAtlas to accept this request, define it into Access-Control-Allow-Headers
with the accepted value Authorization
. Send a token need a preflight request so it's required to set options
to true
to authorize HTTP Request with OPTIONS method.
Now, you will be able to accept for example the following request which sends an authentification token to our server for the /api/protected/random-quote
resource:
GET /api/protected/random-quote HTTP/1.1
Host: localhost:1337
...
Origin: http://localhost
Authorization: Bearer CODE_DU_JETON
...
Other Cross-Domain requests
All headers for CORS features are accepted by the headers adding mechanism of NodeAtlas.
NodeAtlas itself manages sessions stored on the server as initial settings:
nodeatlas.sid
1234567890bépo
that allows customers to stay connected through the pages to a single set of personal server-side variable.
It is possible to change the default settings (and even compulsory for productions sites) with the parameters of webconfig.json
following:
{
"sessionKey": "personal key",
"sessionSecret": "personal secret"
}
NodeAtlas also employs a memory storage object (MemoryStore) to stock the information in the RAM of the server.
It is possible to change all the parameters of the sessions (except MemoryStore) using the configuration of next webconfig.json
:
{
"session": {
"key": "personal key",
"secret": "personal secret",
"cookie": {
"path": "/",
"httpOnly": true,
"secure": false,
"maxAge": null
},
...,
...,
...
}
}
The entirety of the possible configuration is located on the module documentation express-session.
By default, this is NodeAtlas server that stores sessions in the RAM of the server application. This does not allow users to share sessions across multiple applications NodeAtlas (or other) and erases all current sessions for an application if you restart it.
To address this concern, it should support the recording sessions via a base No SQL such as Redis
or MongoBD
.
You just have to use the setSessions
function in common controller
file.
Implement the following code in the common controller
file to store your sessions in a local Redis.
exports.setModules = function () {
var NA = this;
NA.modules.RedisStore = require("connect-redis");
};
exports.setSessions = function (next) {
var NA = this,
session = NA.modules.session,
RedisStore = NA.modules.RedisStore(session);
NA.sessionStore = new RedisStore();
next();
};
More information to connect-redis page.
Implement the following code in controllers/common.js
to store sessions in the database sessions
of a local MongoDB.
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();
};
More information to connect-mongo page.
It is possible to generate a different URL listening other port with urlHostname
and urlPort
. For example, the local loop listens on port 80 for a script makes the Reverse Proxy from the port 7777 on the 80 with the "http-proxy" module as below:
{
"httpPort": 7777,
"httpHostname": "127.0.0.1",
"urlPort": 80,
"urlHostname": "localhost",
"routes": {
"/": {
"view": "index.htm"
}
}
}
It is possible that the paths created from your URL to be interpreted as subfolders that have actually no real existence. This has the effect the address media/images/example.jpg
initially accessible from template displayed to address http://localhost impossible to reach when the template is displayed to address http://localhost/sub-directory/ (because the path should be ../media/images/example.jpg
).
To no longer have to worry about access to resources regardless of the URL that is requested, simply turn on all the URLs such as:
<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>
in absolute URLs with variable urlBasePath
as below:
<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>
Note that in the case of the following configuration:
{
"routes": {
"/": {
"view": "index.htm"
}
}
}
urlBasePath
return http://localhost/
while in this configuration:
{
"httpPort": 7777,
"urlRelativeSubPath": "sub/folder",
"routes": {
"/": {
"view": "index.htm"
}
}
}
urlBasePath
return http://localhost:7777/sub/folder/
.
Using the following webconfig:
{
"routes": {
"/index.html": {
"view": "index.htm"
},
"/contact.html": {
"view": "contact.htm"
}
}
}
and the corresponding template
<!-- ... -->
<a href="http://localhost/index.html">Link to home</a>
<a href="http://localhost/contact.html">Link to contact</a>
<!-- ... -->
I'd have to change my link in the template if I change the listening port or if I change the path of the URL. The following configuration changes:
{
"httpPort": 7777,
"routes": {
"/home.html": {
"view": "index.htm"
},
"/contact-us.html": {
"view": "contact.htm"
}
}
}
force me to modify the previous template like that:
<!-- ... -->
<a href="http://localhost:7777/home.html">Link to home</a>
<a href="http://localhost:7777/contact-us.html">Link to contact</a>
<!-- ... -->
You can solve this problem by giving a key to a specific path and deporting are way in the url
property.
With the followinh webconfig:
{
"routes": {
"index": {
"url": "/index.html",
"view": "index.htm"
},
"contact": {
"url": "/contact.html",
"view": "contact.htm"
}
}
}
I can now write the link in the dynamic template:
as follows
<!-- ... -->
<a href="<?= urlBasePath ?><?= webconfig.routes.home.url.slice(1) ?>">Link to home</a>
<a href="<?= urlBasePath ?><?= webconfig.routes.contact.url.slice(1) ?>">Link to contact</a>
<!-- ... -->
Note: .slice(1)
makes it easy to remove the dual /
for standard URL.
or as follows
<!-- ... -->
<a href="<?= urlBasePath ?>.<?= webconfig.routes.home.url ?>">Link to home</a>
<a href="<?= urlBasePath ?>.<?= webconfig.routes.contact.url ?>">Link to contact</a>
<!-- ... -->
Note: This would, for example http://localhost/./home.html
, which is a standard URL.
ou comme suit
<!-- ... -->
<a href="<?= urlBasePathSlice + webconfig.routes.home.url ?>">Link to home</a>
<a href="<?= urlBasePathSlice + webconfig.routes.contact.url ?>">Link to contact</a>
<!-- ... -->
Note : urlBasePathSlice
return http://localhost
in place of http://localhost/
or http://localhost:7777/sub/folder
in place of http://localhost:7777/sub/folder/
.
It's maybe useful to know the key used for the current page displayed for find the equivalent page in an other language.
With the following webconfig:
{
"languageCode": "en-us",
"routes": {
"index_en-us": {
"url": "/",
"view": "/index.htm"
},
"index_fr-fr": {
"url": "/francais/",
"view": "index.htm",
"languageCode": "fr-fr"
},
"cv_en-us": {
"url": "/resume/",
"view": "cv.htm"
},
"cv_fr-fr": {
"url": "/francais/cv/",
"view": "index.htm",
"languageCode": "fr-fr"
}
}
}
and the common variation following:
{
"language": [{
"name": "English",
"code": "en-us"
}, {
"name": "French",
"code": "fr-fr"
}]
}
in fr
:
{
"language": [{
"name": "Anglais",
"code": "en-us"
}, {
"name": "Français",
"code": "fr-fr"
}]
}
we could create link between each page as following:
<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>
It is possible to let the Express Template Engine implementation to bypass the NodeAtlas Template Engine implementation for view render. To do this, use the engine
parameter. See an example with the Handlebars engine:
First, add the Express Handlebars middleware amongs your modules:
npm install express-handlebars
then, use engine
with the arbtrary hbs
value
{
"engine": "hbs",
"controller": "common.js",
"variation": "common.json",
"routes": {
"/": {
"view": "index.hbs",
"variation": "index.json"
}
}
}
and explain to Express from NodeAtlas how to render views:
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();
};
finaly, see what could be the content of index.hbs
:
<!DOCTYPE html>
<html lang="en-us">
<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>
The goal of engine
, it is to not use the NodeAtlas Template Engine but use it from Express. Because Express need a response
object to render view, it is not possible to use this feature with the NA.view
function from NodeAtlas API. NA.view
only support, EJS, Pug and NodeAtlas syntaxes.
engine
, templateEngineDelimiter
and pug
It's possible to render EJS and Pug view with the Express Template Engine. In this case, because node-atlas
use already ejs
and pug
modules as dependencies, it is not mandatory to use a controller
and a npm
command to set them. You have just to set engine: "ejs"
or engine: "pug"
.
However, do this remove all additional feature added by NodeAtlas for this engines like for example the dynamic include of view for Pug in the common view
file with #{routeParameters.view}
.
It is possible to not using a view and only use a controller. In this case, the changeVariations
hook is unused. You will fill the locals.dom
value yourself with the changeDom
hook.
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();
};
So to the http://localhost/huey/?query=dewey
URL requested in POST with member=louie
body you will have the ouput:
{
"params": "huey",
"query": "dewey",
"body": "louie"
}
No webconfig example not use the routes
parameter. But it is also optional than others. For example, with the following webconfig:
webconfig.json
{
"controller": "common.js"
}
and the following controller:
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();
};
It is possible to have at the address http://localhost/
a simple "Hello World" message.
It's a good thing to not serve file with no modification in production. You could set the websconfig's cache
option to true
for this:
{
"cache": true,
"route": {
"/": "index.htm"
}
}
You can also start node-atlas
with --cache
option :
node-atlas --cache
or set your environment variable NODE_ENV
to production
:
if you are in Unix / MacOS
export NODE_ENV=production
or if you are in Windows
SET NODE_ENV=production
or you can run NodeAtlas like this:
NODE_ENV=production node-atlas
or you can also set it in your JavaScript file:
process.env.NODE_ENV = "production";
We will see now how to use data from the database. We will use MySQL for this example. The mysql
npm module will be useful. And first, install a MySQL server.
So, from your webconfig.json
directory, use:
npm install mysql
First, we will create a database demo
on the server:
CREATE DATABASE demo;
and select it:
USE demo
and create a user
table:
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)
);
and fill it with this set of data:
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"
);
See now what files we will create to present our example:
├─ controllers/
│ ├─ common.js
│ └─ index.js
├─ models/
│ ├─ objects/
│ │ └─ user.js
│ └─ connectors/
│ └─ user.js
├─ views/
│ └─ index.htm
├─ variations/
│ ├─ common.json
│ └─ index.json
└─ webconfig.json
We will use the following webconfig.json
with the custom _mysqlConfig
variable which contains all information for database connection:
{
"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"
}
}
Then, we will be connected to the database with the common controller controllers/common.js
:
exports.setModules = function () {
var NA = this;
// Import of `mysql` module.
NA.modules.mysql = require('mysql');
// Create a model collection...
NA.models = {};
// ...and use the User model with MySQL connection capability.
NA.models.User = require('../models/connectors/user.js');
};
exports.setConfigurations = function (next) {
var NA = this,
path = NA.modules.path,
mysql = NA.modules.mysql;
// Create a connection pool to MySQL.
NA.mySql = mysql.createPool(NA.webconfig._mysqlConfig);
next();
};
And display result via specific controller 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;
}
// Read example.
user
.setConnection(connection)
.lastname("Elric")
.read(function (allUsers) {
locals.user = user;
locals.users = allUsers;
// Create Example.
user2
.setConnection(connection)
.firstname("Winry")
.lastname("Rockbell")
.email("winry.rockbell@fma.br")
.gender(true)
.create(function (infos) {
locals.insertId = infos.insertId;
locals.user2 = user2;
// Update Example.
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;
// Delete Example.
user4
.setConnection(connection)
.gender(false)
.delete(function (infos) {
locals.deletedRows = infos.affectedRows;
next();
});
});
});
});
});
};
with the user
model via connecting file to database 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;
based on user
class shared between client-side and server-side 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;
}
};
}));
With following files to display the page:
views/index.htm
<!DOCTYPE html>
<html lang="en-us">
<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": "Example MySql",
"male": "Man",
"female": "Woman"
}
variations/index.json
{
"titlePage": "User Table",
"content": "<p>First entry details.</p>",
"contents": "<p>All entries details.</p>",
"contentInsert": "<p>Added and Updated user details.</p>"
}
You will get the following output:
<!DOCTYPE html>
<html lang="en-us">
<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>
We will see now how to use data from NoSQL database. We will use the mongoose
npm module. And first, install a MongoDB server.
So, from your webconfig.json
directory, use:
npm install mongoose
First, we will create a database demo
on the server and select it:
use demo
and create a user
collection:
db.createCollection("user")
and fill it with this 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"
}
})
With the following data set:
├─ controllers/
│ ├─ common.js
│ └─ index.js
├─ models/
│ └─ user.js
├─ views/
│ └─ index.htm
├─ variations/
│ ├─ common.json
│ └─ index.json
└─ webconfig.json
We will use the following webconfig.json
with the custom _mongodbConfig
variable which contain all informations for database connection:
{
"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"
}
}
With following files to display the 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": "Example MongoDB",
"male": "Man",
"female": "Woman"
}
variations/index.json
{
"titlePage": "User Collection",
"content": "<p>Document `{ \"identity.firstname\": \"John\" }` details.</p>"
}
And last, we will be connected to the database with the common controller 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();
});
};
And display result via specific controller controllers/index.js
:
exports.changeVariations = function (next, locals) {
var NA = this,
mongoose = NA.modules.mongoose,
User = mongoose.model('user');
User
.findOne({ "identity.firstname": "Bruno" })
.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();
});
};
based on user
classe shared between client-side and server-side part 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
}
})));
You will get the following output:
<!DOCTYPE html>
<html lang="en-us">
<head>
<meta charset="utf-8" />
<title>MongoDB Example</title>
</head>
<body>
<div class="title">MongoDB Example</div>
<div>
<h1>User Collection</h1>
<p>Collection `{ "identity.firstname": "Bruno" }` details.</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>
An isomorphic app is an app which JavaScript source code is for a big part the same as client-side executed code and as server-side executed code. NodeAtlas provide an example of an isomorphic app in the template dedicated to Vue.js.
For test this, just:
create a test folder:
mkdir hello-vue
cd hello-vue
then place it the hello-vue
content
node-atlas --create hello-vue
then install dependencies
npm install
and finaly run the french version
node-atlas --browse
or the international version
node-atlas --browse --webconfig webconfig.en-us.json
You will find all you need about server-side code from constrollers/common.js
and client-side code on https://ssr.vuejs.org/ and from assets/javascripts/common.js
on https://vuejs.org/.