├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitattributes ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── bin └── apiize.js ├── dist ├── apiize │ ├── index.js │ ├── server.js │ └── utils.js ├── cli.js └── index.js ├── gulpfile.js ├── images ├── apiize-cli.gif └── apiize-example.gif ├── lib ├── apiize │ ├── index.js │ ├── server.js │ └── utils.js ├── cli.js └── index.js ├── package.json └── test ├── datasets ├── bad_json.json └── punchlines.json └── index.js /.babelrc: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "env": { 4 | "node": true, 5 | "mocha": true, 6 | "es6": true 7 | }, 8 | "rules": { 9 | "array-bracket-spacing": [ 10 | 2, 11 | "never" 12 | ], 13 | "brace-style": [ 14 | 2, 15 | "1tbs" 16 | ], 17 | "consistent-return": 0, 18 | "indent": [ 19 | 2, 20 | 2 21 | ], 22 | "no-multiple-empty-lines": [ 23 | 2, 24 | { 25 | "max": 2 26 | } 27 | ], 28 | "no-use-before-define": [ 29 | 2, 30 | "nofunc" 31 | ], 32 | "one-var": [ 33 | 2, 34 | "never" 35 | ], 36 | "quote-props": [ 37 | 2, 38 | "as-needed" 39 | ], 40 | "quotes": [ 41 | 1, 42 | "single" 43 | ], 44 | "space-after-keywords": [ 45 | 2, 46 | "always" 47 | ], 48 | "space-before-function-paren": [ 49 | 2, 50 | { 51 | "anonymous": "always", 52 | "named": "never" 53 | } 54 | ], 55 | "space-in-parens": [ 56 | 2, 57 | "never" 58 | ], 59 | "strict": [ 60 | 2, 61 | "global" 62 | ], 63 | "curly": [ 64 | 2, 65 | "all" 66 | ], 67 | "eol-last": 2, 68 | "key-spacing": [ 69 | 2, 70 | { 71 | "beforeColon": false, 72 | "afterColon": true 73 | } 74 | ], 75 | "no-eval": 2, 76 | "no-with": 2, 77 | "space-infix-ops": 2, 78 | "dot-notation": [ 79 | 2, 80 | { 81 | "allowKeywords": true 82 | } 83 | ], 84 | "eqeqeq": 2, 85 | "no-alert": 2, 86 | "no-caller": 2, 87 | "no-empty-label": 2, 88 | "no-extend-native": 2, 89 | "no-extra-bind": 2, 90 | "no-implied-eval": 2, 91 | "no-iterator": 2, 92 | "no-label-var": 2, 93 | "no-labels": 2, 94 | "no-lone-blocks": 2, 95 | "no-loop-func": 2, 96 | "no-multi-spaces": 2, 97 | "no-multi-str": 2, 98 | "no-native-reassign": 2, 99 | "no-new": 1, 100 | "no-new-func": 2, 101 | "no-new-wrappers": 2, 102 | "no-octal-escape": 2, 103 | "no-proto": 2, 104 | "no-return-assign": 2, 105 | "no-script-url": 2, 106 | "no-sequences": 2, 107 | "no-unused-expressions": 2, 108 | "yoda": 2, 109 | "no-shadow": 2, 110 | "no-shadow-restricted-names": 2, 111 | "no-undef-init": 2, 112 | "camelcase": 2, 113 | "comma-spacing": 2, 114 | "new-cap": 2, 115 | "new-parens": 2, 116 | "no-array-constructor": 2, 117 | "no-extra-parens": 2, 118 | "no-new-object": 2, 119 | "no-console": 0, 120 | "no-spaced-func": 2, 121 | "no-trailing-spaces": 2, 122 | "no-underscore-dangle": 2, 123 | "semi": 2, 124 | "semi-spacing": [ 125 | 2, 126 | { 127 | "before": false, 128 | "after": true 129 | } 130 | ], 131 | "space-return-throw-case": 2 132 | }, 133 | "ecmaFeatures": { 134 | "modules": true 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | - '0.12' 5 | - 'iojs' 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Andre Aubin (http://andral.kiwi) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # apiize [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Status][daviddm-image]][daviddm-url] [![Coverage percentage][coveralls-image]][coveralls-url] 2 | 3 | **Turn any JSON file into a full, explorable, REST API.** 4 | 5 | ## Presentation 6 | 7 | #### Start apiize on a JSON file or url 8 | 9 | ![cli-demo](https://raw.githubusercontent.com/lambda2/apiize/master/images/apiize-cli.gif) 10 | 11 | #### And make requests on it 12 | 13 | ![cli-demo](https://raw.githubusercontent.com/lambda2/apiize/master/images/apiize-example.gif) 14 | 15 | 16 | ## Install 17 | 18 | ```sh 19 | $ npm install -g apiize 20 | ``` 21 | 22 | 23 | ## Command line usage 24 | 25 | - Start a new server on a given JSON file or url 26 | 27 | ```bash 28 | $ apiize 'https://raw.githubusercontent.com/lambda2/apiize/master/test/datasets/punchlines.json' 29 | 30 | ___ _ _ 31 | / _ \ (_|_) 32 | / /_\ \_ __ _ _ _______ 33 | | _ | '_ \| | |_ / _ \ 34 | | | | | |_) | | |/ / __/ 35 | \_| |_/ .__/|_|_/___\___| 36 | | | 37 | |_| 38 | 39 | 💫 Express has taken the stage and is listening on port 1313 40 | 41 | ∙ GET /api/contents Return all the contents 42 | ∙ GET /api/contents/:id Return the given contents 43 | ∙ GET /api/tags Return all the tags 44 | ∙ GET /api/tags/:id Return the given tags 45 | ∙ GET /api/authors Return all the authors 46 | ∙ GET /api/authors/:id Return the given authors 47 | ∙ GET /api/albums Return all the albums 48 | ∙ GET /api/albums/:id Return the given albums 49 | ∙ GET /api/titles Return all the titles 50 | ∙ GET /api/titles/:id Return the given titles 51 | ∙ GET /api/ Return all items 52 | ∙ GET /api/random A random item 53 | 54 | ``` 55 | 56 | - Make requests on it 57 | 58 | ```bash 59 | $ curl -H "Accept: application/json" "localhost:1313/" 60 | 61 | {"endpoints":[{"url":"/api/contents","description":"Return all the contents"},{"url":"/api/contents/:id","description":"Return the given contents"},{"url":"/api/tags","description":"Return all the tags"},{"url":"/api/tags/:id","description":"Return the given tags"},{"url":"/api/authors","description":"Return all the authors"},{"url":"/api/authors/:id","description":"Return the given authors"},{"url":"/api/albums","description":"Return all the albums"},{"url":"/api/albums/:id","description":"Return the given albums"},{"url":"/api/titles","description":"Return all the titles"},{"url":"/api/titles/:id","description":"Return the given titles"},{"url":"/api/","description":"Return all items"},{"url":"/api/random","description":"A random item"}]}% 62 | ``` 63 | 64 | ## Usage 65 | 66 | ```js 67 | const Apiize = require('apiize'); 68 | 69 | // Here is the default options 70 | const options = { 71 | verbose: false, // Output more verbose requests 72 | prefix: 'api', // The root endpoint of the API 73 | port: 1313 // The express server port. 74 | }; 75 | 76 | // Turn the 'http://example.org/file.json' file into an API 77 | let apiize = new Apiize('http://example.org/file.json', options); 78 | 79 | // When apiize is ready 80 | apiize.on('ready', function (server) { 81 | server.serve(); // run the server 82 | }); 83 | 84 | ``` 85 | 86 | ## API 87 | 88 | ```js 89 | var Apiize = require('apiize'); 90 | 91 | var api = new Apiize(link, options); 92 | ``` 93 | 94 | ------------------ 95 | 96 | ### Events 97 | 98 | #### ready 99 | 100 | Triggered when the file or the link is fully loaded, and the express server is ready to be started. 101 | 102 | ```js 103 | var api = new Apiize(link, options); 104 | api.on('ready', function (server) { 105 | server.serve(); // run the server 106 | }); 107 | 108 | ``` 109 | 110 | 111 | ------------------ 112 | 113 | ### Methods 114 | 115 | #### new Apiize(link, options) 116 | 117 | Create a new Apiize object using the given `link` and `options`. 118 | The `link` argument must be a string of a JSON file or a JSON URL. 119 | 120 | > Apiize accepts these properties in the options object. 121 | 122 | - verbose 123 | 124 | > Defaults to `false`, add a more verbose logging of incoming requests. 125 | 126 | - Without `verbose`: 127 | `GET / 200 146 - 6.079 ms` 128 | 129 | - With `verbose`: 130 | `::1 - - [25/Jun/2016:15:41:06 +0000] "GET / HTTP/1.1" 200 750 "-" "curl/7.43.0"` 131 | 132 | - prefix 133 | 134 | > Set the root path of the generated api routes. Defaults to `api`. 135 | 136 | - port 137 | 138 | > Set the express server port. Defaults to `1313`. 139 | 140 | #### on(event, callback) 141 | 142 | Triggered when an event is emmmited (e.g., the `ready` event). 143 | 144 | #### serve() 145 | 146 | Start the express server. Must be called when the `ready` event is called. 147 | 148 | 149 | ------------------ 150 | 151 | ## Tests 152 | 153 | To run the test suite, first install the dependencies, then run `npm test`: 154 | 155 | ```bash 156 | $ npm install 157 | $ npm test 158 | ``` 159 | 160 | ## People 161 | 162 | [André Aubin](https://github.com/lambda2) [![Andre's Gratipay][gratipay-image-lambda2]][gratipay-url-lambda2] 163 | 164 | [List of all contributors](https://github.com/lambda2/apiize/graphs/contributors) 165 | 166 | ## License 167 | 168 | MIT © [Andre Aubin](http://andral.kiwi) 169 | 170 | 171 | [npm-image]: https://badge.fury.io/js/apiize.svg 172 | [npm-url]: https://npmjs.org/package/apiize 173 | [travis-image]: https://travis-ci.org/lambda2/apiize.svg?branch=master 174 | [travis-url]: https://travis-ci.org/lambda2/apiize 175 | [daviddm-image]: https://david-dm.org/lambda2/apiize.svg?theme=shields.io 176 | [daviddm-url]: https://david-dm.org/lambda2/apiize 177 | [coveralls-image]: https://coveralls.io/repos/github/lambda2/apiize/badge.svg?branch=master 178 | [coveralls-url]: https://coveralls.io/github/lambda2/apiize?branch=master 179 | [gratipay-image-lambda2]: https://img.shields.io/gratipay/lambda2.svg 180 | [gratipay-url-lambda2]: https://gratipay.com/lambda2/ 181 | -------------------------------------------------------------------------------- /bin/apiize.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var cli = require('../dist/cli.js'); 3 | cli(); 4 | -------------------------------------------------------------------------------- /dist/apiize/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | 7 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 8 | 9 | var _get = function get(_x2, _x3, _x4) { var _again = true; _function: while (_again) { var object = _x2, property = _x3, receiver = _x4; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x2 = parent; _x3 = property; _x4 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 10 | 11 | function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2; } else { return Array.from(arr); } } 12 | 13 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 14 | 15 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 16 | 17 | function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 18 | 19 | var _ = require('lodash'); 20 | var Type = require('type-of-is'); 21 | 22 | var _require = require('./utils'); 23 | 24 | var stringToSlug = _require.stringToSlug; 25 | 26 | var pluralize = require('pluralize'); 27 | var isJSON = require('is-json'); 28 | 29 | var _require2 = require('get-content'); 30 | 31 | var get = _require2.get; 32 | 33 | var Promise = require('promise'); 34 | var Server = require('./server'); 35 | var EventEmitter = require('events'); 36 | 37 | var Apiize = (function (_EventEmitter) { 38 | _inherits(Apiize, _EventEmitter); 39 | 40 | // Take the url or the file to apiize 41 | 42 | function Apiize(data) { 43 | var _this = this; 44 | 45 | var params = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; 46 | 47 | _classCallCheck(this, Apiize); 48 | 49 | _get(Object.getPrototypeOf(Apiize.prototype), 'constructor', this).call(this); 50 | 51 | var defaults = { verbose: false }; 52 | this.params = Object.assign(defaults, params); 53 | this.server = undefined; 54 | 55 | if (typeof data !== 'string') { 56 | throw 'The provided file or url must be a string (got ' + typeof data + ')'; 57 | } 58 | this.dataToJson(data).then(function (response) { 59 | _this.rawData = response; 60 | _this.generate(); 61 | _this.emit('ready', _this); 62 | })['catch'](function () { 63 | console.warn('The ' + data + ' content is not parsable.'); 64 | throw new Error('The ' + data + ' content is not parsable.'); 65 | }); 66 | } 67 | 68 | _createClass(Apiize, [{ 69 | key: 'dataToJson', 70 | value: function dataToJson(data) { 71 | return new Promise(function (accept, reject) { 72 | get(data).then(function (content) { 73 | if (isJSON(content)) { 74 | accept(JSON.parse(content)); 75 | } else { 76 | reject(content); 77 | } 78 | })['catch'](function (e) { 79 | reject(e); 80 | }); 81 | }); 82 | } 83 | }, { 84 | key: 'generate', 85 | value: function generate() { 86 | var _this2 = this; 87 | 88 | // We collect all the available keys 89 | var values = {}; 90 | var currentId = 1; 91 | 92 | // Manage root key 93 | if (this.params.root && this.rawData[0][this.params.root]) { 94 | this.rawData = _.map(this.rawData, function (e) { 95 | return Object.assign(e.id ? { id: e.id } : {}, e[_this2.params.root]); 96 | }); 97 | } 98 | 99 | // Generate ids if needed 100 | _.each(this.rawData, function (e, i) { 101 | if (_.has(e, 'id') && Type.is(e.id, 'Integer')) { 102 | if (e.id > currentId) { 103 | currentId = e.id + 1; 104 | } 105 | } else { 106 | _this2.rawData[i].id = currentId; 107 | currentId++; 108 | } 109 | }); 110 | 111 | _.each(this.rawData, function (e) { 112 | _.each(_.omit(e, ['id']), function (v, k) { 113 | if (!_.isEmpty(k)) { 114 | (function () { 115 | var arrayData = Type.is(v, 'Array') ? v : [v]; 116 | var slug = stringToSlug(k); 117 | 118 | if (!_.has(values, slug)) { 119 | values[slug] = {}; 120 | } 121 | 122 | _.each(arrayData, function (key) { 123 | if (!_.isEmpty(key)) { 124 | var keySlug = stringToSlug(key); 125 | values[slug][keySlug] = values[slug][keySlug] ? _.union(values[slug][keySlug], [e.id]) : [e.id]; 126 | } 127 | }); 128 | })(); 129 | } 130 | }); 131 | }); 132 | this.data = values; 133 | } 134 | }, { 135 | key: 'indexCallbackForResource', 136 | value: function indexCallbackForResource(res, req, resource, values) { 137 | var response = _.map(values, function (val, key) { 138 | var _ref; 139 | 140 | return _ref = {}, _defineProperty(_ref, resource, key), _defineProperty(_ref, 'url', '/' + resource + '/' + key), _defineProperty(_ref, 'count', val.length), _ref; 141 | }); 142 | res.json(response); 143 | } 144 | }, { 145 | key: 'showCallbackForResource', 146 | value: function showCallbackForResource(res, req, values, slug) { 147 | var _this3 = this; 148 | 149 | var ids = values[slug]; 150 | var response = _.map(ids, function (e) { 151 | return _.find(_this3.rawData, { id: e }); 152 | }); 153 | res.json(response); 154 | } 155 | }, { 156 | key: 'randomCallbackForAll', 157 | value: function randomCallbackForAll(res) { 158 | res.json(_.sample(this.rawData)); 159 | } 160 | }, { 161 | key: 'allCallbackForAll', 162 | value: function allCallbackForAll(res) { 163 | res.json(this.rawData); 164 | } 165 | }, { 166 | key: 'routes', 167 | value: function routes() { 168 | var _this4 = this; 169 | 170 | var resourceRoutes = _.flatten(_.map(this.data, function (v, k) { 171 | var indexRoute = '/' + pluralize(k); 172 | var showRoute = '/' + pluralize(k) + '/:id'; 173 | return [{ 174 | route: indexRoute, 175 | info: 'Return all the ' + pluralize(k), 176 | callback: function callback(req, res) { 177 | _this4.indexCallbackForResource(res, req, k, v); 178 | } 179 | }, { 180 | route: showRoute, 181 | info: 'Return the given ' + pluralize(k), 182 | callback: function callback(req, res) { 183 | _this4.showCallbackForResource(res, req, v, req.params.id); 184 | } 185 | }]; 186 | })); 187 | 188 | var indexRoutes = [{ 189 | route: '/', 190 | info: 'Return all items', 191 | callback: function callback(req, res) { 192 | _this4.allCallbackForAll(res, req); 193 | } 194 | }, { 195 | route: '/random', 196 | info: 'A random item', 197 | callback: function callback(req, res) { 198 | _this4.randomCallbackForAll(res, req); 199 | } 200 | }]; 201 | return [].concat(_toConsumableArray(resourceRoutes), indexRoutes); 202 | } 203 | }, { 204 | key: 'serve', 205 | value: function serve() { 206 | this.server = new Server(this, this.params); 207 | this.server.listen(); 208 | } 209 | }]); 210 | 211 | return Apiize; 212 | })(EventEmitter); 213 | 214 | exports['default'] = Apiize; 215 | module.exports = exports['default']; -------------------------------------------------------------------------------- /dist/apiize/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | 7 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 8 | 9 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 10 | 11 | require('colors'); 12 | var morgan = require('morgan'); 13 | var express = require('express'); 14 | var app = express(); 15 | var expressRouter = express.Router; 16 | var responseTime = require('response-time'); 17 | 18 | var Server = (function () { 19 | 20 | // Create a new Express server 21 | 22 | function Server(apiize) { 23 | var params = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; 24 | 25 | _classCallCheck(this, Server); 26 | 27 | var defaults = { 28 | port: 1313, 29 | prefix: 'api' 30 | }; 31 | 32 | this.apiize = apiize; 33 | this.params = Object.assign(defaults, params); 34 | app.use(responseTime()); 35 | 36 | // Set json header 37 | app.use(function (req, res, next) { 38 | res.header('Content-Type', 'application/json'); 39 | next(); 40 | }); 41 | 42 | app.use(morgan(this.apiize.params.verbose ? 'combined' : 'tiny')); 43 | 44 | this.server = false; 45 | this.routes = this.apiize.routes(); 46 | 47 | // Mount routes 48 | app.use('/api', this.router()); 49 | app.use(this.index()); 50 | app.use(function (req, res) { 51 | res.status(404).json({ error: 'Not found', message: 'Unable to find route to ' + req.path }); 52 | }); 53 | } 54 | 55 | // Handle root path 56 | 57 | _createClass(Server, [{ 58 | key: 'index', 59 | value: function index() { 60 | var _this = this; 61 | 62 | var router = expressRouter(); 63 | 64 | router.get('/', function (req, res) { 65 | res.format({ 66 | 67 | 'text/html': function textHtml() { 68 | // The client requested an html page 69 | res.send('\n

This api only respond to json, please try to request this endpoint with application/json Content Type.

\n '); 70 | }, 71 | 'default': function _default() { 72 | // Will print the index ouf available routes 73 | res.json(_this.jsonIndex(_this.routes)); 74 | } 75 | }); 76 | }); 77 | 78 | // test route to make sure everything is working (accessed at GET http://localhost:8080/api) 79 | var _iteratorNormalCompletion = true; 80 | var _didIteratorError = false; 81 | var _iteratorError = undefined; 82 | 83 | try { 84 | for (var _iterator = this.routes[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { 85 | var route = _step.value; 86 | 87 | router.get(route.route, route.callback); 88 | } 89 | } catch (err) { 90 | _didIteratorError = true; 91 | _iteratorError = err; 92 | } finally { 93 | try { 94 | if (!_iteratorNormalCompletion && _iterator['return']) { 95 | _iterator['return'](); 96 | } 97 | } finally { 98 | if (_didIteratorError) { 99 | throw _iteratorError; 100 | } 101 | } 102 | } 103 | 104 | return router; 105 | } 106 | 107 | // Create index ouf routes on the root path 108 | }, { 109 | key: 'jsonIndex', 110 | value: function jsonIndex(routes) { 111 | var _this2 = this; 112 | 113 | if (!this.indexCache) { 114 | this.indexCache = { 115 | endpoints: routes.map(function (r) { 116 | return { url: '/' + _this2.params.prefix + r.route, description: r.info }; 117 | }) 118 | }; 119 | } 120 | return this.indexCache; 121 | } 122 | 123 | // Create routes 124 | }, { 125 | key: 'router', 126 | value: function router() { 127 | var router = expressRouter(); 128 | // test route to make sure everything is working (accessed at GET http://localhost:8080/api) 129 | var _iteratorNormalCompletion2 = true; 130 | var _didIteratorError2 = false; 131 | var _iteratorError2 = undefined; 132 | 133 | try { 134 | for (var _iterator2 = this.routes[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { 135 | var route = _step2.value; 136 | 137 | router.get(route.route, route.callback); 138 | } 139 | } catch (err) { 140 | _didIteratorError2 = true; 141 | _iteratorError2 = err; 142 | } finally { 143 | try { 144 | if (!_iteratorNormalCompletion2 && _iterator2['return']) { 145 | _iterator2['return'](); 146 | } 147 | } finally { 148 | if (_didIteratorError2) { 149 | throw _iteratorError2; 150 | } 151 | } 152 | } 153 | 154 | return router; 155 | } 156 | 157 | // Stop the server 158 | }, { 159 | key: 'stop', 160 | value: function stop() { 161 | if (this.server) { 162 | this.server.close(); 163 | this.server = false; 164 | } 165 | } 166 | 167 | // Say hello and print routes 168 | }, { 169 | key: 'hello', 170 | value: function hello() { 171 | console.log('\n ___ _ _ \n / _ \\ (_|_) \n/ /_\\ \\_ __ _ _ _______ \n| _ | \'_ \\| | |_ / _ \\\n| | | | |_) | | |/ / __/\n\\_| |_/ .__/|_|_/___\\___|\n | | \n |_| \n '.rainbow); 172 | console.log('💫 ' + (' Express has taken the stage and is listening on port ' + this.params.port + '\n').green); 173 | var _iteratorNormalCompletion3 = true; 174 | var _didIteratorError3 = false; 175 | var _iteratorError3 = undefined; 176 | 177 | try { 178 | for (var _iterator3 = this.routes[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { 179 | var route = _step3.value; 180 | 181 | var info = route.info ? (' ' + route.info).grey : ''; 182 | console.log('∙ '.green + ('GET /' + this.params.prefix + route.route) + info); 183 | } 184 | } catch (err) { 185 | _didIteratorError3 = true; 186 | _iteratorError3 = err; 187 | } finally { 188 | try { 189 | if (!_iteratorNormalCompletion3 && _iterator3['return']) { 190 | _iterator3['return'](); 191 | } 192 | } finally { 193 | if (_didIteratorError3) { 194 | throw _iteratorError3; 195 | } 196 | } 197 | } 198 | } 199 | 200 | // Start the server 201 | }, { 202 | key: 'listen', 203 | value: function listen() { 204 | this.server = app.listen(this.params.port); 205 | this.hello(); 206 | } 207 | }]); 208 | 209 | return Server; 210 | })(); 211 | 212 | exports['default'] = Server; 213 | module.exports = exports['default']; -------------------------------------------------------------------------------- /dist/apiize/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | var _ = require('lodash'); 7 | 8 | var utils = { 9 | stringToSlug: function stringToSlug(string) { 10 | return _.kebabCase(string); 11 | } 12 | }; 13 | 14 | exports['default'] = utils; 15 | module.exports = exports['default']; -------------------------------------------------------------------------------- /dist/cli.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | 7 | var program = require('commander'); 8 | 9 | var Apiize = require('./apiize'); 10 | var Info = require('../package.json'); 11 | 12 | // If we are called from the command line 13 | 14 | exports['default'] = function () { 15 | program.version(Info.version).usage('[options] ').option('-p, --port ', 'Set the server port (default to 1313)', parseInt).option('-r, --root ', 'Get only data under the specified root key').option('-v, --verbose', 'Add additional logs').arguments(' [options]').action(function (cmd, env, o) { 16 | var apiize = new Apiize(cmd, o); 17 | apiize.on('ready', function (e) { 18 | e.serve(); 19 | }); 20 | }).parse(process.argv); 21 | 22 | if (process.argv.length === 2 || program.args.length === 0) { 23 | program.help(); 24 | } 25 | return program; 26 | }; 27 | 28 | module.exports = exports['default']; -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | var Apiize = require('./apiize'); 7 | 8 | exports['default'] = Apiize; 9 | module.exports = exports['default']; -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var gulp = require('gulp'); 3 | var eslint = require('gulp-eslint'); 4 | var excludeGitignore = require('gulp-exclude-gitignore'); 5 | var mocha = require('gulp-mocha'); 6 | var istanbul = require('gulp-istanbul'); 7 | var plumber = require('gulp-plumber'); 8 | var coveralls = require('gulp-coveralls'); 9 | var babel = require('gulp-babel'); 10 | var del = require('del'); 11 | var isparta = require('isparta'); 12 | 13 | // Initialize the babel transpiler so ES2015 files gets compiled 14 | // when they're loaded 15 | require('babel-core/register'); 16 | 17 | gulp.task('static', function () { 18 | return gulp.src(['test/**/*.js', 'lib/**/*.js']) 19 | .pipe(excludeGitignore()) 20 | .pipe(eslint()) 21 | .pipe(eslint.format()) 22 | .pipe(eslint.failAfterError()); 23 | }); 24 | 25 | gulp.task('pre-test', function () { 26 | return gulp.src('lib/**/*.js') 27 | .pipe(istanbul({ 28 | includeUntested: true, 29 | instrumenter: isparta.Instrumenter 30 | })) 31 | .pipe(istanbul.hookRequire()); 32 | }); 33 | 34 | gulp.task('test', ['pre-test'], function (cb) { 35 | var mochaErr; 36 | 37 | gulp.src('test/**/*.js') 38 | .pipe(plumber()) 39 | .pipe(mocha({reporter: 'spec'})) 40 | .on('error', function (err) { 41 | mochaErr = err; 42 | }) 43 | .pipe(istanbul.writeReports()) 44 | .on('end', function () { 45 | cb(mochaErr); 46 | }); 47 | }); 48 | 49 | gulp.task('coveralls', ['test'], function () { 50 | if (!process.env.CI) { 51 | return; 52 | } 53 | 54 | return gulp.src(path.join(__dirname, 'coverage/lcov.info')) 55 | .pipe(coveralls()); 56 | }); 57 | 58 | gulp.task('babel', ['clean'], function () { 59 | return gulp.src('lib/**/*.js') 60 | .pipe(babel()) 61 | .pipe(gulp.dest('dist')); 62 | }); 63 | 64 | gulp.task('clean', function () { 65 | return del('dist'); 66 | }); 67 | 68 | gulp.task('prepublish', ['babel']); 69 | gulp.task('default', ['static', 'test', 'coveralls']); 70 | -------------------------------------------------------------------------------- /images/apiize-cli.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambda2/apiize/3994efacbf31372b96cc69cca922d4dadbe180a4/images/apiize-cli.gif -------------------------------------------------------------------------------- /images/apiize-example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambda2/apiize/3994efacbf31372b96cc69cca922d4dadbe180a4/images/apiize-example.gif -------------------------------------------------------------------------------- /lib/apiize/index.js: -------------------------------------------------------------------------------- 1 | let _ = require('lodash'); 2 | let Type = require('type-of-is'); 3 | let { stringToSlug } = require('./utils'); 4 | let pluralize = require('pluralize'); 5 | let isJSON = require('is-json'); 6 | let { get } = require('get-content'); 7 | let Promise = require('promise'); 8 | let Server = require('./server'); 9 | const EventEmitter = require('events'); 10 | 11 | class Apiize extends EventEmitter { 12 | 13 | // Take the url or the file to apiize 14 | constructor(data, params = {}) { 15 | super(); 16 | 17 | const defaults = {verbose: false}; 18 | this.params = Object.assign(defaults, params); 19 | this.server = undefined; 20 | 21 | if (typeof data !== 'string') { 22 | throw `The provided file or url must be a string (got ${typeof data})`; 23 | } 24 | this.dataToJson(data).then((response) => { 25 | this.rawData = response; 26 | this.generate(); 27 | this.emit('ready', this); 28 | }).catch(() => { 29 | console.warn(`The ${data} content is not parsable.`); 30 | throw new Error(`The ${data} content is not parsable.`); 31 | }); 32 | } 33 | 34 | dataToJson(data) { 35 | return new Promise(function (accept, reject) { 36 | get(data).then((content) => { 37 | if (isJSON(content)) { 38 | accept(JSON.parse(content)); 39 | } else { 40 | reject(content); 41 | } 42 | }).catch((e) => { 43 | reject(e); 44 | }); 45 | }); 46 | } 47 | 48 | generate() { 49 | // We collect all the available keys 50 | let values = {}; 51 | let currentId = 1; 52 | 53 | // Manage root key 54 | if (this.params.root && this.rawData[0][this.params.root]) { 55 | this.rawData = _.map(this.rawData, (e) => { 56 | return Object.assign(e.id ? {id: e.id} : {}, e[this.params.root]); 57 | }); 58 | } 59 | 60 | // Generate ids if needed 61 | _.each(this.rawData, (e, i) => { 62 | if (_.has(e, 'id') && Type.is(e.id, 'Integer')) { 63 | if (e.id > currentId) { 64 | currentId = e.id + 1; 65 | } 66 | } else { 67 | this.rawData[i].id = currentId; 68 | currentId++; 69 | } 70 | }); 71 | 72 | _.each(this.rawData, (e) => { 73 | _.each(_.omit(e, ['id']), function (v, k) { 74 | if (!_.isEmpty(k)) { 75 | let arrayData = Type.is(v, 'Array') ? v : [v]; 76 | let slug = stringToSlug(k); 77 | 78 | if (!_.has(values, slug)) { 79 | values[slug] = {}; 80 | } 81 | 82 | _.each(arrayData, function (key){ 83 | if (!_.isEmpty(key)) { 84 | let keySlug = stringToSlug(key); 85 | values[slug][keySlug] = values[slug][keySlug] ? _.union(values[slug][keySlug], [e.id]) : [e.id]; 86 | } 87 | }); 88 | } 89 | }); 90 | }); 91 | this.data = values; 92 | } 93 | 94 | indexCallbackForResource(res, req, resource, values) { 95 | let response = _.map(values, function (val, key) { 96 | return { 97 | [resource]: key, 98 | url: `/${resource}/${key}`, 99 | count: val.length 100 | }; 101 | }); 102 | res.json(response); 103 | } 104 | 105 | showCallbackForResource(res, req, values, slug) { 106 | let ids = values[slug]; 107 | let response = _.map(ids, (e) => { 108 | return _.find(this.rawData, {id: e}); 109 | }); 110 | res.json(response); 111 | } 112 | 113 | randomCallbackForAll(res) { 114 | res.json(_.sample(this.rawData)); 115 | } 116 | 117 | allCallbackForAll(res) { 118 | res.json(this.rawData); 119 | } 120 | 121 | routes() { 122 | const resourceRoutes = _.flatten(_.map(this.data, (v, k) => { 123 | const indexRoute = `/${pluralize(k)}`; 124 | const showRoute = `/${pluralize(k)}/:id`; 125 | return [{ 126 | route: indexRoute, 127 | info: `Return all the ${pluralize(k)}`, 128 | callback: (req, res) => { 129 | this.indexCallbackForResource(res, req, k, v); 130 | } 131 | }, { 132 | route: showRoute, 133 | info: `Return the given ${pluralize(k)}`, 134 | callback: (req, res) => { 135 | this.showCallbackForResource(res, req, v, req.params.id); 136 | } 137 | }]; 138 | })); 139 | 140 | const indexRoutes = [ 141 | { 142 | route: '/', 143 | info: `Return all items`, 144 | callback: (req, res) => { 145 | this.allCallbackForAll(res, req); 146 | } 147 | }, { 148 | route: '/random', 149 | info: `A random item`, 150 | callback: (req, res) => { 151 | this.randomCallbackForAll(res, req); 152 | } 153 | } 154 | ]; 155 | return [...resourceRoutes, ...indexRoutes]; 156 | } 157 | 158 | serve() { 159 | this.server = new Server(this, this.params); 160 | this.server.listen(); 161 | } 162 | } 163 | 164 | export default Apiize; 165 | -------------------------------------------------------------------------------- /lib/apiize/server.js: -------------------------------------------------------------------------------- 1 | require('colors'); 2 | let morgan = require('morgan'); 3 | let express = require('express'); 4 | let app = express(); 5 | let expressRouter = express.Router; 6 | let responseTime = require('response-time'); 7 | 8 | class Server { 9 | 10 | // Create a new Express server 11 | constructor(apiize, params = {}) { 12 | 13 | const defaults = { 14 | port: 1313, 15 | prefix: 'api' 16 | }; 17 | 18 | this.apiize = apiize; 19 | this.params = Object.assign(defaults, params); 20 | app.use(responseTime()); 21 | 22 | // Set json header 23 | app.use(function (req, res, next) { 24 | res.header('Content-Type', 'application/json'); 25 | next(); 26 | }); 27 | 28 | app.use(morgan(this.apiize.params.verbose ? 'combined' : 'tiny')); 29 | 30 | this.server = false; 31 | this.routes = this.apiize.routes(); 32 | 33 | // Mount routes 34 | app.use('/api', this.router()); 35 | app.use(this.index()); 36 | app.use(function (req, res) { 37 | res.status(404).json({error: 'Not found', message: `Unable to find route to ${req.path}`}); 38 | }); 39 | } 40 | 41 | // Handle root path 42 | index() { 43 | let router = expressRouter(); 44 | 45 | router.get('/', (req, res) => { 46 | res.format({ 47 | 48 | 'text/html': function (){ 49 | // The client requested an html page 50 | res.send(` 51 |

This api only respond to json, please try to request this endpoint with application/json Content Type.

52 | `); 53 | }, 54 | default: () => { 55 | // Will print the index ouf available routes 56 | res.json(this.jsonIndex(this.routes)); 57 | } 58 | }); 59 | }); 60 | 61 | // test route to make sure everything is working (accessed at GET http://localhost:8080/api) 62 | for (const route of this.routes) { 63 | router.get(route.route, route.callback); 64 | } 65 | 66 | return router; 67 | } 68 | 69 | // Create index ouf routes on the root path 70 | jsonIndex(routes) { 71 | if (!this.indexCache) { 72 | this.indexCache = { 73 | endpoints: routes.map((r) => { 74 | return {url: `/${this.params.prefix}${r.route}`, description: r.info}; 75 | }) 76 | }; 77 | } 78 | return this.indexCache; 79 | } 80 | 81 | // Create routes 82 | router() { 83 | let router = expressRouter(); 84 | // test route to make sure everything is working (accessed at GET http://localhost:8080/api) 85 | for (const route of this.routes) { 86 | router.get(route.route, route.callback); 87 | } 88 | 89 | return router; 90 | } 91 | 92 | // Stop the server 93 | stop() { 94 | if (this.server) { 95 | this.server.close(); 96 | this.server = false; 97 | } 98 | } 99 | 100 | // Say hello and print routes 101 | hello() { 102 | console.log(`\n ___ _ _ \n / _ \\ (_|_) \n/ /_\\ \\_ __ _ _ _______ \n| _ | '_ \\| | |_ / _ \\\n| | | | |_) | | |/ / __/\n\\_| |_/ .__/|_|_/___\\___|\n | | \n |_| \n `.rainbow); 103 | console.log('💫 ' + ` Express has taken the stage and is listening on port ${this.params.port}\n`.green); 104 | for (const route of this.routes) { 105 | let info = route.info ? ` ${route.info}`.grey : ''; 106 | console.log('∙ '.green + `GET /${this.params.prefix}${route.route}` + info); 107 | } 108 | } 109 | 110 | // Start the server 111 | listen() { 112 | this.server = app.listen(this.params.port); 113 | this.hello(); 114 | } 115 | 116 | } 117 | 118 | export default Server; 119 | -------------------------------------------------------------------------------- /lib/apiize/utils.js: -------------------------------------------------------------------------------- 1 | let _ = require('lodash'); 2 | 3 | let utils = { 4 | stringToSlug: function (string) { 5 | return _.kebabCase(string); 6 | } 7 | }; 8 | 9 | export default utils; 10 | -------------------------------------------------------------------------------- /lib/cli.js: -------------------------------------------------------------------------------- 1 | 2 | let program = require('commander'); 3 | 4 | let Apiize = require('./apiize'); 5 | let Info = require('../package.json'); 6 | 7 | // If we are called from the command line 8 | export default function () { 9 | program 10 | .version(Info.version) 11 | .usage(`[options] `) 12 | .option('-p, --port ', 'Set the server port (default to 1313)', parseInt) 13 | .option('-r, --root ', 'Get only data under the specified root key') 14 | .option('-v, --verbose', 'Add additional logs') 15 | .arguments(' [options]') 16 | .action(function (cmd, env, o) { 17 | let apiize = new Apiize(cmd, o); 18 | apiize.on('ready', function (e) { 19 | e.serve(); 20 | }); 21 | }) 22 | .parse(process.argv); 23 | 24 | if (process.argv.length === 2 || program.args.length === 0) { 25 | program.help(); 26 | } 27 | return program; 28 | } 29 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | let Apiize = require('./apiize'); 2 | 3 | export default Apiize; 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apiize", 3 | "version": "0.1.2", 4 | "description": "Turn any json file into a full, explorable, REST API", 5 | "homepage": "", 6 | "author": { 7 | "name": "Andre Aubin", 8 | "email": "andre.aubin@lamdaweb.fr", 9 | "url": "http://andral.kiwi" 10 | }, 11 | "files": [ 12 | "dist", 13 | "bin" 14 | ], 15 | "main": "dist/index.js", 16 | "keywords": [ 17 | "api", 18 | "automation", 19 | "json", 20 | "server", 21 | "endpoints" 22 | ], 23 | "repository": "git@github.com:lambda2/apiize.git", 24 | "bin": { 25 | "apiize": "bin/apiize.js" 26 | }, 27 | "engines": { 28 | "node": ">=0.10.0" 29 | }, 30 | "devDependencies": { 31 | "babel-core": "^5.5.0", 32 | "del": "^2.0.2", 33 | "gulp": "^3.6.0", 34 | "gulp-babel": "^5.1.0", 35 | "gulp-coveralls": "^0.1.0", 36 | "gulp-eslint": "^1.0.0", 37 | "gulp-exclude-gitignore": "^1.0.0", 38 | "gulp-istanbul": "^0.9.0", 39 | "gulp-mocha": "^2.0.0", 40 | "gulp-nsp": "^0.4.5", 41 | "gulp-plumber": "^1.0.0", 42 | "isparta": "^3.0.3", 43 | "mocha": "^2.5.3", 44 | "request": "^2.72.0" 45 | }, 46 | "scripts": { 47 | "prepublish": "gulp prepublish", 48 | "test": "gulp" 49 | }, 50 | "license": "Apache-2.0", 51 | "dependencies": { 52 | "colors": "^1.1.2", 53 | "commander": "^2.9.0", 54 | "expect.js": "^0.3.1", 55 | "express": "^4.13.4", 56 | "get-content": "^1.0.1", 57 | "is-json": "^2.0.0", 58 | "jsonfile": "^2.3.1", 59 | "lodash": "^4.11.1", 60 | "mkpath": "^1.0.0", 61 | "morgan": "^1.7.0", 62 | "pluralize": "^3.0.0", 63 | "promise": "^7.1.1", 64 | "response-time": "^2.3.1", 65 | "type-of-is": "^3.4.0" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 2 | import chai from 'chai'; 3 | import { get } from 'get-content'; 4 | import Apiize from '../lib'; 5 | import cli from '../lib/cli'; 6 | 7 | const assert = chai.assert; 8 | 9 | const jsonTestFile = './test/datasets/punchlines.json'; 10 | // const jsonTestBadFile = 'test/datasets/bad_json.json'; 11 | // const jsonTestInexistantFile = './i-dont-exist.json'; 12 | const jsonTestUrl = 'http://opendata.paris.fr/explore/dataset/les-1000-titres-les-plus-reserves-dans-les-bibliotheques-de-pret/download\?format\=json'; 13 | 14 | describe('apiize', function () { 15 | 16 | describe('cli', function () { 17 | it('should handle args', function (done) { 18 | 19 | // Thug life 20 | let oldArgvs = process.argv; 21 | process.argv = ['node', 'apiize', '-v', '-p', '1234', 'tata']; 22 | let program = cli(); 23 | assert.equal(program.port, 1234, 'port should be set'); 24 | assert.isTrue(program.verbose, 'verbose is enabled'); 25 | process.argv = oldArgvs; 26 | done(); 27 | }); 28 | }); 29 | 30 | describe('boot', function () { 31 | 32 | it('should not be nil', function (done) { 33 | let apiize = new Apiize(jsonTestFile); 34 | assert.isOk(apiize, 'should not be nil'); 35 | done(); 36 | }); 37 | 38 | it('should be created from file', function (done) { 39 | // this.timeout(10000); 40 | let apiize = new Apiize(jsonTestFile); 41 | apiize.on('ready', function (e) { 42 | assert.isOk(e, 'should not be nil'); 43 | assert.isOk(e.rawData, 'should not be nil'); 44 | done(); 45 | }); 46 | }); 47 | 48 | // it('should fail on unparsable file', function () { 49 | // this.timeout(10000); 50 | // assert.throws(() => { 51 | // new Apiize(jsonTestBadFile); 52 | // }, Error, /content is not parsable/, 'must throw an error'); 53 | // }); 54 | 55 | it('should be created from url', function (done) { 56 | this.timeout(5000); 57 | let apiize = new Apiize(jsonTestUrl); 58 | apiize.on('ready', function (e) { 59 | assert.isOk(e, 'should not be nil'); 60 | assert.isOk(e.rawData, 'should not be nil'); 61 | done(); 62 | }); 63 | }); 64 | 65 | it('should create server', function (done) { 66 | // this.timeout(5000); 67 | let apiize = new Apiize(jsonTestFile); 68 | apiize.on('ready', function (e) { 69 | assert.isOk(e, 'should not be nil'); 70 | assert.isOk(e.rawData, 'should not be nil'); 71 | assert.isUndefined(e.server, 'should be nil'); 72 | e.serve(); 73 | assert.isOk(e.server, 'should be created'); 74 | get(`http://localhost:${e.server.params.port}`).then((response) => { 75 | console.log("Content: ", response); 76 | assert.match(response, /This api only respond to json/, 'should return html response'); 77 | e.server.stop(); 78 | done(); 79 | }).catch((err) => { 80 | return done(err); 81 | }); 82 | }); 83 | }); 84 | }); 85 | 86 | }); 87 | --------------------------------------------------------------------------------