├── .gitignore
├── .travis.yml
├── CONTRIBUTING.md
├── README.md
├── TLDR.md
├── examples
├── hapibell.js
├── hellogood.js
├── hellohapi.js
├── hellolog.js
├── helloparam.js
├── hellovalidate.js
├── public
│ └── hello.js
├── socketio.js
├── staticfiles.js
└── views
│ └── index.html
├── makemehapi
├── 01-HELLO_HAPI.js
├── 02-ROUTES.js
├── 03-HANDLING.js
├── 04-DIRECTORIES.js
├── 05-VIEWS.js
├── 06-PROXIES.js
├── 07-HELPING.js
├── 08-STREAMS.js
├── 09-VALIDATION.js
├── 10-VALIDATION-USING-JOI-OBJECT.js
├── 11-UPLOADS.js
├── 12-COOKIES.js
├── foo
│ └── bar
│ │ └── baz
│ │ └── file.html
├── helpers
│ └── helper.js
├── index.html
├── input.txt
└── templates
│ ├── helper-index.html
│ └── index.html
├── package.json
└── test
├── coverage.html
├── hellovalidate.test.js
└── test.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 |
3 | # Logs
4 | logs
5 | *.log
6 |
7 | # Runtime data
8 | pids
9 | *.pid
10 | *.seed
11 |
12 | # Directory for instrumented libs generated by jscoverage/JSCover
13 | lib-cov
14 | lcov.info
15 |
16 | # Coverage directory used by tools like istanbul
17 | coverage
18 | coverage.html
19 |
20 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
21 | .grunt
22 |
23 | # Compiled binary addons (https://nodejs.org/api/addons.html)
24 | build/Release
25 |
26 | # Dependency directory
27 | # Deployed apps should consider commenting this line out:
28 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
29 | node_modules
30 | .DS_Store
31 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "node"
4 | before_install:
5 | - pip install --user codecov
6 | after_success:
7 | - codecov --file coverage/lcov.info --disable search
8 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | _**Please read** our_
2 | [**contribution guide**](https://github.com/dwyl/contributing)
3 | (_thank you_!)
4 |
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | [](https://travis-ci.org/dwyl/learn-hapi)
4 | [](https://codecov.io/github/dwyl/learn-hapi?branch=master)
5 | [](https://codeclimate.com/github/dwyl/learn-hapi)
6 | [](https://david-dm.org/dwyl/learn-hapi)
7 | [](https://david-dm.org/dwyl/learn-hapi?type=dev)
8 | [![NPM Version][npm-image]][npm-url]
9 | [](https://hits.dwyl.com/dwyl/learn-hapi)
10 |
11 | # Learn Hapi
12 |
13 | Happiness is learning how to use the [**Hapi.js**](https://hapijs.com/) (Node.js) web framework to
14 | _**build reliable/scalable apps faster**_.
15 |
16 | ## What is Hapi?
17 |
18 | Hapi is *the* framework for rapidly building RESTful & Real-Time web applications and services with Node.js.
19 | Whether you are building a very simple API
20 | for your website/mobile app or a large scale, cache heavy,
21 | secure e-commerce website, hapi has you covered.
22 | Hapi will help get your server developed quickly with its wide range
23 | of configurable options.
24 |
25 | ### *Watch* this intro/background to Hapi video:
26 |
27 | [](https://youtu.be/BsyvnVOhp4U?t=3m50s "What is Hapi.js - Click to Watch!")
28 |
29 | *Most* people/teams that have _tried_ Hapi have _embraced_ Hapi to build *complete* web applications. But if you are only building a REST API (_e.g. for a mobile app_)
30 | please read:
31 | https://github.com/dwyl/learn-api-design
32 |
33 | ## _Why_ Hapi instead of XYZ framework?
34 |
35 | **Q**: I already know how to build REST APIs in `{framework-xyz}` why learn a *new* framework?
36 | **A**: If you are *happy* with your existing system & level of team productivity,
37 | stick with what you know. If not, learn [how to be] Hapi.
38 | (We have built Sites/APIs with both Express, Restify, Sails & Meteor and find Hapi has solved more
39 | "real world" problems and thus we end up writing less code. YMMV. See benefits below)
40 |
41 | **Q**: Hapi looks like quite a steep learning curve,
42 | how long will it take me to learn?
43 | **A**: You can get started *immediately* with the examples below,
44 | it will take _approximately **60 mins** to complete_ them all (after that add a couple of hours to read/learn further).
45 | The most important part is to ***try Hapi*** on a simple project to gain experience/confidence.
46 |
47 | ### Key Benefits
48 |
49 | - ***Performance*** - WalmartLabs are the guys who found/solved the
50 | [Node.js *CORE* Memory Leak](https://www.joyent.com/blog/walmart-node-js-memory-leak);
51 | they have developed Hapi following
52 | [Benchmark Driven Development](https://github.com/felixge/faster-than-c)
53 | and the result is a high-performance framework
54 | + ***Security*** - The *Lead* Developer of Hapi is [**Eran Hammer**](https://github.com/hueniverse) who was one of the original authors
55 | of the OAuth (Secure Authentication) Spec. He has built a security-focussed
56 | mindset into Hapi and reviews all code included in Hapi. Several members of the [Node Security Project](https://nodesecurity.io) are *core* contributors to
57 | Hapi which means there are many security-minded eyes on the code.
58 | - ***Scalability*** - they have focussed on *horizontal-scalability*
59 | and battle-tested the framework during [Black Friday](https://nodeup.com/fiftysix)
60 | (*holiday shopping busy day*) without incident.
61 | - **Mobile Optimised** (lightweight - built for mobile e-commerce)
62 | - **Plugin Architecture** - extend/add your own modules (good ecosystem)
63 | - ***DevOps Friendly*** - configuration based deployment and great stats/logging see: [#logging with good](https://github.com/dwyl/learn-hapi#logging-with-good) section below!
64 | - Built-in ***Caching*** (Redis, MongoDB or Memcached)
65 | - ***100% Test/Code Coverage*** (for the core) - *disciplined approach to code quality*
66 | + ***Testability*** - End-to-End testing is ***built-in*** to Hapi because
67 | it *includes* [**shot**](https://github.com/hapijs/shot)
68 | - **Key Functionality** is **Built-in** and there are *many* plugins for other
69 | features: https://hapijs.com/plugins
70 |
71 | ### _In-depth Comparison_ to Express.js
72 |
73 | @ethanmick wrote a detailed post on why _he_ prefers Hapi to Express:
74 | https://www.ethanmick.com/why-i-like-hapi-more-than-express/ --its worth a read.
75 | [PDF](https://github.com/dwyl/learn-hapi/files/502449/Why-I-like-Hapi-more-than-Express.pdf)
76 |
77 | ### _Beginner Friendly_ Examples/Apps to Learn From/With
78 |
79 | We have a few "_beginner_" example apps (with documentation & tests!)
80 | that will help you get started with something a bit more "real world":
81 |
82 | + Registration & Login (Basics): https://github.com/dwyl/hapi-login-example-postgres
83 | + Chat using Hapi, Redis & Socket.io: https://github.com/dwyl/hapi-socketio-redis-chat-example
84 |
85 | For a _list_ of examples see: https://github.com/dwyl?&query=example
86 |
87 |
88 | ## Who (_is using Hapi_) ? [](https://github.com/dwyl/learn-hapi/issues)
89 |
90 | The list of teams using Hapi.js to build their node.js apps grows every day!
91 | See: https://hapijs.com/community
92 |
93 | > While you should _not_ make your decisions to use a given technology
94 | based on who _else_ is using it, you should be _aware_ that
95 | and if you need to answer the **question**:
96 | "***Who is already using this in Production?***"
97 | it's _really_ useful to have a good list.
98 |
99 |
100 | ## _How?!_ (_Dive In_!)
101 |
102 | ## Requirements
103 |
104 | - [x] A **computer** that can run [**Node.js**](https://nodejs.org/download/) Mac/Windows/Linux/Chromebook
105 | - [x] Access to the Internet (only required for installation)
106 | - [x] 60 minutes of time +/-
107 |
108 | ### Optional
109 |
110 | (_Not essential before you start, however_) You will _benefit_ from having:
111 |
112 | + [x] Basic JavaScript knowledge
113 | + [x] Basic experience of using node.js's `http` module.
114 |
115 | ## Make Me Hapi ("_Official_" _Beginner Workshop_)
116 |
117 | First thing you should do to get familiar with Hapi is work through the
118 | [makemehapi](https://nodeschool.io/#makemehapi) workshop.
119 | (_assumes some [node.js](https://nodeschool.io/#learn-you-node) prior
120 | knowledge but otherwise a gentle self-paced introduction_)
121 |
122 | _Note: makemehapi currently uses Hapi v16. Some major changes were introduced to Hapi in v17. [Differences between v16 and v17](#hapi-v16)_
123 |
124 | Create a new folder on your local machine for your answers to **makemehapi**:
125 |
126 | ```
127 | mkdir makemehapi && cd makemehapi
128 | ```
129 |
130 | Install the workshop:
131 |
132 | ```
133 | npm install -g makemehapi@latest
134 | ```
135 | ( if it fails to install see: https://stackoverflow.com/questions/16151018/npm-throws-error-without-sudo )
136 |
137 | Once its installed, start the tutorial with the following command:
138 | ```
139 | makemehapi
140 | ```
141 |
142 | _Try_ to complete all challenges.
143 |
144 | 
145 |
146 | If you get *stuck*, you can either _google_ for the specific error you are
147 | seeing or if you are not "getting" it, you can always look at my answers in the /**makemehapi** directory of this repository ***or***
148 | _the_ "official" solutions
149 | in the **/makemehapi/exercises/{exercise-name}/solution** directory
150 | e.g: https://github.com/hapijs/makemehapi/tree/master/exercises/hello_hapi/solution
151 |
152 | or if you still don't get it, _**ask us**_:
153 | https://github.com/dwyl/learn-hapi/issues
154 |
155 |
156 |
157 | ## _Extended_ Examples
158 |
159 | For the rest of the tutorial we will cover the various
160 | plugins and features we have used in the Hapi.js ecosystem
161 | that will help you getting up-and-running with Hapi!
162 |
163 | ### Recap: Hello World in Hapi
164 |
165 | Once you have completed the **makemehapi** workshop,
166 | on your computer, create a new directory called "**hapiapp**". e.g:
167 |
168 | ```sh
169 | mkdir hapiapp && cd hapiapp
170 | ```
171 |
172 | Type out (or copy-paste) this code into a file called **index.js**
173 |
174 | ```js
175 | const Hapi = require('hapi');
176 | const server = new Hapi.Server({port: 3000}); // tell hapi which TCP Port to "listen" on
177 |
178 | server.route({
179 | method: 'GET', // define the method this route will handle
180 | path: '/{yourname*}', // this is how you capture route parameters in Hapi
181 | handler: function(req, h) { // request handler method
182 | return 'Hello ' + req.params.yourname + '!'; // reply with text.
183 | }
184 | });
185 |
186 | async function startServer() {
187 | await server.start() // start the Hapi server on your localhost
188 | console.log('Now Visit: http://localhost:' + server.info.port + '/YOURNAME');
189 | }
190 |
191 | startServer();
192 |
193 | module.exports = server;
194 | ```
195 | Install Hapi:
196 | ```
197 | npm init -y && npm install hapi --save
198 | ```
199 | Run:
200 | ```
201 | node .
202 | ```
203 |
204 | Visit: http://localhost:3000/YOURNAME (in your browser)
205 | you should see something like:
206 |
207 | 
208 |
209 |
210 | ### Validation with Joi
211 |
212 | **Validation** is a fancy way of saying "checking" a value is
213 | the **type** / **format** and **length** you expect it to be.
214 |
215 | e.g. imagine you ask people to input their phone number
216 | and some joker enters letters instead of numbers. The validation
217 | will display a message to the person informing the data is incorrect.
218 |
219 | [**Joi**](https://github.com/hapijs/joi) is the validation library built by
220 | the same team as Hapi.
221 | Most people use Joi with Hapi, but given that it is a separate
222 | module, plenty of people use Joi independently;
223 | its well worth checking it out!
224 |
225 | An example:
226 | Type out (or copy-paste) this code into a file called **hellovalidate.js**
227 |
228 | ```js
229 | // Start this app from your command line with: node hellovalidate.js
230 | // then visit: http://localhost:3000/YOURNAME
231 |
232 | const Hapi = require('hapi'),
233 | Joi = require('joi');
234 |
235 | const server = new Hapi.Server({ port: 3000 });
236 |
237 | server.route({
238 | method: 'GET',
239 | path: '/{yourname*}',
240 | config: { // validate will ensure YOURNAME is valid before replying to your request
241 | validate: {
242 | params: {
243 | yourname: Joi.string().min(2).max(40).alphanum().required()
244 | }
245 | },
246 | handler: function (req, h) {
247 | return 'Hello '+ req.params.yourname + '!';
248 | }
249 | }
250 | });
251 |
252 | async function startServer() {
253 | await server.start(); // start the Hapi server on your localhost
254 | console.log('Now Visit: http://localhost:' + server.info.port + '/YOURNAME');
255 | }
256 |
257 | startServer();
258 | ```
259 |
260 | Now try entering an _invalid_ name: http://localhost:3000/T
261 | You should see a **Validation Error**:
262 |
263 | 
264 |
265 | This might not _look_ like a "Friendly" Error message.
266 | But as we will see later, it provides all the information we need
267 | in our Client/App and we can display a more user-friendly error to people.
268 |
269 | [Joi](https://github.com/hapijs/joi) has many more useful validation methods.
270 | We will use a few of them later on when we build our example app.
271 |
272 | + Detailed example: https://github.com/hapijs/joi#example
273 | and https://vawks.com/blog/2014/03/22/the-joi-of-validation/
274 | + Want _friendly_ error messages in your web app?
275 | see: [https://github.com/dwyl/**hapi-error**](https://github.com/dwyl/hapi-error)
276 |
277 | ### Testing with Lab
278 |
279 | If you're _new_ to Test Driven Development (**TDD**) read
280 | our ***Beginners' TTD Tutorial***:
281 | [https://github.com/dwyl/**learn-tdd**](https://github.com/dwyl/learn-tdd) (_first_)
282 | and then come _back_ to this tutorial!
283 |
284 | If you've done functional or unit testing in previous
285 | programming projects you will be at home with Lab.
286 |
287 | Lab borrows *heavily* from [Mocha](https://github.com/mochajs/mocha),
288 | so if you followed our
289 | [learn-mocha](https://github.com/dwyl/learn-mocha) tutorial this should all be familiar.
290 |
291 | (Using the code we wrote above in the **Validation with Joi** section with a minor addition)
292 | An example of testing with Lab:
293 |
294 | ```js
295 | const Lab = require("lab"); // load Lab module
296 | const lab = exports.lab = Lab.script(); //export test script
297 | const Code = require("code"); //assertion library
298 | const server = require("../examples/hellovalidate.js");
299 |
300 | lab.experiment("Basic HTTP Tests", function() {
301 | // tests
302 | lab.test("GET /{yourname*} (endpoint test)", async function() {
303 | const options = {
304 | method: "GET",
305 | url: "/Timmy"
306 | };
307 | // server.inject lets you simulate an http request
308 | const response = await server.inject(options);
309 | Code.expect(response.statusCode).to.equal(200); // Expect http response status code to be 200 ("Ok")
310 | Code.expect(response.result).to.have.length(12); // Expect result to be "Hello Timmy!" (12 chars long)
311 | await server.stop();
312 | });
313 | });
314 | ```
315 | First we create a *test suite* for our test **Lab.experiment**
316 | (the _first argument_ is the name of the test suite "Basic HTTP Tests")
317 |
318 | Next is a basic test that calls the only route we have `/{yourname}`
319 | in this case **GET /Timmy**.
320 | We expect to receive a **200** http status code and the response body to be
321 | the text "Hello Timmy!".
322 |
323 | 1. Create a **new directory** in your project called **test**
324 | 2. Create a **new file** called **test.js** in the **./test** dir
325 | 3. Type out or copy-paste the above code into **test.js**
326 | 4. Open your package.json file
327 | 5. Add a **scripts** section to the package.json file with the following:
328 | ```
329 | "scripts": {
330 | "test": "lab -c"
331 | }
332 | ```
333 | 6. Save the package.json file
334 | 7. run the **npm test** script from your command line to execute the tests
335 |
336 | The result should look something like this:
337 |
338 |
339 |
340 | Note how the test script has a `-c` (_coverage_) flag above
341 | this give us the **code coverage**.
342 |
343 | We have **100% code coverage** so we can move on to our next test/feature!
344 |
345 | > How do you think we would write a test for an error?
346 | > (hint: have a look inside ./test/test.js and see the second test :)
347 |
348 | ### Note on Testing: Tape is _Simpler_ than Lab+Code
349 |
350 | > *While* ***Lab*** *is really* ***Good*** *and is the "official" testing
351 | framework used by Hapi*, *we* ***prefer***
352 | *the* ***simplicity***
353 | > *of* [***tape***](https://github.com/substack/tape);
354 | > we find our tests are simpler to write/read/understand. #YMMV
355 | > Also we *prefer* to use a *separate* & *specialised* module for tracking
356 | test coverage: [istanbul](https://github.com/dwyl/learn-istanbul)
357 | which we find does a [better job](https://github.com/hapijs/lab/issues/401) at tracking coverage...
358 |
359 | The preceding `Lab` test can be re-written (*simplified*) in `Tape` as:
360 |
361 | ```js
362 | const test = require('tape');
363 | const server = require("../index.js"); // our index.js from above
364 |
365 | test("Basic HTTP Tests - GET /{yourname*}", async function(t) { // t
366 | const options = {
367 | method: "GET",
368 | url: "/Timmy"
369 | };
370 | // server.inject lets you similate an http request
371 | const response = await server.inject(options);
372 | t.equal(response.statusCode, 200); // Expect http response status code to be 200 ("Ok")
373 | t.equal(response.result.length, 12); // Expect result to be "Hello Timmy!" (12 chars long)
374 | await server.stop();
375 | t.end(); // t.end() is required to end the test in tape
376 | });
377 | ```
378 | These tests are *functionally equivalent* in that they test *exactly* the
379 | same *outcome*. Decide for yourself which one you prefer for readability
380 | and maintainability in your projects.
381 | For our **Tape Tutorial** see: https://github.com/dwyl/learn-tape
382 |
383 |
384 | #### Related Links
385 |
386 | - Lab github module: https://github.com/hapijs/lab
387 | - Is TDD Dead? https://www.youtube.com/watch?v=z9quxZsLcfo (hint: no!)
388 | - Getting Started with HapiJS and Testing: https://blog.abcedmindedness.com/2014/10/getting-started-with-hapijs-and-testing.html (on hapi v8.0)
389 |
390 | ## Continuous Integration
391 |
392 | Making sure your code is working as you expect it to (over time).
393 |
394 | ### Integrating Hapi with Travis CI
395 |
396 | If you are new to Travis-CI or need a refresher see: https://github.com/dwyl/learn-travis
397 |
398 | We have Travis-CI enabled for all our hapi.js based projects:
399 | + https://github.com/dwyl/hapi-socketio-redis-chat-example
400 | + https://github.com/dwyl/hapi-auth-jwt2
401 | + https://github.com/dwyl/time
402 | + https://github.com/dwyl/api
403 |
404 | So if you need an example to follow, check out our repos!
405 | And as always, if you have _any questions, **ask**_!
406 |
407 | ### Error Handling with Boom
408 |
409 | [Boom](https://github.com/hapijs/boom) makes custom errors easier in Hapi.
410 | Imagine you have a page or item of content (photo, message, etc.) that
411 | you want to protect from public view (only show to someone who is logged in).
412 |
413 | First **install boom**:
414 |
415 | `npm install boom --save`
416 |
417 | Next write another test in ./test/**test.js**
418 | (If you aren't used to "Test First" - ***trust*** the process...)
419 |
420 | ```js
421 | lab.experiment("Authentication Required to View Photo", function() {
422 | // tests
423 | lab.test("Deny view of photo if unauthenticated /photo/{id*} ", async function() {
424 | const options = {
425 | method: "GET",
426 | url: "/photo/8795"
427 | };
428 | // server.inject lets you simulate an http request
429 | const response = await server.inject(options);
430 | Code.expect(response.statusCode).to.equal(401); // Expect http response status code to be 200 ("Ok")
431 | Code.expect(response.result.message).to.equal("Please log-in to see that"); // (Don't hard-code error messages)
432 | });
433 | });
434 | ```
435 |
436 | When you run `npm test` you will see a fail:
437 |
438 |
439 |
440 | Next we want to make this test pass and we'll use Boom to get our custom error message.
441 |
442 | The wrong way of doing this is to explicitly hard-code the response for this route.
443 | The right way is to create a generic route which responds to any request for a photo with any id.
444 | And since we don't currently have any authentication set up, we ***mock*** (fake) it.
445 | (Don't worry we will get to the authentication in the next step...)
446 |
447 | ```js
448 | const Boom = require('boom');
449 | server.route({
450 | method: 'GET',
451 | path: '/photo/{id*}',
452 | config: { // validate will ensure `id` is valid before replying to your request
453 | validate: { params: { id: Joi.string().max(40).min(2).alphanum() } },
454 | handler: function (req, h) {
455 | // until we implement authentication we are simply returning a 401:
456 | return Boom.unauthorized('Please log-in to see that');
457 | // the key here is our use of the Boom.unauthorised method
458 | }
459 | }
460 | });
461 | ```
462 |
463 | Our test passes but the point was to show that returning errors
464 | with specific messages is *easy* with **Boom**.
465 |
466 |
467 |
468 | Have a look at https://github.com/hapijs/boom for more error response options.
469 | We will be using these later as we build our app.
470 | Let's move on to authentication.
471 |
472 | > For a more _user-friendly_ approach to error-handling see: https://github.com/dwyl/hapi-error
473 |
474 |
475 | ### Logging with `good`
476 |
477 | Application logging can often be an _afterthought_ developers only implement
478 | _after_ they have a production bug which is crashing their API/App and
479 | they are scrambling to try and "debug" it.
480 |
481 | Thankfully, it Hapi has first-class support for logging with the `good` module.
482 |
483 | We have written a little example you can use to get started:
484 | [examples/hellogood.js](https://github.com/dwyl/learn-hapi/blob/master/examples/hellogood.js)
485 |
486 | Run it locally with `node examples/hellogood.js` then visit http://localhost:3000/hello/yourname in your browser.
487 |
488 | _Note: Good is not yet compatible with Hapi 17, so this code will only run if you are using v16. [See here for more details](#hapi-v16)_
489 |
490 | You should expect to see something like this:
491 | 
492 |
493 | There are good examples including logging use http (e.g. to a 3rd party logging tool)
494 | in the Good repo: https://github.com/hapijs/good/tree/master/examples
495 |
496 | Again, if you have _any_ questions, [_ask_](https://github.com/dwyl/learn-hapi/issues)
497 |
498 |
499 | ### Authentication
500 |
501 | Authentication is the process of determining whether someone
502 | or something is, in fact, who or what it is declared to be.
503 |
504 | Authentication (or "Auth") is something many *novice* (*naive*?)
505 | developers attempt to write themselves. (I was once that kid...
506 | trust me, we have *bigger fish to fry*, use a well-written/tested library!)
507 |
508 | We have 4 options:
509 |
510 | 1. Google - If you are building an "enterprise" or "education" app
511 | which you know will be used in Google-enabled companies/schools we
512 | wrote a Hapi plugin: https://github.com/dwyl/hapi-auth-google which
513 | lets you include Google Login in your app in a few clear steps. The plugin uses the [***Official Google Node.js API Client***](https://github.com/google/google-api-nodejs-client) and is
514 | written to be as readable as possible for complete beginners.
515 | 2. EveryAuth - Specific to Connect/Express apps: https://github.com/bnoguchi/everyauth
516 | 3. [Passport.js](https://github.com/jaredhanson/passport) - ***100% Code Coverage*** and *many* excellent integrations https://passportjs.org/guide/providers/
517 | 4. Bell - the 3rd party Login Hapi.js Plugin is *good* however we found it
518 | *lacking in documentation/usage examples*, which is why we wrote
519 | our own (*simpler*) Auth Plugin *specific* to our projects.
520 | see: [https://github.com/**dwyl**?query=**auth**](https://github.com/dwyl?utf8=%E2%9C%93&query=auth)
521 |
522 |
523 | #### Bell
524 |
525 | The go-to solution for 3rd party authentication in hapi is bell: https://github.com/hapijs/bell.
526 | There are a few good examples in the repo: https://github.com/hapijs/bell/tree/master/examples.
527 |
528 |
529 | ### Caching with Catbox
530 |
531 | Most apps don't _need_ caching from "Day 1"
532 | (_because you don't **know** upfront where your app's bottlenecks are going to be..._).
533 |
534 | But, once again, the team that brought you Hapi.js have _solved_ the problem of caching,
535 | see: https://github.com/hapijs/catbox/ and https://hapijs.com/tutorials/caching
536 | > We use Redis for blazing fast application and data caching.
537 | Hapi.js Catbox makes this very easy!
538 |
539 |
540 |
541 | ### Using Socket.io with Hapi for Real-Time Apps
542 |
543 | Using Socket.io with Hapi.js could _not_ be easier!
544 | To help you get started we've built a fully-functional chat application with tests (now [featured on the hapijs.com Resources page](https://hapijs.com/resources#Tutorials)),
545 | which demonstrates the power of Real-Time data-synching in your apps.
546 |
547 | > https://github.com/dwyl/hapi-socketio-redis-chat-example
548 |
549 | ## Hapi v16
550 | There were some major changes introduced to Hapi when version 17 was released. For a full list, see [the version 17 release notes](https://github.com/hapijs/hapi/issues/3658), but here are the major differences relevant to this guide:
551 | * Callbacks replaced with `async` functions. This means that instead of passing a callback to the function, and having that called when the function is finished, hapi functions return a promise that can either be resolved, or called synchronously with `await`. For more on `async` functions, see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) and [this guide](https://ponyfoo.com/articles/understanding-javascript-async-await)
552 | * `server.connection()` replaced with options passed directly into the server when it's created. A small change, but important to update. Before, we created our server with:
553 | ```js
554 | var server = new Hapi.server();
555 | ```
556 | and passed our options to:
557 | ```js
558 | server.connection({port: 8000});
559 | ```
560 | Now, we just pass our options straight away, and no longer need to call the connection method:
561 | ```js
562 | const server = new Hapi.server({port: 8000});
563 | ```
564 | * `reply()` interface replaced with a new lifecycle methods interface. You no longer have to call reply when sending a response from a handler. You can now just:
565 | ```js
566 | return "your reply";
567 | ```
568 | And the reply parameter to your handler has been replaced with a response toolkit (h) containing helpers from hapi core and your plugins.
569 |
570 | Not all of the hapi plugins have been updated to work with v17 yet (For example [Bell](https://github.com/hapijs/bell/issues/330), and [Good](https://github.com/hapijs/good/issues/568)), so be careful if you decide to upgrade an existing project.
571 |
572 | The previous version of this tutorial and code examples for Hapi 16 can be found here: https://github.com/dwyl/learn-hapi/tree/b58495ea002a9f3f8af8d183f6004d2b483f4591
573 |
574 | ## Please Suggest Improvements! [](https://github.com/dwyl/learn-hapi/issues)
575 |
576 | If you want to extend this tutorial or simply request additional sections,
577 | open an issue on GitHub: https://github.com/dwyl/learn-hapi/issues
578 |
579 |
580 | ## Background Reading / Watching
581 |
582 | - GitHub Repo: https://github.com/hapijs/hapi (has documentation)
583 | - An ecosystem of tools and best practices for the working hapijs developer (up-to-date with last version of hapi): https://hapipal.com/ https://hapipal.com/getting-started
584 |
585 | - Restify vs Express performance: https://stackoverflow.com/questions/17589178/why-should-i-use-restify
586 | - REST API in Express: https://pixelhandler.com/posts/develop-a-restful-api-using-nodejs-with-express-and-mongoose
587 | - Hapi API Reference: https://github.com/hapijs/hapi/blob/master/API.md
588 |
589 |
590 | ### Video Intro
591 |
592 | - Hapi.js and why it's awesome: https://www.youtube.com/watch?v=ZI9wXL-add0&t=2m5s
593 | - Hapi overview: https://www.youtube.com/watch?v=Recv7vR8ZlA (old version but concepts still relevant)
594 |
595 | ### Tutorials
596 |
597 | - Hapi Boilerplate app: https://github.com/poeticninja/hapi-ninja [updated for hapi 8.0]
598 | - Building APIs with Hapi and MongoDB: https://speakerdeck.com/donnfelker/building-web-apis-with-hapi-dot-js-and-mongodb-mongoose
599 | - Repo for the above speakerdeck: https://github.com/donnfelker/hapi-mongodb-example
600 | - Micro-tutorial: https://github.com/hapijs/makemehapi
601 | - https://stackoverflow.com/questions/21455076/hapi-and-node-js-to-create-a-rest-api-server
602 | - Hapi + Twilio (sms): https://code.tutsplus.com/tutorials/creating-a-node-web-app-with-hapi-and-twilio-integration--cms-20769
603 | - Authentication: https://github.com/hapijs/hapi-auth-cookie
604 | - A few examples: https://github.com/andyroyle/hapi-examples
605 | - More examples: https://github.com/wpreul/hapikc (*very old* version of Hapi!)
606 | - BDD with Hapi and Lab: https://gist.github.com/thebillkidy/10a11fed1bf61d04c3c5 (*old* version of Hapi!)
607 | + If you like React.js checkout Mullet.js (Hapi.js + React.js):
608 | https://mullet.io/ + https://github.com/lynnaloo/mullet
609 | + If you have an *existing* ***Express*** App and are thinking of
610 | migrating to Hapi, read: https://matt-harrison.com/moving-from-express-to-hapi-js/
611 |
612 | Selected StackOverflow Questions & Answers:
613 | - https://stackoverflow.com/questions/22934340/hapi-js-api-authentication
614 | see: https://stackoverflow.com/a/33877047/1148249 (*answer*)
615 | - https://stackoverflow.com/questions/22985392/how-do-you-make-a-hapi-js-plugin-module
616 | see: https://stackoverflow.com/a/25135343/1148249 (*answer*)
617 | - https://stackoverflow.com/questions/18343509/hapi-js-with-socket-io-where-is-socket-io-js
618 | see: https://stackoverflow.com/a/33876615/1148249 (*answer*
619 |
620 |
621 | [npm-image]: https://img.shields.io/npm/v/hapi.svg?style=flat
622 | [npm-url]: https://npmjs.org/package/learn-hapi
623 |
--------------------------------------------------------------------------------
/TLDR.md:
--------------------------------------------------------------------------------
1 | # Note: Hapi was built by Walmart
2 |
3 | 
4 |
5 | Hapi was (_originally_) built by (_the fantastic team of people assembled by [@hueniverse](https://github.com/hueniverse)_) [@WalmartLabs](https://en.wikipedia.org/wiki/@WalmartLabs)
6 | for Walmart.
7 |
8 | [Walmart](https://en.wikipedia.org/wiki/Walmart) is *by far* the most successful
9 | retailer in the world and they have achieved their success (*in part*) by
10 | investing heavily in their *technological competitive advantage*.
11 |
12 | If you are not keen on Walmart for any of
13 | [these](https://en.wikipedia.org/wiki/Criticism_of_Walmart) reasons,
14 | at least *consider* the fact that they have open-sourced their full
15 | node stack to allow others to benefit from their hard work.
16 |
17 | I took the time to read *extensively* about Walmart as part of my
18 | Retail course at University
19 | see: [History of Walmart](https://en.wikipedia.org/wiki/History_of_Walmart)
20 | and [In Sam We Trust](https://www.bizsum.com/summaries/sam-we-trust).
21 | The fact is that
22 | [Sam Walton](https://en.wikipedia.org/wiki/Sam_Walton)
23 | achieved *much* of his success through
24 | investing in *technology*
25 | (Barcodes, EPOS, Satellite Uplink for faster IT Systems and Logistics Tracking, etc)
26 | to drive cost savings and passed those savings on to the
27 | customers where other retailers got left behind with their paper-based
28 | "*it still works, why change?*" approach.
29 |
30 | Since Sam's passing the Walmart leadership has compromised its ethics
31 | in favor of maximising profits. This documented in:
32 | [The High Cost Of Low Price](https://www.youtube.com/results?search_query=Wal*Mart+-+The+High+Cost+Of+Low+Price)
33 | and [The Wal-Mart Effect](https://en.wikipedia.org/wiki/The_Wal-Mart_Effect)
34 |
35 | While I think we can/should continue send a
36 | [*clear message*](https://en.wikipedia.org/wiki/Dollar_voting)
37 | to [Bentonville](https://en.wikipedia.org/wiki/Walmart#Corporate_affairs)
38 | by preferring to spend our $¥£€ at Local & Fairtrade retailers where ever possible,
39 | we can still *use* the ***best-in-class*** code the *fantastic engineers*
40 | have built (to meet their *vast* supply-chain and e-commerce needs and
41 | open-sourced) to craft our own software products/projects.
42 |
43 | Using the transport analogy, I don't *like* using fossil fuels to get from A-to-B
44 | because of the CO2 emissions. But I'm *pragmatic* about how I travel
45 | the [thousand miles](https://www.wolframalpha.com/input/?i=distance+from+London+to+Lisbon)
46 | to visit my family twice a year. I could do two weeks by horse-and-cart,
47 | two days by train or two hours by plane each way. Which option do you take...?
48 | By chosing Hapi you are opting for the jet engine.
49 |
50 | Make up your own mind on whether you feel that using code written for Walmart
51 | goes against your ethics.
52 | If you find a *better* open source Node.js stack that fits your needs,
53 | *please* ***[tell us](https://twitter.com/nelsonic)*** about it!
54 |
--------------------------------------------------------------------------------
/examples/hapibell.js:
--------------------------------------------------------------------------------
1 | /**
2 | Bell is currently not compatible with Hapi v17,
3 | so this example is using Hapi v16
4 | **/
5 |
6 | var Hapi = require('hapi');
7 | var server = new Hapi.Server();
8 | server.connection({
9 | port: 3000
10 | });
11 |
12 | // Register bell with the server
13 | server.register(require('bell'), function (err) {
14 |
15 | // Declare an authentication strategy using the bell scheme
16 | // with the name of the provider, cookie encryption password,
17 | // and the OAuth client credentials.
18 | server.auth.strategy('twitter', 'bell', {
19 | provider: 'twitter',
20 | password: 'cookie_encryption_password',
21 | clientId: 'my_twitter_client_id',
22 | clientSecret: 'my_twitter_client_secret',
23 | isSecure: false // Terrible idea but required if not using HTTPS
24 | });
25 |
26 | // Use the 'twitter' authentication strategy to protect the
27 | // endpoint handling the incoming authentication credentials.
28 | // This endpoints usually looks up the third party account in
29 | // the database and sets some application state (cookie) with
30 | // the local application account information.
31 | server.route({
32 | method: ['GET', 'POST'], // Must handle both GET and POST
33 | path: '/login', // The callback endpoint registered with the provider
34 | config: {
35 | auth: 'twitter',
36 | handler: function (request, reply) {
37 |
38 | // Perform any account lookup or registration, setup local session,
39 | // and redirect to the application. The third-party credentials are
40 | // stored in request.auth.credentials. Any query parameters from
41 | // the initial request are passed back via request.auth.credentials.query.
42 | return reply.redirect('/home');
43 | }
44 | }
45 | });
46 |
47 | server.start();
48 | });
49 |
--------------------------------------------------------------------------------
/examples/hellogood.js:
--------------------------------------------------------------------------------
1 | /**
2 | Good is currently not compatible with Hapi v17,
3 | so this example is using Hapi v16
4 | **/
5 |
6 | /**
7 | Start this app from your command line with:
8 | node examples/hellogood.js
9 | then visit: http://localhost:3000/hello/YOURNAME
10 | */
11 |
12 | var Hapi = require('hapi');
13 |
14 | var server = new Hapi.Server();
15 |
16 | server.connection({
17 | host: '0.0.0.0',
18 | port: 3000
19 | });
20 |
21 | server.route({
22 | method: 'GET',
23 | path: '/hello/{name*}',
24 | handler: function(request, reply) {
25 | // log the request
26 | request.log(['name'], request.params.name + ' (request.log)');
27 | // use request.log instead of console.log:
28 | request.log('info', {my: 'object', has: 'many', props: { hello: request.params.name } });
29 | // server.log(['name'], request.params.name + ' requested / ' + '(server.log)');
30 | return reply('Hello ' + request.params.name);
31 |
32 | }
33 | })
34 |
35 | const options = {
36 | ops: {
37 | interval: 30000 // reporting interval (30 seconds)
38 | },
39 | reporters: {
40 | myConsoleReporter: [{
41 | // good-squeeze allows filtering events based on the `good` event options
42 | // @see https://github.com/hapijs/good/blob/master/API.md#event-types
43 | module: 'good-squeeze', // https://github.com/hapijs/good-squeeze
44 | name: 'Squeeze',
45 | // @example "Log everything"
46 | args: [{ log: '*', error: '*', response: '*', request: '*', ops: '*' }]
47 | // @example "Log only request logs with a certain tag":
48 | // args: [{ request: ['name'] }]
49 | }, {
50 | module: 'good-console'
51 | }, 'stdout']
52 | }
53 | };
54 |
55 | server.register({
56 | register: require('good'),
57 | options,
58 | }, function (err) {
59 |
60 | if (err) {
61 | return console.error(err);
62 | }
63 |
64 | server.start(function () {
65 | console.log('Now Visit: http://localhost:' + server.info.port + '/YOURNAME');
66 | console.dir(server.info);
67 | });
68 | });
69 |
--------------------------------------------------------------------------------
/examples/hellohapi.js:
--------------------------------------------------------------------------------
1 | // Start the app from your command line with: node examples/hellohapi.js
2 | // then visit: http://localhost:8000 in your browser
3 |
4 | const Hapi = require('hapi');
5 | const server = new Hapi.Server({
6 | host: '0.0.0.0',
7 | port: Number(process.argv[2] || 8000)
8 | });
9 |
10 | server.route({
11 | method: 'GET',
12 | path: '/',
13 | handler: function(req, h) {
14 | return 'Hello Hapi';
15 | }
16 | });
17 |
18 | async function startServer() {
19 | await server.start(); // boots your server
20 | console.log('Now Visit: http://localhost:'+server.info.port);
21 | }
22 |
--------------------------------------------------------------------------------
/examples/hellolog.js:
--------------------------------------------------------------------------------
1 | /**
2 | Start this app from your command line with:
3 | node examples/hellolog.js
4 | then visit: http://localhost:3000/YOURNAME
5 | */
6 | const Hapi = require('hapi');
7 |
8 | const server = new Hapi.Server({
9 | debug: {
10 | request: ["received"] // logs all requests to stdout
11 | },
12 | host: '0.0.0.0',
13 | port: 8000
14 | });
15 |
16 |
17 | server.route({
18 | method: 'GET',
19 | path: '/{name*}',
20 | handler: function(request, h){
21 | // log before seding a reply
22 | server.log(["test"], request.params.name + " requested the hello page!");
23 |
24 | return 'Hai ' + request.params.name;
25 | }
26 | })
27 |
28 | async function startServer() {
29 | await server.start();
30 | console.log('Now Visit: http://localhost:' + server.info.port + '/YOURNAME');
31 | console.dir(server.info);
32 | }
33 |
34 | startServer();
35 |
36 | // Listen for events of type 'log'
37 | server.events.on("log", function(event, tags) {
38 | const tagsJoined = Object.keys(tags).join();
39 | const msg = event.data;
40 | console.log(tagsJoined+" | "+msg);
41 | });
42 |
--------------------------------------------------------------------------------
/examples/helloparam.js:
--------------------------------------------------------------------------------
1 | /**
2 | Start this app from your command line with:
3 | node examples/helloparam.js
4 | then visit: http://localhost:3000/YOURNAME
5 | */
6 | const Hapi = require('hapi');
7 | const server = new Hapi.Server({
8 | host: '0.0.0.0',
9 | port: 3000
10 | });
11 |
12 | server.route({
13 | method: 'GET',
14 | path: '/{p*}',
15 | handler: function(req, h){
16 | return 'Hello ' + req.params.p;
17 | }
18 | })
19 |
20 | async function startServer() {
21 | await server.start();
22 | console.log('Now Visit: http://localhost:' + server.info.port + '/YOURNAME');
23 | }
24 |
--------------------------------------------------------------------------------
/examples/hellovalidate.js:
--------------------------------------------------------------------------------
1 | // Start this app from your command line with: node examples/hellovalidate.js
2 | // then visit: http://localhost:3000/YOURNAME
3 |
4 | const Hapi = require('hapi');
5 | const Joi = require('joi');
6 | const Boom = require('boom');
7 | const port = 3000; // process.env.PORT || 3000; // allow port to be set by environment
8 |
9 | const server = new Hapi.Server({ port: port });
10 |
11 | server.route({
12 | method: ['GET', 'POST'],
13 | path: '/{name*}',
14 | config: { // validate will ensure YOURNAME is valid before replying to your request
15 | validate: { params: Joi.object().keys({ name: Joi.string().max(40).min(2).alphanum() }) },
16 | handler: function (request, h) {
17 | return 'Hai '+ request.params.name + '!';
18 | }
19 | }
20 | });
21 |
22 | server.route({
23 | method: 'GET',
24 | path: '/photo/{id*}',
25 | config: { // validate will ensure YOURNAME is valid before replying to your request
26 | validate: { params: { id: Joi.string().max(40).min(2).alphanum() } },
27 | handler: function (request, h) {
28 | // until we implement authentication we are simply returning a 401:
29 | return Boom.unauthorized('Please log-in to see that');
30 | }
31 | }
32 | });
33 |
34 | module.exports = server;
35 |
--------------------------------------------------------------------------------
/examples/public/hello.js:
--------------------------------------------------------------------------------
1 | console.log('Hello World!');
2 |
--------------------------------------------------------------------------------
/examples/socketio.js:
--------------------------------------------------------------------------------
1 | // see: https://github.com/dwyl/hapi-socketio-redis-chat-examples
2 |
--------------------------------------------------------------------------------
/examples/staticfiles.js:
--------------------------------------------------------------------------------
1 | const Path = require('path');
2 | const Hapi = require('hapi');
3 | const port = process.env.PORT || 5000;
4 | const server = new Hapi.Server({ port: port });
5 |
6 | const plugins = [
7 | require('inert'), // serve static content
8 | require('vision') // views
9 | ]
10 |
11 | async function startServer() {
12 |
13 | await server.register(plugins);
14 |
15 | server.views({
16 | engines: {
17 | html: require('handlebars')
18 | },
19 | path: Path.join(__dirname, 'views')
20 | });
21 |
22 | server.route([
23 | {
24 | path: '/',
25 | method: 'GET',
26 | config: {
27 | auth: false,
28 | handler: function(request, h) {
29 | return h.view("index");
30 | }
31 | }
32 | },
33 | {
34 | method: 'GET',
35 | path: '/public/{param*}',
36 | handler: {
37 | directory: {
38 | path: Path.normalize(__dirname + '/public')
39 | }
40 | }
41 | }
42 | ]);
43 |
44 | await server.start();
45 | console.log('Static Server Listening on : http://127.0.0.1:' +port);
46 | }
47 |
48 | startServer();
49 |
50 | module.exports = server;
51 |
--------------------------------------------------------------------------------
/examples/views/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Try Serving Static Content in Hapi.js 9.0.1
5 |
6 |
7 |
8 |
9 |