├── .gitignore ├── README.md ├── dredd.yml ├── index.js ├── package.json └── test ├── manifest.json └── test.md /.gitignore: -------------------------------------------------------------------------------- 1 | .tmp/ 2 | node_modules/ 3 | *.log 4 | coverage/ 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![crudy](https://cloud.githubusercontent.com/assets/2857535/9075332/fa99d7a4-3b13-11e5-99cb-189b1c67f08c.png) 2 | 3 | A [hapi](http://hapijs.com/) plugin that will expose a RESTful **CRUD** interface using your [Dogwater](https://github.com/devinivy/dogwater) *models* and [Bedwetter](https://github.com/devinivy/bedwetter)'s *route-handler*. 4 | 5 | E.g.: 6 | If you've defined a *Person* model through *Dogwater*, using this plugin will **automatically** expose the following routes: 7 | 8 | GET /person List all Persons 9 | GET /person/{id} Get one Person by id 10 | POST /person Create a new Person 11 | POST /person/{id} Update a Person by id 12 | PATCH /person/{id} Update a Person by id 13 | DELETE /person/{id} Delete a Person by id 14 | 15 | ## Installation 16 | This plugin uses **ES6 features** and it works like charm with **iojs > 2.0**. If you are planing to use it with non ES6-enabled engines, consider using [Babel.js](http://babeljs.io). 17 | 18 | npm install --save crudy 19 | 20 | Don't forget that *hapi*, *Dogwater* and *Bedwetter* should be already dependencies of your project, defined in the ```package.json``` file. These are in facts **peer-dependencies**. 21 | 22 | ## Usage 23 | This module can be used as common [Hapi.js](http://hapijs.com/) plug-in, configuring it in the ```plugin``` section of the ```manifest.json``` of your application: 24 | 25 | "crudy": { 26 | "person": { 27 | "config": { 28 | "tags": ["api"], 29 | "notes": "Notes...", 30 | "handler": { 31 | "bedwetter": { 32 | "prefix": "/api/v1" 33 | } 34 | } 35 | }, 36 | "actions": { 37 | "find": true, 38 | "findOne": true 39 | } 40 | } 41 | } 42 | 43 | 44 | or using the ```server.register()``` [method](http://hapijs.com/tutorials/plugins#loading-a-plugin) and passing this object as *options*. 45 | 46 | Note that you can pass **routes-specific options** to the plug-in: *prefixes*, *notes*, *tags*... 47 | 48 | Using the ```actions``` section you can define which actions should be provided by *crudy*. 49 | 50 | **Possible actions are:** 51 | 52 | * find 53 | * findOne 54 | * create 55 | * update 56 | * destroy 57 | 58 | If no configuration is passed to the *plugin*, it will expose the complete **CRUD interface**, as in the example above. 59 | 60 | ## Tests 61 | npm test 62 | 63 | ## Contribution 64 | Contributions are welcome. Send me your pull-request or open an [issue](https://github.com/g-div/crudy/issues). 65 | 66 | ### TODOs: 67 | - [ ] Per-action options (Auth, ACL, Prefix, ...) 68 | - [ ] Use Joi for validation in order to unlock Hapi-compliant documentation (lout/swagger) 69 | - [ ] Better Unit testing 70 | - [ ] Test coverage 71 | -------------------------------------------------------------------------------- /dredd.yml: -------------------------------------------------------------------------------- 1 | dry-run: null 2 | hookfiles: null 3 | language: nodejs 4 | sandbox: false 5 | server: rejoice -p ./ -c test/manifest.json 6 | server-wait: 3 7 | init: false 8 | custom: {} 9 | names: false 10 | only: [] 11 | reporter: [] 12 | output: [] 13 | header: [] 14 | sorted: false 15 | user: null 16 | inline-errors: false 17 | details: false 18 | method: [] 19 | color: true 20 | level: info 21 | timestamp: false 22 | silent: false 23 | path: [] 24 | blueprint: test/test.md 25 | endpoint: 'http://localhost:9000' 26 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const Hoek = require('hoek'); 3 | 4 | let internals = { 5 | defaults: { 6 | config: { 7 | handler: { 8 | bedwetter: {} 9 | } 10 | }, 11 | actions: { 12 | find: true, 13 | findOne: true, 14 | create: true, 15 | update: true, 16 | destroy: true 17 | } 18 | } 19 | }; 20 | 21 | exports.register = function (server, options, next) { 22 | internals = Hoek.applyToDefaults(internals, options); 23 | server.dependency(['bedwetter', 'dogwater'], internals.after); 24 | 25 | next(); 26 | }; 27 | 28 | internals.after = function (server, next) { 29 | for (const entity in server.plugins.dogwater) { 30 | if (server.plugins.dogwater.hasOwnProperty(entity)) { 31 | let current; 32 | if (internals[entity]) { 33 | current = Hoek.applyToDefaultsWithShallow(internals.defaults, internals[entity], ['actions']); 34 | } else { 35 | current = internals.defaults; 36 | } 37 | const path = `${current.config.handler.bedwetter.prefix || ''}/${entity}`; 38 | 39 | if (current.actions.find) { 40 | current.config.description = `Find all ${entity} entries`; 41 | server.route({ 42 | method: 'GET', 43 | path, 44 | config: current.config 45 | }); 46 | } 47 | if (current.actions.findOne) { 48 | current.config.description = `Find one ${entity} by id`; 49 | server.route({ 50 | method: 'GET', 51 | path: `${path}/{id}`, 52 | config: current.config 53 | }); 54 | } 55 | if (current.actions.create) { 56 | current.config.description = `Create a new ${entity}`; 57 | server.route({ 58 | method: 'POST', 59 | path, 60 | config: current.config 61 | }); 62 | } 63 | if (current.actions.update) { 64 | current.config.description = `Update one ${entity} by id`; 65 | server.route({ 66 | method: ['POST', 'PATCH'], 67 | path: `${path}/{id}`, 68 | config: current.config 69 | }); 70 | } 71 | if (current.actions.destroy) { 72 | current.config.description = `Delete one ${entity} by id`; 73 | server.route({ 74 | method: 'DELETE', 75 | path: `${path}/{id}`, 76 | config: current.config 77 | }); 78 | } 79 | } 80 | } 81 | next(); 82 | }; 83 | 84 | exports.register.attributes = { 85 | pkg: require('./package.json') 86 | }; 87 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "crudy", 3 | "version": "1.0.1", 4 | "description": "A thin layer acting as glue for Dogwater and Bedwetter. It will expose a RESTful CRUD interface based on your models' definitions.", 5 | "main": "index.js", 6 | "scripts": { 7 | "pretest": "npm run lint", 8 | "lint": "xo", 9 | "test": "dredd" 10 | }, 11 | "author": "g-div", 12 | "url": "https://github.com/g-div/crudy/issues", 13 | "repository": "g-div/crudy", 14 | "license": "ISC", 15 | "peerDependencies": { 16 | "hapi": "^8.4.0", 17 | "dogwater": "^0.4.6", 18 | "bedwetter": "^1.8.1" 19 | }, 20 | "devDependencies": { 21 | "dredd": "^1.0.0", 22 | "rejoice": "^2.0.0", 23 | "sails-memory": "^0.10.5", 24 | "xo": "^0.5.1" 25 | }, 26 | "xo": { 27 | "esnext": true, 28 | "rules": { 29 | "strict": [ 30 | 1, 31 | "global" 32 | ] 33 | } 34 | }, 35 | "dependencies": { 36 | "hoek": "^2.14.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": { 3 | "app": { 4 | "slogan": "Test manifest" 5 | } 6 | }, 7 | "connections": [{ 8 | "port": 9000, 9 | "labels": ["api"] 10 | }], 11 | "plugins": { 12 | "dogwater": { 13 | "connections": { 14 | "memoryDB": { 15 | "adapter": "memory" 16 | } 17 | }, 18 | "adapters": { 19 | "memory": "sails-memory" 20 | }, 21 | "models": [{ 22 | "schema": true, 23 | "identity": "user", 24 | "connection": "memoryDB", 25 | "attributes": { 26 | "name": { 27 | "type": "string", 28 | "unique": true 29 | } 30 | } 31 | }] 32 | }, 33 | "bedwetter": {}, 34 | "../crudy": {} 35 | } 36 | } -------------------------------------------------------------------------------- /test/test.md: -------------------------------------------------------------------------------- 1 | # User API 2 | 3 | User management API 4 | 5 | ## Users [/user] 6 | 7 | ### Create an User [POST] 8 | + Request (application/json; charset=utf-8) 9 | 10 | {"name": "test"} 11 | 12 | + Response 201 (application/json; charset=utf-8) 13 | 14 | {"name": "test", "createdAt": "2015-08-03T18:27:44.603Z", "updatedAt": "2015-08-03T18:27:44.603Z", "id": 1} 15 | 16 | ### Get all Users [GET] 17 | + Response 200 (application/json; charset=utf-8) 18 | 19 | [{"name": "test"}] 20 | 21 | ### Get an User [/user/{id}] 22 | + Parameters 23 | + id: 1 (required, number) - The UserID 24 | 25 | ### Update an User [PATCH] 26 | + Parameters 27 | + id: 1 (required, number) - The UserID 28 | + Request (application/json; charset=utf-8) 29 | 30 | {"name": "tested"} 31 | 32 | + Response 200 (application/json; charset=utf-8) 33 | 34 | {"name": "tested", "createdAt": "2015-08-03T18:27:44.603Z", "updatedAt": "2015-08-03T18:27:44.603Z", "id": 1} 35 | 36 | 37 | ### Delete an User [DELETE] 38 | + Parameters 39 | + id: 1 (required, number) - The UserID 40 | 41 | + Response 204 42 | --------------------------------------------------------------------------------