├── .env.example ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── example ├── controllers │ └── productController.js ├── models │ └── Product.js ├── routes │ └── index.js └── server.js ├── index.js ├── package.json └── test └── autocrud.test.js /.env.example: -------------------------------------------------------------------------------- 1 | DATABASE_URI= # MongoDB connection string -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Test Action 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Setup Test ENV 13 | run: | 14 | docker run -d -p 27017:27017 mongo 15 | - name: npm install and test 16 | run: | 17 | npm install 18 | npm run test 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "8" 5 | - "10" 6 | - "11" 7 | - "12" 8 | 9 | services: 10 | - mongodb 11 | 12 | env: 13 | - DATABASE_URI=mongodb://admin:admin@127.0.0.1/mydb_test 14 | 15 | os: 16 | - linux 17 | 18 | before_script: 19 | - sleep 15 20 | - mongo mydb_test --eval 'db.createUser({user:"admin",pwd:"admin",roles:["readWrite"]});' 21 | 22 | script: 23 | - npm run coveralls 24 | 25 | notifications: 26 | email: 27 | on_success: never 28 | on_failure: always 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Marco Ferraioli 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 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, 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fastify-autocrud 2 | 3 |
4 | 5 | [![build status](https://travis-ci.org/paranoiasystem/fastify-autocrud.svg?branch=master)](https://travis-ci.org/paranoiasystem/fastify-autocrud) 6 | [![issues](https://img.shields.io/github/issues/paranoiasystem/fastify-autocrud)](https://github.com/paranoiasystem/fastify-autocrud/issues) 7 | [![Coverage Status](https://coveralls.io/repos/github/paranoiasystem/fastify-autocrud/badge.svg?branch=master)](https://coveralls.io/github/paranoiasystem/fastify-autocrud?branch=master) 8 | [![license](https://img.shields.io/github/license/paranoiasystem/fastify-autocrud)](./LICENSE) 9 | ![version](https://img.shields.io/npm/v/fastify-autocrud) 10 | [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/) 11 | [![Beerpay](https://img.shields.io/beerpay/paranoiasystem/fastify-autocrud)](https://beerpay.io/paranoiasystem/fastify-autocrud) 12 | 13 |
14 | 15 | Plugin for autogenerate CRUD routes as fast as possible. 16 | 17 | ## Install 18 | 19 | ``` 20 | npm i fastify-autocrud --save 21 | ``` 22 | 23 | ## Usage 24 | 25 | ```js 26 | fastify.register(require('fastify-autocrud'), { 27 | prefix: '/api/products', 28 | Collection: require('../models/Product') 29 | }) 30 | ``` 31 | 32 | **_Product.js_** 33 | ```js 34 | const mongoose = require('mongoose') 35 | 36 | const Product = new mongoose.Schema({ 37 | name: String, 38 | description: String, 39 | image: String, 40 | cost: Number, 41 | price: Number, 42 | qty: Number 43 | }, { 44 | timestamps: true 45 | }) 46 | 47 | module.exports = mongoose.model('Product', Product) 48 | ``` 49 | 50 | If you want add custom routes 51 | 52 | ```js 53 | const customController = require('customController') 54 | 55 | fastify.register(require('fastify-autocrud'), { 56 | prefix: '/api/products', 57 | Collection: require('../models/Product'), 58 | additionalRoute: customController 59 | }) 60 | ``` 61 | 62 | **_customController.js_** 63 | ```js 64 | const boom = require('boom') 65 | 66 | const customRoute = { 67 | method: 'GET', 68 | url: '/custom', 69 | handler: async function (req, reply) { 70 | try { 71 | reply.type('application/json').code(200).send({ "customroute": "ok" }) 72 | } catch (err) { 73 | throw boom.boomify(err) 74 | } 75 | } 76 | } 77 | 78 | module.exports = [customRoute] 79 | ``` 80 | 81 | ## License 82 | 83 | Licensed under [MIT](./LICENSE). 84 | -------------------------------------------------------------------------------- /example/controllers/productController.js: -------------------------------------------------------------------------------- 1 | const boom = require('boom') 2 | 3 | const customRoute = { 4 | method: 'GET', 5 | url: '/custom', 6 | handler: async function (req, reply) { 7 | try { 8 | reply.type('application/json').code(200).send({ 'customroute': 'ok' }) 9 | } catch (err) { 10 | throw boom.boomify(err) 11 | } 12 | } 13 | } 14 | 15 | module.exports = [customRoute] 16 | -------------------------------------------------------------------------------- /example/models/Product.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const Product = new mongoose.Schema({ 4 | name: String, 5 | description: String, 6 | image: String, 7 | cost: Number, 8 | price: Number, 9 | qty: Number 10 | }, { 11 | timestamps: true 12 | }) 13 | 14 | module.exports = mongoose.model('Product', Product) 15 | -------------------------------------------------------------------------------- /example/routes/index.js: -------------------------------------------------------------------------------- 1 | const productController = require('../controllers/productController') 2 | 3 | module.exports = function app (fastify, opts, next) { 4 | fastify.register(require('../../index'), { 5 | prefix: '/api/products', 6 | Collection: require('../models/Product'), 7 | additionalRoute: productController 8 | }) 9 | 10 | next() 11 | } 12 | -------------------------------------------------------------------------------- /example/server.js: -------------------------------------------------------------------------------- 1 | const fastify = require('fastify')({ 2 | logger: true 3 | }) 4 | 5 | require('dotenv').config() 6 | const mongoose = require('mongoose') 7 | 8 | mongoose.connect(process.env.DATABASE_URI, { 9 | useCreateIndex: true, 10 | useNewUrlParser: true, 11 | useFindAndModify: false 12 | }).then(() => console.log('MongoDB connected...')) 13 | .catch(err => console.log(err)) 14 | 15 | fastify.register(require('./routes')) 16 | 17 | const start = async () => { 18 | try { 19 | await fastify.listen(3000, '0.0.0.0') 20 | fastify.log.info(`server listening on ${fastify.server.address().port}`) 21 | } catch (err) { 22 | fastify.log.error(err) 23 | process.exit(1) 24 | } 25 | } 26 | start() 27 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const boom = require('boom') 2 | 3 | function plugin (fastify, opts, next) { 4 | fastify.get('/', opts, async (req, reply) => { 5 | try { 6 | reply.type('application/json').code(200).send(await opts.Collection.find()) 7 | } catch (err) { 8 | throw boom.boomify(err) 9 | } 10 | }) 11 | 12 | fastify.get('/:id', opts, async (req, reply) => { 13 | try { 14 | reply.type('application/json').code(200).send(await opts.Collection.findById(req.params.id)) 15 | } catch (err) { 16 | throw boom.boomify(err) 17 | } 18 | }) 19 | 20 | fastify.post('/', opts, async (req, reply) => { 21 | try { 22 | reply.type('application/json').code(201).send(await new opts.Collection(req.body).save()) 23 | } catch (err) { 24 | throw boom.boomify(err) 25 | } 26 | }) 27 | 28 | fastify.put('/:id', opts, async (req, reply) => { 29 | try { 30 | reply.type('application/json').code(200).send(await opts.Collection.findOneAndUpdate({ _id: req.params.id }, req.body, { new: true })) 31 | } catch (err) { 32 | throw boom.boomify(err) 33 | } 34 | }) 35 | 36 | fastify.delete('/:id', opts, async (req, reply) => { 37 | try { 38 | reply.type('application/json').code(204).send(await opts.Collection.deleteOne({ _id: req.params.id })) 39 | } catch (err) { 40 | throw boom.boomify(err) 41 | } 42 | }) 43 | 44 | opts.additionalRoute.map(route => { 45 | fastify.route(route) 46 | }) 47 | 48 | next() 49 | } 50 | 51 | module.exports = plugin 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fastify-autocrud", 3 | "version": "0.0.4", 4 | "description": "Plugin for autogenerate CRUD route as fast as possible.", 5 | "main": "index.js", 6 | "scripts": { 7 | "coveralls": "npm run test -- --cov --coverage-report=html && nyc report --reporter=text-lcov | coveralls", 8 | "lint": "npm run lint:standard", 9 | "lint:standard": "standard | snazzy", 10 | "lintfix": "standard --fix", 11 | "example": "node example/server.js", 12 | "test": "npm run lint && npm run unit", 13 | "unit": "tap test/*.test.js" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/paranoiasystem/fastify-autocrud.git" 18 | }, 19 | "keywords": [ 20 | "fastify", 21 | "CRUD", 22 | "mongoose" 23 | ], 24 | "author": "Marco Ferraioli - @ParanoiaSystem", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/paranoiasystem/fastify-autocrud/issues" 28 | }, 29 | "homepage": "https://github.com/paranoiasystem/fastify-autocrud", 30 | "dependencies": { 31 | "boom": "^7.3.0", 32 | "fastify-plugin": "^1.6.0" 33 | }, 34 | "devDependencies": { 35 | "axios": "^0.19.0", 36 | "coveralls": "^3.0.6", 37 | "dotenv": "^8.0.0", 38 | "fastify": "^2.7.1", 39 | "mongoose": "^5.6.9", 40 | "snazzy": "^8.0.0", 41 | "standard": "^12.0.0", 42 | "tap": "^14.6.1" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/autocrud.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const tap = require('tap') 4 | const axios = require('axios') 5 | const Fastify = require('fastify') 6 | const mongoose = require('mongoose') 7 | const dotenv = require('dotenv') 8 | 9 | var testId = null 10 | 11 | tap.test('Test fastify-autocrud', (tap) => { 12 | const fastify = Fastify() 13 | dotenv.config() 14 | 15 | mongoose.connect(process.env.DATABASE_URI || 'mongodb://127.0.0.1/mydb_test', { 16 | useCreateIndex: true, 17 | useNewUrlParser: true, 18 | useFindAndModify: false 19 | }).catch(err => console.log(err)) 20 | 21 | fastify.register(require('../example/routes')) 22 | 23 | tap.tearDown(() => { 24 | mongoose.disconnect() 25 | fastify.close() 26 | }) 27 | 28 | fastify.listen(3000, '0.0.0.0', err => { 29 | tap.error(err) 30 | 31 | tap.test('Test custom route', (tap) => { 32 | axios.get('http://localhost:3000/api/products/custom').then((res) => { 33 | tap.strictEqual(res.status, 200) 34 | tap.is(JSON.stringify(res.data), '{"customroute":"ok"}') 35 | tap.end() 36 | }).catch((err) => { 37 | throw err 38 | }) 39 | }) 40 | 41 | tap.test('Test get all route', (tap) => { 42 | axios.get('http://localhost:3000/api/products').then((res) => { 43 | tap.strictEqual(res.status, 200) 44 | tap.is(JSON.stringify(res.data), '[]') 45 | tap.end() 46 | }).catch((err) => { 47 | throw err 48 | }) 49 | }) 50 | 51 | tap.test('Test post route', (tap) => { 52 | axios.post('http://localhost:3000/api/products', { 53 | name: 'unit test' 54 | }).then((res) => { 55 | tap.strictEqual(res.status, 201) 56 | tap.is(res.data.name, 'unit test') 57 | testId = res.data._id 58 | tap.end() 59 | }).catch((err) => { 60 | throw err 61 | }) 62 | }) 63 | 64 | tap.test('Test get by id route', (tap) => { 65 | axios.get('http://localhost:3000/api/products/' + testId).then((res) => { 66 | tap.strictEqual(res.status, 200) 67 | tap.is(res.data.name, 'unit test') 68 | tap.end() 69 | }).catch((err) => { 70 | throw err 71 | }) 72 | }) 73 | 74 | tap.test('Test put route', (tap) => { 75 | axios.put('http://localhost:3000/api/products/' + testId, { 76 | name: 'unit test 2' 77 | }).then((res) => { 78 | tap.strictEqual(res.status, 200) 79 | tap.is(res.data.name, 'unit test 2') 80 | tap.end() 81 | }).catch((err) => { 82 | throw err 83 | }) 84 | }) 85 | 86 | tap.test('Test get by id route after put', (tap) => { 87 | axios.get('http://localhost:3000/api/products/' + testId).then((res) => { 88 | tap.strictEqual(res.status, 200) 89 | tap.is(res.data.name, 'unit test 2') 90 | tap.end() 91 | }).catch((err) => { 92 | throw err 93 | }) 94 | }) 95 | 96 | tap.test('Test delete route', (tap) => { 97 | axios.delete('http://localhost:3000/api/products/' + testId).then((res) => { 98 | tap.strictEqual(res.status, 204) 99 | tap.end() 100 | }).catch((err) => { 101 | throw err 102 | }) 103 | }) 104 | 105 | tap.test('Test error get by id route', (tap) => { 106 | axios.get('http://localhost:3000/api/products/wrong_id').then((res) => { 107 | tap.end() 108 | }).catch((err) => { 109 | tap.strictEqual(err.response.status, 500) 110 | tap.end() 111 | }) 112 | }) 113 | 114 | tap.test('Test error post route', (tap) => { 115 | axios.post('http://localhost:3000/api/products', { 116 | _id: 'unit test error' 117 | }).then((res) => { 118 | tap.end() 119 | }).catch((err) => { 120 | tap.strictEqual(err.response.status, 500) 121 | tap.end() 122 | }) 123 | }) 124 | 125 | tap.test('Test error put route', (tap) => { 126 | axios.put('http://localhost:3000/api/products/wrong_id', { 127 | name: 'unit test 2' 128 | }).then((res) => { 129 | tap.end() 130 | }).catch((err) => { 131 | tap.strictEqual(err.response.status, 500) 132 | tap.end() 133 | }) 134 | }) 135 | 136 | tap.test('Test error delete route', (tap) => { 137 | axios.delete('http://localhost:3000/api/products/wrong_id').then((res) => { 138 | tap.end() 139 | }).catch((err) => { 140 | tap.strictEqual(err.response.status, 500) 141 | tap.end() 142 | }) 143 | }) 144 | 145 | tap.end() 146 | }) 147 | }) 148 | --------------------------------------------------------------------------------