├── .npmignore ├── Logo ├── HAPI.ai ├── HAPI-128.png └── HAPI-64.png ├── .gitignore ├── package.json ├── LICENSE ├── hapi.js └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | Logo -------------------------------------------------------------------------------- /Logo/HAPI.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jheising/HAPI/HEAD/Logo/HAPI.ai -------------------------------------------------------------------------------- /Logo/HAPI-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jheising/HAPI/HEAD/Logo/HAPI-128.png -------------------------------------------------------------------------------- /Logo/HAPI-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jheising/HAPI/HEAD/Logo/HAPI-64.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | lcov.info 3 | *.seed 4 | *.log 5 | *.csv 6 | *.dat 7 | *.out 8 | *.pid 9 | *.gz 10 | 11 | pids 12 | logs 13 | results 14 | build 15 | .grunt 16 | 17 | node_modules 18 | .idea -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "humanized-api", 3 | "version": "0.0.1", 4 | "description": "A spec and implementation for Humanized web APIs, aka HAPI. The goal of HAPI is to define a standard for creating Web-based APIs that are machine ready but human friendly.", 5 | "main": "hapi.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/jheising/HAPI.git" 12 | }, 13 | "keywords": [ 14 | "api", 15 | "humanized", 16 | "human", 17 | "readable" 18 | ], 19 | "author": "Jim Heising ", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/jheising/HAPI/issues" 23 | }, 24 | "homepage": "https://github.com/jheising/HAPI" 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Jim Heising 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /hapi.js: -------------------------------------------------------------------------------- 1 | var hapi = {}; 2 | 3 | hapi.HAPI_VERB = { 4 | get: "getting", 5 | create: "creating", 6 | update: "changing", 7 | delete: "deleting" 8 | }; 9 | 10 | hapi.makeHAPISuccess = function (HAPI_VERB, resourceType, data) { 11 | var hapiResponse = { 12 | "this": "succeeded", 13 | "by": HAPI_VERB, 14 | "the": resourceType, 15 | "with": data 16 | }; 17 | 18 | return hapiResponse; 19 | } 20 | 21 | hapi.makeHAPIFailure = function (why, apiErrorCode) { 22 | var hapiResponse = { 23 | "this": "failed", 24 | "with": apiErrorCode, 25 | "because": why 26 | }; 27 | 28 | return hapiResponse; 29 | } 30 | 31 | function middleware() 32 | { 33 | function hapiMiddleware(req, res, next) { 34 | res.sendHAPISuccess = function(HAPI_VERB, resourceType, data) 35 | { 36 | res.send(hapi.makeHAPISuccess(HAPI_VERB, resourceType, data)); 37 | } 38 | 39 | res.sendHAPIFailure = function(why, apiErrorCode) 40 | { 41 | res.send(hapi.makeHAPIFailure(why, apiErrorCode)); 42 | } 43 | 44 | res.sendHAPINotFoundFailure = function() 45 | { 46 | res.sendHAPIFailure("we couldn't find this", 404); 47 | } 48 | 49 | return (next()); 50 | } 51 | 52 | return (hapiMiddleware); 53 | } 54 | 55 | hapi.middleware = middleware; 56 | 57 | module.exports = hapi; 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Follow [@jimheising](https://twitter.com/jimheising) for updates and news related to HAPI 2 | 3 | # HAPI - The Humanized API 4 | 5 | ![logo](https://raw.github.com/jheising/HAPI/master/Logo/HAPI-128.png) 6 | 7 | The goal of HAPI is to define a standard for creating Web-based APIs that are machine ready but human/developer friendly— a self-documenting API. 8 | HAPI attempts to accomplish this by adhering to the following principles: 9 | 10 | 1. A HAPI should be accessible through a URL with nothing more than a standard web browser. 11 | 2. All inputs and outputs to a HAPI should generally be readable in a sentence form. 12 | 3. A HAPI should be self-documenting and generally understandable by non-technical people. 13 | 14 | Here's an example of HAPIness: 15 | 16 | **A HAPI request URL** 17 | ``` 18 | https://api.dohmain.com/create/donut/with/?filling=jelly 19 | ``` 20 | 21 | **A HAPI response** 22 | ```json 23 | { "this": "succeeded", "by": "creating", "the": "donut", "with": { 24 | "id": "mmmmm_donut_01", 25 | "filling": "jelly" 26 | } 27 | } 28 | ``` 29 | 30 | ### Why? 31 | 32 | Question: Why should an API be humanized? 33 |
34 | Answer: Machines consume APIs, but people still write the software that run on those machines. 35 | 36 | Developers should write easily testable and self/well-documented code— shouldn't an API aspire to the same ideals? 37 | 38 | HAPI addresses this problem by reducing all operations to simple requests that can be initiated and read by any standard web browser. API calls are reduced from a series of instructions into a single, self-documenting URL that can be clicked from an Email, chat, blog post (or anything else) with results that are simple enough to be read back over the phone in plain english. 39 | 40 | ### URLs 41 | 42 | URLs to access the operations on a HAPI are meant to generally follow the structure of an english sentence so as to be easily memorized and recalled without having to consult documentation. 43 | 44 | #### CRUD Operations 45 | 46 | URLs for CRUD (Create, Read, Update, Delete) operations might look like: 47 | 48 | (Note: spaces have been added for readability) 49 | 50 | Read (all): `https://api.` **[domain]** `/ get / all /` **[resource_type]** 51 | 52 | Read (query): `https://api.` **[domain]** `/ get / all /` **[resource_type]** `/ where / ?` **[parameters]** 53 | 54 | Read (specific): `https://api.` **[domain]** `/ get /` **[resource_type]** `/ called /` **[resource_id]** 55 | 56 | Create: `https://api.` **[domain]** `/ create /` **[resource_type]** `/ with / ?` **[parameters]** 57 | 58 | Update: `https://api.` **[domain]** `/ change /` **[resource_type]** `/ called /` **[resource_id]** `/ to / ?` **[parameters]** 59 | 60 | Delete: `https://api.` **[domain]** `/ delete /` **[resource_type]** `/ called /` **[resource_id]** 61 | 62 | Where: 63 | 64 | **[domain]**: The domain name of the HAPI. While HAPIs should generally not be segmented into separate sub-domains, it is acceptable to prefix the domain with a sub-domain for purposes of staging, and testing environments such as: *api.staging.mydomain.com*. 65 | 66 | **[resource_type]**: The name of a type of resource, like *employee* or *post*. A HAPI *should* support and treat the singular and plural form of the resource type as equal. For example, *employee* and *employees* will both be valid and considered equal. 67 | 68 | **[resource_id]**: The unique ID or name of a resource, like *2a89ef* or *fritz_734*. 69 | 70 | **[parameters]**: Any parameters required to complete the request in standard name/value pair format for HTTP query params. 71 | 72 | Examples: 73 | 74 | ``` 75 | https://api.dohmain.com/create/donut/with/?filling=jelly 76 | 77 | https://api.dohmain.com/get/all/donuts 78 | 79 | https://api.dohmain.com/get/all/donuts/with/?filling=jelly 80 | 81 | https://api.dohmain.com/get/donut/called/mmmmm_donut_01 82 | 83 | https://api.dohmain.com/change/donut/called/mmmmm_donut_01/to/?filling=custard 84 | 85 | https://api.dohmain.com/delete/donut/called/mmmmm_donut_01 86 | ``` 87 | 88 | ## Success Responses 89 | 90 | Responses to HAPI operations are meant to generally follow the structure of an english sentence so as to be easily understood by a layperson. 91 | 92 | The success response *should* adhere to the following form: 93 | 94 | ```json 95 | { "this": "succeeded", "by": "[verb]", "the": "[resource_type]", "with": [data] } 96 | ``` 97 | 98 | Where: 99 | 100 | **[verb]**: A "past-tense-like" form of the verb (technically a factitive gerund derived from the verb) describing the operation of the request. The value should be composed of the same root word as the one in the request URL. Examples: *getting*, *creating*, *deleting*, *changing*, *opening*, *logging in*, etc. 101 | 102 | **[resource_type]**: The name of a type of resource, like *employee* or *post*. The HAPI *should* return the same singular or plural form of the resource type passed in the request. 103 | 104 | **[data]**: The JSON representation of an object or array containing the important content being returned from the HAPI. In the case of operations that generally do not return data (like DELETE), this might contain the unique ID of the resource being deleted. This spec does not concern the actual conventions used to represent this data (other than it being JSON), but generally it should keep within the spirit of this spec by remaining clear and easily understood by the layperson. 105 | 106 | Examples: 107 | 108 | ``` 109 | https://api.dohmain.com/create/donut/with/?filling=jelly 110 | ``` 111 | ```json 112 | { "this": "succeeded", "by": "creating", "the": "donut", "with": { "id": "mmmmm_donut_01", "filling": "jelly" } } 113 | ``` 114 | 115 | ``` 116 | https://api.dohmain.com/get/all/donuts 117 | ``` 118 | ```json 119 | { "this": "succeeded", "by": "getting", "the": "donuts", "with": [ 120 | { "id": "mmmmm_donut_01", "filling": "jelly" }, 121 | { "id": "mmmmm_donut_02", "filling": "custard" } 122 | ] 123 | } 124 | ``` 125 | ``` 126 | https://api.dohmain.com/delete/donut/called/mmmmm_donut_01 127 | ``` 128 | ```json 129 | { "this": "succeeded", "by": "deleting", "the": "donut", "with": { "id": "mmmmm_donut_01" } } 130 | ``` 131 | 132 | ### Errors 133 | 134 | Errors follow a similar form. 135 | 136 | The error response *should* adhere to the following form: 137 | 138 | ```json 139 | { "this": "failed", "with": "[error_code]", "because": "[error_message]" } 140 | ``` 141 | 142 | Where: 143 | 144 | **[error_code]**: A machine readable error code (number or string) that might be used as an index for localized error messages. 145 | 146 | **[error_message]**: A human readable error message in case the application does not provide a lookup mechanism for the `error_code`. 147 | 148 | Example: 149 | 150 | ```json 151 | { "this": "failed", "with": "InvalidParameter", "because": "donuts with holes can't contain jelly" } 152 | ``` 153 | --------------------------------------------------------------------------------