It's Sunday, which for Joe it means that he has much time to continue his business on learning Hapi. In his pajamas, he opens his laptop, turns it on, and then navigates to Hapi documentation to learn about how to create a server.
From the documentation, Joe found that to create a server in Hapi, he needs to do the following.
First, the most obvious one, he needs to import Hapi and store it inside a variable.
To execute this step, he creates his first file and names it app.js
. Then, he imports Hapi and assigning to a variable named Hapi. Actually, he can name it anything, but he chooses the most typical one.
const Hapi = require("@hapi/hapi");
Second, he needs to create a server object by calling Hapi.server()
and assigning it into a variable. Again, he can name it anything, he chooses the most common one: 'server'.
const server = Hapi.server();
"That's it? Wow, super easy! He said.
After having the server object, Joe wanted to start it. After all, what's the purpose of a server if it just sits there and serves nothing, right?
Then, Joe call server.start()
to start the server.
That alone will work, however, the documentation mentions that server.start()
is actually a promise that returns no value. And a promise is still a promise even if it returns no value, right? So he needs to treat it properly either by using .then()
or async/await
. He opted for the async/await
since it makes his code concise.
It means Joe needs to create an async function that wraps the await server.start()
. Also, it would be nice if he caught its error too, in case something gone wrong.
Now his code looks like this:
const init = async () => {
try {
await server.start();
console.log("Server started....");
} catch (error) {
console.log(error);
}
};
So, now to really start the server, Joe calls init()
instead of calling server.start()
directly.
Finally, Joe's app.js
file looks like this:
const Hapi = require("@hapi/hapi");
const server = Hapi.server();
const init = async () => {
try {
await server.start();
console.log("Server started....");
} catch (error) {
console.log(error);
}
};
init();
Then he opens up his terminal, navigating to his project folder, and runs node app.js
. Instantly, he sees the terminal shows the Server started...
log message. It means his server is working. Yay!
Server Properties
After successfully started the server. Joe dives deeper into the documentation to find out more about the server object. He finds that the server has several properties. Most of them are read-only and seem too advanced for him right now.
One particular property that picks his interest is server.info
. It's an object that describes the server about its id, server creation and starts time, port, host, address, protocol, and uri.
Joe wants to see server.info
in action, so he adds it into his init function.
const init = async () => {
try {
await server.start();
console.log("Server started....");
console.log(server.info);
} catch (error) {
console.log(error);
}
};
When he spins up the server, here is what he sees on his terminal:
Server started...
{
created: 1613264249044,
started: 1613264249059,
host: 'L645',
port: 40549,
protocol: 'http',
id: 'L645:6956:kl4fvpas',
uri: 'http://L645:40549',
address: '0.0.0.0'
}
At this point, Joe feels happy because he was able to know about his server information. But then he gets curious, "How to modify some of those properties?"
Server Options
After doing a quick search at the Server API documentation, he finds that he can configure the server by providing a server configuration object. Basically, it's just another server property like server.info
above.
The documentation shows a bunch of the available configurations. However, as a Hapi beginner, here are some options that he familiar with:
- host: refers to the operating system hostname, which is set at the time when the operating system is installed. If the machine has no hostname specified, it will default to 'localhost'.
- port: the TCP port the server will listen to. If not specified, it'll be assigned with the available port at the time the server started.
- tls: an option object to create https. It consists of key and cert properties which are identical to the option object passed into raw Node
https.createServer()
. - address: the hostname or IP address. Its default value is
0.0.0.0
. However, if Joe set thehost
option tolocalhost
then its value will be127.0.0.1
Then Joe tries to configure his server to listen to port 3000 and set host
to localhost
, which is a common setup he found on the internet.
const server = Hapi.server({
port: 3000,
host: "localhost",
});
Now when he spins up the server, the terminal shows something like this:
Server started...
{
created: 1613264576969,
started: 1613264576983,
host: 'localhost',
port: 3000,
protocol: 'http',
id: 'L645:7011:kl4g2qbt',
uri: 'http://localhost:3000',
address: '127.0.0.1'
}
Testing for Error
Up until now, Joe finds his code always works. While it's good, he wonders if the code inside the catch block of the init function actually can do his job. So he tries to cause an error by mistyping the hostname.
const server = Hapi.server({
port: 3000,
host: "loclhst",
});
Now when he run his server he sees:
Error: getaddrinfo EAI_AGAIN loclhst
at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:67:26) {
errno: -3001,
code: 'EAI_AGAIN',
syscall: 'getaddrinfo',
hostname: 'loclhst'
}
Then he removes the catch block to see the difference, so his init function looks like this:
const init = async () => {
await server.start();
console.log("Server started....");
console.log(server.info);
};
Now when he runs the server, he gets UnhandledPromiseRejectionWarning like this:
(node:10184) UnhandledPromiseRejectionWarning: Error: getaddrinfo EAI_AGAIN loclhst
at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:67:26)
(Use `node --trace-warnings ...` to show where the warning was created)
(node:10184) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:10184) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
Now Joe knew that when there is an error, the server stopped and the catch block inside the init function is doing its job. Without it, Node will spit out the UnhandledPromiseRejectionWarning. With it, he got a more friendly error message.
Watching for Changes
Joe feels like he ready to move on to the next topic. However, he feels that stopping and restarting the server every time he made changes is bothersome. He believes there must be a solution to this problem. And yes there is, nodemon
. It's a simple program that will watch for changes on his node app and restart it accordingly.
Without further ado, he installs nodemon by issuing npm install -g nodemon
command to his terminal. He adds the -g
flag because he want it available globally to all of his project.
So now instead of starting the server with node app.js
, he uses nodemon app.js
. This way, the server will restart automatically every time he made modifications to app.js
(as long as not an error, which will stop the server).
Joe decides to call it a day and rewards himself with a cone of ice cream. Among all other places in his house, he chooses the living room to enjoy the ice cream. He sits there facing the open door. He likes this spot because he can rest his eyes by looking at the dancing trees across his house. His mouth busy with the tasty ice cream while his mind happily recalls what he had accomplished today. Now he knows how to include Hapi in his project, how to create a server, and how to access the server's info. He also knows how to configure the server and successfully caught its error.
But he realizes that all of those things are only the beginning. His server still does nothing even if it's active. No users except him can interact with it yet. So he decides that tomorrow he needs to learn how to make his server does something more useful than simply outputting 'Server started...'. He wants his server capable of accepting and responding to requests.
Top comments (0)