├── .gitignore ├── README.md ├── config.json ├── main.js ├── package.json └── routes.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | npm-debug.log 3 | node_modules 4 | .DS_Store 5 | */.DS_Store 6 | */*/.DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Hapi Plugin Example 2 | =================== 3 | 4 | [Hapi](http://hapijs.com/) is a framework for rapidly building RESTful web services. Whether you 5 | are building a very simple set of RESTful services or a large scale, cache 6 | heavy, and secure set of services, `hapi` has you covered. [Hapi](http://hapijs.com/) will 7 | help get your server developed quickly with its wide range of configurable options. 8 | 9 | ## Building a Products API 10 | 11 | The following example will walk you through using hapi to build a RESTful set 12 | of services for creating and listing out products. To get started create a 13 | directory named _products_ and add a `package.json` file to the directory 14 | that looks like the following. 15 | 16 | ```json 17 | { 18 | "name": "products", 19 | "version": "0.0.1", 20 | "engines": { 21 | "node": ">=4.0.0" 22 | }, 23 | "peerDependencies": { 24 | "hapi": "11.x.x" 25 | } 26 | } 27 | ``` 28 | 29 | Create a `main.js` file that will serve as the entry point for the plugin. Add the following to the file: 30 | 31 | ```js 32 | var routes = require('./routes'); 33 | 34 | exports.register = function (server, options, next) { 35 | server.route(routes(options)); 36 | next(); 37 | }; 38 | 39 | exports.register.attributes = { 40 | pkg: require('./package.json') 41 | }; 42 | 43 | ``` 44 | 45 | [Hapi](http://hapijs.com/) provides a function for adding a single route or an 46 | array of routes. In this example we are adding an array of routes from a routes module. 47 | 48 | Go ahead and create a `routes.js` file, which will contain the route 49 | information and handlers. When defining the routes we will also be specifying 50 | *validation requirements*. 51 | 52 | For this example three routes will be created. Below is the code you should 53 | use to add the routes. Add the following code to your `routes.js` file. 54 | 55 | ```js 56 | module.exports = function routes (options) { 57 | var Joi = require('joi'); 58 | return [ 59 | { method: 'GET', path: '/products', config: { handler: getProducts, query: { name: Joi.string() } } }, 60 | { method: 'GET', path: '/products/{id}', config: { handler: getProduct } }, 61 | { method: 'POST', path: '/products', config: { 62 | handler: addProduct, 63 | payload: 'parse', 64 | schema: Joi.string().required().min(3) , 65 | response: { id: Joi.number().required() } 66 | } } 67 | ]; 68 | }; 69 | ``` 70 | 71 | The routes are exported as an array so that they can easily be included by the 72 | plugin register function. For the products listing endpoint we are 73 | allowing a querystring parameter for name. When this querystring parameter 74 | exists then we will filter the products for those that have a matching name. 75 | 76 | The second route is a very simple route that demonstrates how a parameter can 77 | become part of the path definition. This route will return a product matching 78 | the ID that’s requested. 79 | 80 | In the last route, the one used for creating a product, you will notice that 81 | extra validation requirements are added, even those on the response body. The 82 | request body must contain a parameter for name that has a minimum of 3 83 | characters and the response body must contain an ID to be validated. 84 | 85 | Next add the handlers to the `routes.js` file. 86 | 87 | ```js 88 | function getProducts(request, reply) { 89 | if (request.query.name) { 90 | reply(findProducts(request.query.name)); 91 | } else { 92 | reply(products); 93 | } 94 | } 95 | 96 | function findProducts(name) { 97 | return products.filter(function(product) { 98 | return product.name.toLowerCase() === name.toLowerCase(); 99 | }); 100 | } 101 | 102 | function getProduct(request, reply) { 103 | var product = products.filter(function(p) { 104 | return p.id == request.params.id; 105 | }).pop(); 106 | 107 | reply(product); 108 | } 109 | 110 | function addProduct(request, reply) { 111 | var product = { 112 | id: products[products.length - 1].id + 1, 113 | name: request.payload.name 114 | }; 115 | 116 | products.push(product); 117 | 118 | reply.created('/products/' + product.id)({ 119 | id: product.id 120 | }); 121 | } 122 | ``` 123 | 124 | As you can see in the handlers, *hapi* provides a simple way to add a 125 | response body by using the `reply function`. Also, in the instance 126 | when you have created an item you can use the `reply.created` function 127 | to send a 201 response. 128 | 129 | Lastly, add a simple array to contain the products that the service will serve. 130 | 131 | ```js 132 | var products = [{ 133 | id: 1, 134 | name: 'Guitar' 135 | }, 136 | { 137 | id: 2, 138 | name: 'Banjo' 139 | } 140 | ]; 141 | ``` 142 | 143 | 144 | ## Composing the server 145 | 146 | The plugin can now be added to a server using a `config.json` file. Create a `config.json` 147 | file outside of the plugin directory in a new directory you plan to run the server. Add 148 | the following contents to `config.json` 149 | 150 | ```json 151 | { "connections": [ 152 | { 153 | "host": "0.0.0.0", 154 | "port": 8080, 155 | "labels": ["http", "api"] 156 | } 157 | ], 158 | "plugins": { 159 | "products": {} 160 | } 161 | } 162 | ``` 163 | 164 | Next run `npm link` within the products folder and then run `npm link products` inside the folder where 165 | the `config.json` exists. After this you will want to also run `npm install -g hapi` to install hapi. 166 | 167 | ## Running the server 168 | 169 | Start the hapi server using the following command [rejoice](https://github.com/hapijs/rejoice), the hapi-cli: 170 | 171 | rejoice -c config.json 172 | 173 | To see a list of the products navigate to 174 | . Below is a screenshot of what the response 175 | looks like. 176 | 177 | 178 | 179 | Go ahead and append `?name=banjo` to the URL to try searching for a product by 180 | name. 181 | 182 | 183 | 184 | Use curl or a REST console to create a product. Make a POST request to the 185 | products endpoint with a name in the body. Using curl the command looks like: 186 | `curl http://localhost:8080/products -d "name=test"`. Below is an example of 187 | the response headers from making a request to create a product. 188 | 189 | 190 | 191 | Now if you navigate to the _Location_ specified in the response headers you 192 | should see the product that you created. 193 | 194 | ## Other features 195 | 196 | There are a lot of different configuration features that you can add to the 197 | server. The extensive list can be found in the readme at 198 | . 199 | 200 | The built-in cache support has providers for mongo and redis. Setting up cache 201 | is as simple as passing cache: true as part of the server configuration. 202 | 203 | Additionally, there are several configuration options available on a per route 204 | basis. For example, caching expiration times can also be configured on a per route basis. Also, 205 | you can have per-route authentication settings. 206 | 207 | ## Conclusion 208 | 209 | By now you should have a decent understanding of what *hapi* has to offer. 210 | 211 | There are still many other features and options available to you when using 212 | hapi that is covered in the [documentation](http://hapijs.com/api). 213 | A [tutorial is also available](hapijs.com/tutorials/plugins) 214 | 215 | 216 | Please take a look at the [github repository][] and feel free to provide any feedback you may have. 217 | 218 | [github repository]: https://github.com/wpreul/hapi-plugin-example 219 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "connections": [ 3 | { 4 | "host": "0.0.0.0", 5 | "port": 8080, 6 | "labels": ["http", "api"] 7 | } 8 | ], 9 | "plugins": { 10 | "products": {} 11 | } 12 | } -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const routes = require('./routes'); 3 | 4 | exports.register = function (server, options, next) { 5 | server.route(routes(options)); 6 | next(); 7 | }; 8 | 9 | exports.register.attributes = { 10 | pkg: require('./package.json') 11 | }; 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "products", 3 | "version": "0.0.2", 4 | "engines": { 5 | "node": ">=4.0.0" 6 | }, 7 | "main": "main.js", 8 | "peerDependencies": { 9 | "hapi": "11.x.x" 10 | }, 11 | "dependencies": { 12 | "joi": "^7.0.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function routes (options) { 4 | const Joi = require('joi'); 5 | return [ 6 | { method: 'GET', path: '/products', 7 | config: { 8 | handler: getProducts, 9 | validate: { query: { name: Joi.string() } } } }, 10 | { method: 'GET', path: '/products/{id}', config: { handler: getProduct } }, 11 | { method: 'POST', path: '/products', config: { 12 | handler: addProduct, 13 | validate: {payload: Joi.string().required().min(3)} 14 | } } 15 | ]; 16 | }; 17 | 18 | 19 | function getProducts(request, reply) { 20 | if (request.query.name) { 21 | reply(findProducts(request.query.name)); 22 | } else { 23 | reply(products); 24 | } 25 | } 26 | 27 | function findProducts(name) { 28 | return products.filter(function(product) { 29 | return product.name.toLowerCase() === name.toLowerCase(); 30 | }); 31 | } 32 | 33 | function getProduct(request, reply) { 34 | const product = products.filter(function(p) { 35 | return p.id == request.params.id; 36 | }).pop(); 37 | 38 | reply(product); 39 | } 40 | 41 | function addProduct(request, reply) { 42 | const product = { 43 | id: products[products.length - 1].id + 1, 44 | name: request.payload.name 45 | }; 46 | 47 | products.push(product); 48 | 49 | reply.created('/products/' + product.id)({ 50 | id: product.id 51 | }); 52 | } 53 | 54 | const products = [{ 55 | id: 1, 56 | name: 'Guitar' 57 | }, 58 | { 59 | id: 2, 60 | name: 'Banjo' 61 | } 62 | ]; 63 | --------------------------------------------------------------------------------