Use JavaScript and hapijs to build web applications. See the essentials of how to build hapi applications: authentication, validation, application architecture, and testing. Plus, delve into deeper topics like: bearer-tokens, caching, the request lifecycle.
First, we need some basic structure: a lib folder, a package.json file, and an initial ./lib/index.js.
Since we will be using hapi, we need to include that in the project dependencies.
Second, create a basic hapi server on port 8000 which responds to /version requests and replies with a simple { "version": "0.1.1" } JSON payload. The version data in the response should come from the package.json file.
npm init
npm install --save hapi
compare assignment1 solution to start point
view assignment1 solution
Utilizes Github's compare tags feature to view the solution.
Above assignment based on original assignment1 by @hueniverse and work by @AdriVanHoudt. See this PR: #7.
- style guide note how hapi uses Module globals and internals.
- Discussion on assigning variables, modules, singletons, and callbacks
- On Github see comparing on the commits when across time compare be. That feature comparing on the commits is used alot to compare and view the said solution again with the set beginning. // That feature comparing on the commits is used on the Lesson one solution. It compares the starting point with the solution of lesson one.
- hapi documentation on server options
The right way to work with hapi is to build your application using plugins. Plugins provide a way to organize your code into logical components and then put them together in different combinations or deploy them in different configurations. While some plugins are published as general purpose utilities (e.g. adding authentication), you should think of plugins as a way to break your code into pieces.
Now that we have a basic server with one endpoint, we want to move the creation of the endpoint to a plugin,
and then register that plugin with the server. Create a new file lib/version.js and move the /version endpoint there.
Then, change our current lib/index.js to require the new plugin file and register the version plugin with our server.
The server.register() function is used to register the plugin.
Remember to follow the style guide, and ask any questions in the comments of the issue created for the assignment. If you are not sure how to get your fork back in sync with the current updated code, use the git guide.
/version end point should reply with a simple JSON payload:
{
version: Package.version,
message: options.message
}
- Notice that
const plugins = []is an array. An array is used to register the Version plugin because more plugins are to be registered in later assignments. - Notice how options are passed to the plugin:
{ message: 'assignment2' }. The/versionpoint returns the message value. - In contrast to plugin options, notice how
server.registeroptions are passed:{ once: true }. Often there is confusion between hapi'sserver.registeroptions and options passed to a plugin being registered. lesson2 clearly shows the difference.{ message: 'assingment2' }illustrates options passed to a plugin. These are options used inside the Version plugin. And, the{ once: true }option is passed to the server when registering plugins:server.register(). See/lib/index.jsfor details.
require('./version') should be declared at the top and assigned toVersion`.- no need for specifying the plugin version in the attributes. This is an internal plugin, not a publicly published one.
compare assignment2 solution to assignment1
view assignment2 solution source
This assignment based on the original assignment2 convert to plugin and work done by MylesBorins for that assignment.
Things are getting a bit more interesting...
It's time to add tests, verify coverage, confirm style, and automate all of this with CI (CI means: Continuous Integration). We will be using the lab module to perform these tasks and automate them with travis. Code will be our test's assertian library.
- Export
init()and move the server invocation to a newlib/start.jsfile. Thelib/start.jsfile calls the exportedinit()function and passes configurations options to it. The resolved promise function in start.js outputs the server config details to the console. Changepackage.jsonfile to usestart.jsas the start up script.start.jsfile is not covered by tests. Designing the server to start with an exportedinitfunction allows other scripts and applications to start and stop the server. This is important for several reasons:- It allows testing scripts to start and stop the server to execute tests.
- It allows another program to start and stop the application. This is useful when using hapi to build services - soa (service oriented architecture). Sometimes you make several hapi services to run on one computer and collaborate with each other.
- It allows for the
start.jsscript to start and stop the application.
- Add a
.travis.ymlfile. When a .travis.yml file exists in a GitHub repository the project is built and all tests are executed..travisreports if all tests successfully pass or not. Note, you must configure github to excute travis CI upon events (push or PR) to the repository. This is found under: Settings -> Integration & Services. - Add a test folder with two files,
version.jsandindex.js, each testing the corresponding file under/lib. - Modify the
package.jsonfile to include tests. Install the dev dependencieslabandcode. - When using lab, enable coverage, require 100% coverage, enable linting with default rules, and install and use code assertion library.
See
package.jsonfile to view the test command or see a test command write up here. - Write a basic test to verify our version endpoint in
version.js. - Write tests to get 100% coverage.
To get 100% test coverage you also need to confirm style.
labconfirms if the project's code abides by the hapijs style guide. This is called 'linting'.
Everything should be pretty straight forward. If you are not sure on how to use lab and code, look at other hapi.js modules and copy their test scripts and setup.
Getting 100% coverage can be tricky sometimes so if you are not sure, get as much coverage as you can, and comment on the lines in your pull request where you are having a hard time reaching and someone will give you a clue.
Remember to properly stop() your servers when calling the init() method in each test.
For now, avoid using any of the before() and after() lab features.
As always, ask for help and help others!
- When stubbing / patching code in a test, mark that test with the
{ parallel: false }lab option to make it both safe for future parallel testing as well as visual cue. - Never return anything other than an actual
Erroras an error (e.g. no strings, plain objects, etc.). - Never use fixed port numbers in tests, only
0or the default. - Always
returnbeforenext()unless there is a reason to continue. - Make arguments / options in
init()required. - When calling
server.inject()with a GET request, just pass the path string as the first argument instead of an options object. Makes the code much more readable. - Use the testing shortcuts boilerplate used in hapi. Makes reading tests easier.
- elegant lab and code
good lesson three game plan
go test for dirty bugs- clean up guy on github see travis agree
- talk style, value guidance, hapi emotion,
lab enforces all.
Seek linting, Geek leadership no excuses find lab have fun.
Compare Assignment3 Solution to Assignment2
view assignment3 solution source
Assignment is based on original assignment3 and the discussion related to it: 100% coverage and work by idanwe.
- Use the
server.appproperty to store the application'sresponse.versionandoptions.message. See documentation for more about server.app - Access the
request.serverproperty in the./lib/version.jshandler to return theserver.app.versionand server.app.message values. - Note: The
server.appproperty is useful to set a DB connection in.server.appproperties are available wherever therequest.serveris exposed.
Compare Assignment4 Solution to Assignment3
- In this lesson we change the
/versionroute handler function location. The goal is to remove route functions from the plugin which registers the route. This creates readable route registration plugins.- There is a limitation to removing route functions from the plugin registering the route. The limitation is options passed to the plugin at registration time are not available to functions built in files seperate from the plugin.
- When a plugin registers multiple routes or has routes with the request-lifecycle extended, plugin code can get cluttered. Moving method / function logic out of the plugin keeps route logic readable.
- Start to familiarize yourself with hapi's request-lifecycle extensions. Adding extensions allows for logic to be split up into multiple functions and be executed at specific times when a request is made to a route. See docs: route.opt.ext, request-lifecycle, route.options.pre
- Create a route methods directory
./lib/route-methods. Resources used in routes will stored in this directory. - Create a
version.jsfile in the route-methods directory./lib/route-methods/version.jsMethods used in the/versionroute will be stored here. Move the/versionhandler function to this file. - Note: this is a personal style preferrence.
Preferrence is to make a split screen view with:
- the screen on the left
displays the routes./lib/version.js - the screen on the right
displays the methods executed in the routes./lib/route-methods/version.js
- the screen on the left
- Run tests. No new tests need to be built. But, executing passing tests proves changes did not break the application.
Compare Assignment5 Solution to Assignment4
- add hapi-auth-bearer-token
npm install --save hapi-auth-bearer-token - Register the authentication strategy in it's own plugin
./lib/authtoken.js. - all routes must have valid token to be accessed
- currently only one route exists:
/version. - valid token equals
1234574
- currently only one route exists:
- 100% tests coverage.
Adjust/versiontests for token authentication. Test for passing and failing tokens.
Notice we have not created user authentication yet -- users have no way to log in.
Tests for the assignment assume a valid auth bearer token for the user already exists.
The focus is on getting hapi auth bearer token plugin installed and configured.
This lesson does not build a complete authentication system.
Here are resources related to auth bearer tokens. Please share if you know of other links and resources related to the subject.
Compare Assignment6 Solution to Assignment5
This assignment originally was Assignment4
- tls the project.
- TLS - Transport Layer Security
- nodejs tls docs
Compare Assignment7 Solution to Assignment6
- build
./authenticateroute. - use prerequisite extensions to execute authentication logic.
- Make a simple data store
database.jsto authenticate user records with. - No authStrategy applied to
/authenticatepoint. - generate bearer-token upon successful authentication (cryptiles).
- Use Boom to return errors.
Compare Assignment8 Solution to Assignment7
In this lesson we will complete our authentication system. First, the bearer token
cache is configured. When a user successfully authenticates, the auth-bearer-token
generated for the session is stored in the cache catabox-redis.
Plus, user account data associated with the session is stored in the cache with the token.
Then, the validateFunction for the auth-bearer-token strategy is modified to use the bearer token cache
to validate if the received token is valid or not. After the /authentication route is built, the token cache
plugin is written, and the auth strategy is changed, we create the /private route which requires a token for an
administrative user to access route data.
- catbox-redis ./lib/tokencache.js
- install redisdb
- configure server to use catbox-redis.
See hapi caching service and catbox-redis documentation. - Set bearer-token in catbox-cache along with user record.
- Expire the token after xxxxx time. Set expiresIn: value with server.options.
- configure scopes ['admin', 'member'] for role based access.
- configure
.travis.ymlto use the redis server
- authentication
Refactor authentication logic to:- pre-empt one user from generating multiple tokens.
- upon successful authentication set token and user records in the cache.
- Relevate file:
lib/route-methods/authenticate.js
lib/authtoken.js- re-write the
defaultValidateFuncto uses the catbox-redis cache to validate tokens.
- re-write the
- server configuration options
Configure the application options forpluginoptions to be set along with server options. This allows for test configurations to set shorter token expiration times so tokens made in previous test do not collide with the current test. - create ./private point which requires admin scope for access
- Apply default authStrategy to ./private point.
- tests
- write 100% test coverage
- Add
{ debug: false }config for tests. Otherwise, the tests print out hapi-auth-bearer-token error reports. Originally, added in assignment9 but can go here.