├── .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 | [](https://travis-ci.org/paranoiasystem/fastify-autocrud)
6 | [](https://github.com/paranoiasystem/fastify-autocrud/issues)
7 | [](https://coveralls.io/github/paranoiasystem/fastify-autocrud?branch=master)
8 | [](./LICENSE)
9 | 
10 | [](http://standardjs.com/)
11 | [](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 |
--------------------------------------------------------------------------------