Creating HapiJs server that can serve complex web pages - node.js

I have just started learning Node.js and hapi.js. What I am trying to accomplish now is build a REST web server that should also have a web interface for configuration and statistics collection.
I found that Inert plugin allows serving static pages and, as I understand, this limits me to loading a single web page that consists of a single file.
However, what I do not understand is whether it is possible to setup hapi.js to serve a full dynamic webpage with css, js and other files referenced within its body.
Am I heading the wrong direction with this or else how can I setup my scenario?

You can serve multiple static files from a specified directory.
Just tried out inert with hapi, using this example:
https://github.com/hapijs/inert#static-file-server
Inert has no problem serving multiple static files from a given directory, e.g public.
So you'll have no issue serving multiple static html, css, js files from a specified dir. You can then build a dynamic JSON api using Hapi, and have that consumed by your js client-side code, served from static js files in your public dir.
If you are needing templating, where you generate dynamic content on the serverside, hapi can do that out of the box, check out:
http://hapijs.com/tutorials/views
Sorry if this isn't what you mean, do feel free to clarify if not :-)
Hope that helps!

The vision plugin is used for templating, which is what I think you're after. If you want to bundle css and js files along with your pages, you can put them in a public directory and serve that with the inert plugin. And then you only need to reference the relative path in whatever html file you're trying to render.
Here's a simple example that uses handlebars. Inert is responsible for serving your css and js files while vision still renders your templates.
./index.js
var hapi = require('hapi');
var server = new hapi.Server();
server.connection({port: 5555});
server.register([require('vision'), require('inert')], (err) => {
if(err){
throw err;
}
server.views({
engines: {
html: require('handlebars')
},
relativeTo: __dirname + '/',
path: 'www'
});
var homeroute = {
method: 'GET',
path: '/',
handler: (request, reply) => {
reply.view('index', {name: 'cuthbert'});
}
};
var publicassetsroute = {
method: 'GET',
path: '/public/{param*}',
handler: {
directory: {
path: './public',
listing: false,
index: false
}
}
};
server.route(homeroute);
server.route(publicassetsroute);
server.start((err) => {
console.log('server started -- ' + server.info.uri)
});
});
www/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hapi Test</title>
<link rel="stylesheet" href="../public/index.css">
</head>
<body>
<h1>A Hapi Happy Test.</h1>
<p>This is a test page. Woo!</p>
<p>My name is {{name}}.</p>
</body>
</html>
public/index.css
p {
color: blue;
}

Related

How to return a 404 Not found page in an Express App?

I have an express app, in which I have the following code:
app.get('*', (req, res) => {
res.send('404', {
title: 404,
name: 'James Olaleye',
errorMessage: 'Page not found',
});
});
However, My IDE is warning about this message:
express deprecated res.send(status, body): Use
res.status(status).send(body) instead
And with the above code, My Browser is returning the following payload as a JSON object:
{
"title": 404,
"name": "James Olaleye",
"errorMessage": "Page not found"
}
What I want, is to display a 404 Not found page to the user, how can this be achived?
You have two seperate problem
1: you are using an old way to response to the request insted use this res.status(STATUS_CODE).send(BODY)
2: you are sending a json yet you want to display a 404 page in this case you need to send a html template
so your code should look like this
app.get('*', (req, res) => {
res.status(404).send("<div>404 Not Found</div>");
});
I updated your question a bit to make it clearer for future references.
the method res.send is deprecated, among other things because it's usages is too ambiguous. A server response, can be a lot of things, it can be a page, it can be a file, and it can be a simple JSON object (which you have here).
In your case, when you run res.send(404,{ /*...*/ }), the express app assumes you want to send a JSON object, so it does just that.
There are multiple possible ways, to achieve what you want, but I will stick to the most simple solution.
If you want to display an HTML page, in the most simplest form, you can actually just change your piece of code to do this instead:
app.status(404).send(`<h1>Page not found</h1>`)
This will essentially, show a page, instead of a JSON object.
You can even define the whole HTML file if you like:
app.status(404).send(
`
<html>
<!DOCTYPE html>
<head>
<title>404</title>
</head>
<body>
<h1>James Olaleye</h1>
<h1>Page Not Found</h1>
</body>
</html>
`
)
This would be the fastest way to achieve what you want.
A step further, would be to create an HTML file some where in your app, and to send the HTML file instead.
If your source code looks like this:
/
src/
index.js
htmls/
404.html
<!-- htmls/404.html -->
<html>
<!DOCTYPE html>
<head>
<title>404</title>
</head>
<body>
<h1>James Olaleye</h1>
<h1>Page Not Found</h1>
</body>
</html>
// src/index.js
const express = require('express');
const app = express();
const path = require('path');
const PORT = 3000;
app.get('/', function(req, res) {
const options = {
root: path.join(__dirname, '..', 'htmls')
};
res.sendFile('404.html', options, function (err) {
if (err) {
next(err);
} else {
console.log('Sent:', fileName);
}
});
});
This would allow you to have multiple HTML files which you can send around.
There are like I stated, other options as well, but that would make this answer way too long and out of scope. If you are interested, you can research Using template engines with Express and start with the following link.
Happy coding :)

Node Express i18next - How to send locale text to client side js file?

I have i18next setup in Node.js server side. What is the best practice to use i18next.t() in client side javascript files? I've already set up Express to render server side variables to be used in .ejs files. However, I can't transfer them to the js files that are imported in ejs.
Some possible ways I've thought of:
Export and load i18next that was initialized in the server to the client.
I know the <%= %> variables for ejs work in inline scripts. However, I'm avoiding to have them for content security policy. Perhaps there is a way to send this over to the js file?
Load and initialize i18next on the client-side again. I've tried this and it works, but then there are duplicated locale files for both server and client.
Export the locale files by specifying path. EX
app.use('/locale', express.static(path.join(__dirname, 'locale', {{lng}}.json)));
// app.js
const i18next = require('i18next');
const Backend = require('i18next-fs-backend');
const middleware = require('i18next-http-middleware');
i18next.use(Backend)
.use(middleware.LanguageDetector)
.init({
detection: detection_options,
fallbackLng: 'en',
backend: {
loadPath(lng, ns) {
if (lng === 'zh' || lng === 'zh-HK' || lng === 'zh-TW') {
return path.join(__dirname, 'locales/zh-hant.json');
} else if (lng === 'en-US') {
return path.join(__dirname, 'locales/en.json');
}
return path.join(__dirname, 'locales/{{lng}}.json');
}
}
})
app.use(middleware.handle(i18next));
// index.ejs
<%- include('../includes/head.ejs') %>
<link rel="stylesheet" href="/css/index.css">
</head>
<body>
<h1>
<%= t('locale-text-from-en.json-goes-here') %> // this works well
</h1>
<script src="/js/index.js"></script>
</body>
// index.js
console.log(t('locale-text-from-en.json-goes-here')) // how to use i18next.t() here?
To use i18next on the client you need to install the i18next package via npm or yarn on the client. Or download i18next library via their CDN https://unpkg.com/i18next/dist/umd/i18next.js.
i18next has many extensions for your project, like react-i18next for react project, jquery-i18next for jquery project.

Ejs doesn't load when I call a function from another file

Basicly I want to run a function when I clicked the button, but it works when I started the server and go to localhost one time, here's what's supposed to happen, after that localhost page doesn't load. (Unable to connect error)
If I remove the function there is no problem. How can I get it to work only when I click the button ?
Many thanks.
My func.js
const mongoose = require("mongoose");
const axios = require('axios');
async function func() {
//MyCodes
}
module.exports = {
func: func
}
My index.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<button type="button" onClick= <%= func.func() %> >Click</button>
//Other codes are independent the button
</body>
</html>
My res.render codeblocks in app.js
var func = require('./func');
app.get("/", (req, res) => {
res.render('index', {
cName: name,
symbol: symbol,
len: finds[0].result.length,
cPrice: price,
cDate: date,
func:func
});
});
});
})
You are misunderstanding. You cannot call an internal nodejs function(backend) from the html (frontend). If your frontend need to execute some backend operation like query to mongo, you have these options:
#1 client side rendering (Modern)
This is the most used in the modern world: Ajax & Api
your backend exposes a rest endpoints like /products/search who recieve a json and return another json
this endpoints should be consumed with javascript on some js file of your frontend:
html
<html lang="en">
<head>
<script src="./controller.js"></script>
</head>
<body>
<button type="button" onClick="search();" >Click</button>
</body>
</html>
controller.js
function search(){
$.ajax({
url:"./api/products/search",
type:"POST",
data:JSON.stringify(fooObject),
contentType:"application/json; charset=utf-8",
dataType:"json",
success: function(response){
...
}
})
}
Note 1: controller.js contains javascript for browser not for backend : nodejs
Note 2: ejs is only used to return the the initial html, so it is better to use another frameworks like:react, angular, vue
#2 server side rendering (Legacies)
In this case, ajax and js for browser are not strictly required.
Any event on your html should use <form> to trigger an entire page reload
You backend receives any parameter from the , make some operations like mongo queries and returns html instead json, using res.render in your case
Note
Ejs is for SSR = server side rendering, so add ajax could be complex for novices. In this case, use the option #2
You cannot use a nodejs function (javascript for server) in the client side (javascript for browser). Maybe some workaround are able to do that but, don't mix different things.

React Components on the Server

I've been playing around with React for a while now but still can't wrap my head around on how to integrate it with an existing node/express/handlebars app.
For example, if I had a feed component which required json data to be fetched from AWS - how do I handle this.
var VideoFeed = require('./component/VideoFeed');
app.use('/', function(res, req) {
DataService.getVideoFeed().then(function(data) {
res.render('home', {videoComponent: React.renderToString(<VideoFeed feed={data} />);
});
});
Home
<!DOCTYPE html>
<body>
Some sample text. Here's the Video Feed
{{videoComponent}}
</body>
</html>

How to use Node.js to build pages that are a mix between static and dynamic content?

All pages on my 5 page site should be output using a Node.js server.
Most of the page content is static. At the bottom of each page, there is a bit of dynamic content.
My node.js code currently looks like:
var http = require('http');
http.createServer(function (request, response) {
console.log('request starting...');
response.writeHead(200, { 'Content-Type': 'text/html' });
var html = '<!DOCTYPE html><html><head><title>My Title</title></head><body>';
html += 'Some more static content';
html += 'Some more static content';
html += 'Some more static content';
html += 'Some dynamic content';
html += '</body></html>';
response.end(html, 'utf-8');
}).listen(38316);
I'm sure there are numerous things wrong about this example. Please enlighten me!
For example:
How can I add static content to the
page without storing it in a string as a variable value with += numerous times?
What is the best practices way to build a small site in Node.js where all pages are a mix between static and dynamic content?
Personally, I'd use a server that has higher level constructs. For instance, take a look at the expressjs framework - http://expressjs.com/
The constructs you'll be interested in from this package are:
Truly static files (assets etc): app.use(express.static(__dirname + '/public'));
A templating language such as jade, mustache, etc:
http://expressjs.com/en/guide/using-template-engines.html
https://github.com/visionmedia/jade/
You'll want to look up 'locals' and 'partials' for embedding small bits of dynamic content in mostly static content
For example in jade:
!!! 5
html(lang="en")
head
title= pageTitle
script(type='text/javascript')
if (foo) {
bar()
}
body
h1 Jade - node template engine
#container
- if (youAreUsingJade)
p You are amazing
- else
p Get on it!
Becomes:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Jade</title>
<script type="text/javascript">
if (foo) {
bar()
}
</script>
</head>
<body>
<h1>Jade - node template engine</h1>
<div id="container">
<p>You are amazing</p>
</div>
</body>
</html>
If you prefer something a little less drastic I would say look at mustache or one of the other engines that looks a bit more like regular-sauce html.
Alternative you can just use jsDOM. This means you have a DOM object you can manipulate on the server to add your dynamic content, then you can just flush the DOM as a HTML file / string
These days the answer is not so straightforward.
If you don't need to be indexed by Google, consider making a single-page application using socket.io and client-side templates such as jQuery Templates. There are even emerging node.js frameworks for this type of architecture, e.g. socketstream.
If you need to be indexed by Google, do you need your dynamic content to be indexed? If yes,
consider using express and server-side templates such as ejs, jade or mustache. Another (discouraged) approach might be to generate XML from JSON on server and use an XSLT front-end.
If you need only static content to be indexed, consider using express on server, but don't generate any dynamic HTML on server. Instead, send your dynamic content in JSON format to client using AJAX or socket.io, and render it using client-side templates such as jQuery Templates.
Don't consider server-side DOM: DOM doesn't scale for complex layouts, you will sink in a sea of selectors and DOM calls. Even client-side developers understood that and implemented client-side templates. A new promising approach is weld library. It offers best of both worlds, but it is not mature yet to be used in production (e.g. simple things like conditional rendering are not supported yet).
One good way is to use a templating engine. You can store the templates as separate files, and the templating engine has the ability to make the content dynamic. Personally I use yajet (http://www.yajet.net/) which is written for the web but works fine with node, and there are numerous template engines for node on npm.
One of the best things I found is to use NodeJS, Express and Mustache...
You can create your HTML pages as you normally would using Mustache syntax for placeholders for your variables {{name}}...
When a user hits your site, express routs the slug to the correct template...
NodeJS get's the file...
NodeJS get's the dataset from a DB...
Run it through Mustache on the server...
Send the completed page to the client...
Here is a scaled back version I wrote on my blog. It's simple but the idea is pretty sound. I use it to quickly deploy pages on my site.
http://devcrapshoot.com/javascript/nodejs-expressjs-and-mustachejs-template-engine
I went this route because I didn't want to learn all of the extra syntax to write a language I already knew (html). It makes more sense and follows more of a true MVC pattern.
First deliver only static HTML files from server to the client. Then use something like AJAX / server.io to serve the dynamic content. IMO Jade is really ugly for writing HTML code and its better to use a template engine.
I did some Google and found some code by this fellow, its good if you are doing it for PoC / learning.
var server = require('./server');
var controller = require("./controller");
var urlResponseHandlers = require("./urlResponseHandlers");
var handle = {};
handle["/"] = urlResponseHandlers.fetch;
handle["/fetch"] = urlResponseHandlers.fetch;
handle["/save"] = urlResponseHandlers.save;
server.start(controller.dispatch, handle);
Here is how the logic for handling URLs is displayed -
var staticHandler = require('./staticHandler');
function dispatch(handler, pathname, req, res) {
console.log("About to dispatch a request for " + pathname);
var content = "Hey " + pathname;
if (typeof handler[pathname] === 'function') {
content += handler[pathname](req);
res.writeHead(200, {
'Content-Type': 'text/html'
});
res.write(content);
res.end();
} else {
console.log("No request handler found for " + pathname);
staticHandler.handleStatic(pathname, res);
}
}
Here is how static files can be handled -
function handleStatic(pageUrl, response) {
var filename = path.join(process.cwd(), pageUrl);
path.exists(filename, function (exists) {
if (!exists) {
console.log("not exists: " + filename);
response.writeHead(404, {
'Content-Type': 'text/html'
});
response.write('404 Not Found\n');
response.end();
return;
}
//Do not send Content type, browser will pick it up.
response.writeHead(200);
var fileStream = fs.createReadStream(filename);
fileStream.on('end', function () {
response.end();
});
fileStream.pipe(response);
return;
});
}
exports.handleStatic = handleStatic;
I liked the idea. All code copied from this link!
.
A solution have found to this, without using any other modules and or other script is to make the calling script into a module and include it with the function require().
With this solution I can use javascript which ever way I want
What I would do is make an ajax call to a nodejs script (www.example.com/path/script.js)
script.js would need to be built like a module with the exports.functionName=function(){...}
After that include it in your webserver function require(pathToTheScript).functionName(res,req)
You will also need to end the response in the functionName(res,req) by doing res.end();

Resources