├── .babelrc ├── .env ├── .eslintrc.js ├── .gitignore ├── .sequelizerc ├── .travis.yml ├── README.md ├── api ├── index.js ├── server │ ├── controllers │ │ └── BookController.js │ ├── routes │ │ └── BookRoutes.js │ ├── services │ │ └── BookService.js │ ├── src │ │ ├── config │ │ │ └── config.js │ │ ├── migrations │ │ │ └── 20190418152459-create-book.js │ │ └── models │ │ │ ├── book.js │ │ │ └── index.js │ └── utils │ │ └── Utils.js └── test │ └── test.js ├── package-lock.json └── package.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"], 3 | "plugins": [["@babel/transform-runtime"]] 4 | } -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | DB_NAME=books 2 | DB_USER=steven 3 | DB_PASS= 4 | DB_PORT=5432 5 | DB_HOST=127.0.0.1 6 | SECRET_KEY=any_thing 7 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extends": "airbnb-base", 3 | 4 | "rules": { 5 | "no-console": 0, 6 | "no-param-reassign": [2, {"props": false}], 7 | "prefer-destructuring": 0, 8 | "treatUndefinedAsUnspecified": true, 9 | "arrow-body-style": 0, 10 | "comma-dangle": 0, 11 | }, 12 | 13 | "env": { 14 | "commonjs": true, 15 | "node": true, 16 | "mocha": true 17 | }, 18 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /.nyc_output -------------------------------------------------------------------------------- /.sequelizerc: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | "config": path.resolve('./api/server/src/config', 'config.js'), 5 | "models-path": path.resolve('./api/server/src/models'), 6 | "seeders-path": path.resolve('./api/server/src/seeders'), 7 | "migrations-path": path.resolve('./api/server/src/migrations') 8 | }; 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "stable" 4 | cache: 5 | directories: 6 | - "node_modules" 7 | install: 8 | - npm install 9 | services: 10 | - postgresql 11 | 12 | env: 13 | global: 14 | - NODE_ENV=test 15 | 16 | before_script: 17 | - psql -c 'create database book_test;' -U postgres 18 | - psql -c "CREATE USER steven WITH PASSWORD null;" -U postgres 19 | - npm run build 20 | - npm install -g sequelize-cli 21 | - sequelize db:migrate 22 | script: 23 | - npm test 24 | after_success: 25 | - npm run coverage 26 | 27 | 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Book Application 3 | 4 | [![Build Status](https://travis-ci.org/victorsteven/Book-app-NodeJS-PostgreSQL-Travis-Coveralls-Code-Climate.svg?branch=master)](https://travis-ci.org/victorsteven/Book-app-NodeJS-PostgreSQL-Travis-Coveralls-Code-Climate) [![Coverage Status](https://coveralls.io/repos/github/victorsteven/Book-app-NodeJS-PostgreSQL-Travis-Coveralls-Code-Climate/badge.svg?branch=master)](https://coveralls.io/github/victorsteven/Book-app-NodeJS-PostgreSQL-Travis-Coveralls-Code-Climate?branch=master) [![Maintainability](https://api.codeclimate.com/v1/badges/750e4ce5c8a8112eec3a/maintainability)](https://codeclimate.com/github/victorsteven/Book-app-NodeJS-PostgreSQL-Travis-Coveralls-Code-Climate/maintainability) 5 | 6 | > A Simple Book RestAPI 7 | 8 | ## Getting Started 9 | 10 | > [[Technologies](#technologies-used) · [Testing Tools](#testing-tools) · [Installations](#installations) · [API Endpoints](#api-endpoints) · [Tests](#tests) · [Author](#author) 11 | 12 | 13 | ## Technologies Used 14 | 15 | [node]: (https://nodejs.org) 16 | 17 | - [Node.js](node) 18 | - [postgreSQL](node) 19 | - [Express.js](https://expressjs.com). 20 | - [ESLint](https://eslint.org/). 21 | - [Airbnb](https://www.npmjs.com/package/eslint-config-airbnb). 22 | 23 | ## Testing Tools 24 | 25 | - [Mocha](https://mochajs.org/). 26 | - [Chai](https://chaijs.com). 27 | 28 | ## Installations 29 | 30 | #### Getting started 31 | 32 | - You need to have Node and NPM installed on your computer. 33 | - Installing [Node](node) automatically comes with npm. 34 | 35 | #### Clone 36 | 37 | - Clone this project to your local machine `git@github.com:victorsteven/Book-app-NodeJS-PostgreSQL-Travis-Coveralls-Code-Climate.git` 38 | 39 | #### Setup 40 | 41 | - Installing the project dependencies 42 | > Run the command below 43 | ```shell 44 | $ npm install 45 | ``` 46 | - Start your node server 47 | > run the command below 48 | ```shell 49 | $ npm run dev 50 | ``` 51 | - Use `http://localhost:8000` as base url for endpoints 52 | 53 | ## API Endpoints 54 | 55 | | METHOD | DESCRIPTION | ENDPOINTS | 56 | | ------ | --------------------------------------- | ------------------------- | 57 | | POST | Add a book | `/api/v1/books` | 58 | | GET | Get all the book | `/api/v1/books` | 59 | | PUT | Update the details of a book | `/api/v1/books/:bookId` | 60 | | GET | Get a book particular book | `/api/v1/books/:bookId` | 61 | | DELETE | Remove a book | `/api/v1/books/:bookId` | 62 | 63 | 64 | ## Tests 65 | 66 | - Run test for all endpoints 67 | > run the command below 68 | ```shell 69 | $ npm run test 70 | ``` 71 | 72 | 73 | ## Author 74 | 75 | - Steven Victor 76 | medium: medium.com/@victorsteven 77 | twitter: twitter.com/stevensunflash 78 | linkedin: linkedin.com/in/stevenchikodi -------------------------------------------------------------------------------- /api/index.js: -------------------------------------------------------------------------------- 1 | import config from 'dotenv'; 2 | import express from 'express'; 3 | import bodyParser from 'body-parser'; 4 | import bookRoutes from './server/routes/BookRoutes'; 5 | 6 | config.config(); 7 | 8 | const app = express(); 9 | 10 | app.use(bodyParser.json()); 11 | app.use(bodyParser.urlencoded({ extended: false })); 12 | 13 | const port = process.env.PORT || 8000; 14 | 15 | app.use('/api/v1/books', bookRoutes); 16 | 17 | // when a random route is inputed 18 | app.get('*', (req, res) => res.status(200).send({ 19 | message: 'Welcome to this API.', 20 | })); 21 | 22 | app.listen(port, () => { 23 | console.log(`Server is running on PORT ${port}`); 24 | }); 25 | 26 | export default app; 27 | -------------------------------------------------------------------------------- /api/server/controllers/BookController.js: -------------------------------------------------------------------------------- 1 | import BookService from '../services/BookService'; 2 | import Util from '../utils/Utils'; 3 | 4 | const util = new Util(); 5 | 6 | class BookController { 7 | static async getAllBooks(req, res) { 8 | try { 9 | const allBooks = await BookService.getAllBooks(); 10 | if (allBooks.length > 0) { 11 | util.setSuccess(200, 'Books retrieved', allBooks); 12 | } else { 13 | util.setSuccess(200, 'No book found'); 14 | } 15 | return util.send(res); 16 | } catch (error) { 17 | util.setError(400, error); 18 | return util.send(res); 19 | } 20 | } 21 | 22 | static async addBook(req, res) { 23 | if (!req.body.title || !req.body.price || !req.body.description) { 24 | util.setError(400, 'Please provide complete details'); 25 | return util.send(res); 26 | } 27 | const newBook = req.body; 28 | try { 29 | const createdBook = await BookService.addBook(newBook); 30 | util.setSuccess(201, 'Book Added!', createdBook); 31 | return util.send(res); 32 | } catch (error) { 33 | util.setError(400, error.message); 34 | return util.send(res); 35 | } 36 | } 37 | 38 | static async updatedBook(req, res) { 39 | const alteredBook = req.body; 40 | const { id } = req.params; 41 | if (!Number(id)) { 42 | util.setError(400, 'Please input a valid numeric value'); 43 | return util.send(res); 44 | } 45 | try { 46 | const updateBook = await BookService.updateBook(id, alteredBook); 47 | if (!updateBook) { 48 | util.setError(404, `Cannot find book with the id: ${id}`); 49 | } else { 50 | util.setSuccess(200, 'Book updated', updateBook); 51 | } 52 | return util.send(res); 53 | } catch (error) { 54 | util.setError(404, error); 55 | return util.send(res); 56 | } 57 | } 58 | 59 | static async getABook(req, res) { 60 | const { id } = req.params; 61 | 62 | if (!Number(id)) { 63 | util.setError(400, 'Please input a valid numeric value'); 64 | return util.send(res); 65 | } 66 | 67 | try { 68 | const theBook = await BookService.getABook(id); 69 | 70 | if (!theBook) { 71 | util.setError(404, `Cannot find book with the id ${id}`); 72 | } else { 73 | util.setSuccess(200, 'Found Book', theBook); 74 | } 75 | return util.send(res); 76 | } catch (error) { 77 | util.setError(404, error); 78 | return util.send(res); 79 | } 80 | } 81 | 82 | static async deleteBook(req, res) { 83 | const { id } = req.params; 84 | 85 | if (!Number(id)) { 86 | util.setError(400, 'Please provide a numeric value'); 87 | return util.send(res); 88 | } 89 | 90 | try { 91 | const bookToDelete = await BookService.deleteBook(id); 92 | 93 | if (bookToDelete) { 94 | util.setSuccess(200, 'Book deleted'); 95 | } else { 96 | util.setError(404, `Book with the id ${id} cannot be found`); 97 | } 98 | return util.send(res); 99 | } catch (error) { 100 | util.setError(400, error); 101 | return util.send(res); 102 | } 103 | } 104 | } 105 | 106 | export default BookController; 107 | -------------------------------------------------------------------------------- /api/server/routes/BookRoutes.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import BookController from '../controllers/BookController'; 3 | 4 | const router = Router(); 5 | 6 | router.get('/', BookController.getAllBooks); 7 | router.post('/', BookController.addBook); 8 | router.get('/:id', BookController.getABook); 9 | router.put('/:id', BookController.updatedBook); 10 | router.delete('/:id', BookController.deleteBook); 11 | 12 | export default router; 13 | -------------------------------------------------------------------------------- /api/server/services/BookService.js: -------------------------------------------------------------------------------- 1 | import database from '../src/models'; 2 | 3 | class BookService { 4 | static async getAllBooks() { 5 | try { 6 | return await database.Book.findAll(); 7 | } catch (error) { 8 | throw error; 9 | } 10 | } 11 | 12 | static async addBook(newBook) { 13 | try { 14 | return await database.Book.create(newBook); 15 | } catch (error) { 16 | throw error; 17 | } 18 | } 19 | 20 | static async updateBook(id, updateBook) { 21 | try { 22 | const bookToUpdate = await database.Book.findOne({ 23 | where: { id: Number(id) } 24 | }); 25 | 26 | if (bookToUpdate) { 27 | await database.Book.update(updateBook, { where: { id: Number(id) } }); 28 | 29 | return updateBook; 30 | } 31 | return null; 32 | } catch (error) { 33 | throw error; 34 | } 35 | } 36 | 37 | static async getABook(id) { 38 | try { 39 | const theBook = await database.Book.findOne({ 40 | where: { id: Number(id) } 41 | }); 42 | 43 | return theBook; 44 | } catch (error) { 45 | throw error; 46 | } 47 | } 48 | 49 | static async deleteBook(id) { 50 | try { 51 | const bookToDelete = await database.Book.findOne({ where: { id: Number(id) } }); 52 | 53 | if (bookToDelete) { 54 | const deletedBook = await database.Book.destroy({ 55 | where: { id: Number(id) } 56 | }); 57 | return deletedBook; 58 | } 59 | return null; 60 | } catch (error) { 61 | throw error; 62 | } 63 | } 64 | } 65 | 66 | export default BookService; 67 | -------------------------------------------------------------------------------- /api/server/src/config/config.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | 3 | 4 | module.exports = { 5 | 6 | // If using onine database 7 | // development: { 8 | // use_env_variable: 'DATABASE_URL' 9 | // }, 10 | 11 | development: { 12 | database: 'books', 13 | username: 'steven', 14 | password: null, 15 | host: '127.0.0.1', 16 | dialect: 'postgres' 17 | }, 18 | 19 | test: { 20 | database: 'book_test', 21 | username: 'steven', 22 | password: null, 23 | host: '127.0.0.1', 24 | dialect: 'postgres' 25 | }, 26 | 27 | production: { 28 | database: process.env.DB_NAME, 29 | username: process.env.DB_USER, 30 | password: process.env.DB_PASS, 31 | host: process.env.DB_HOST, 32 | dialect: 'postgres' 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /api/server/src/migrations/20190418152459-create-book.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.createTable('Books', { 4 | id: { 5 | allowNull: false, 6 | autoIncrement: true, 7 | primaryKey: true, 8 | type: Sequelize.INTEGER 9 | }, 10 | title: { 11 | type: Sequelize.STRING, 12 | allowNull: false, 13 | }, 14 | price: { 15 | type: Sequelize.STRING, 16 | allowNull: false, 17 | }, 18 | description: { 19 | type: Sequelize.STRING, 20 | allowNull: false, 21 | }, 22 | createdAt: { 23 | allowNull: false, 24 | type: Sequelize.DATE 25 | }, 26 | updatedAt: { 27 | allowNull: false, 28 | type: Sequelize.DATE 29 | } 30 | }); 31 | }, 32 | down: (queryInterface) => { 33 | return queryInterface.dropTable('Books'); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /api/server/src/models/book.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const Book = sequelize.define('Book', { 3 | title: { 4 | type: DataTypes.STRING, 5 | allowNull: false, 6 | }, 7 | price: { 8 | type: DataTypes.STRING, 9 | allowNull: false, 10 | }, 11 | description: { 12 | type: DataTypes.STRING, 13 | allowNull: false, 14 | }, 15 | }); 16 | return Book; 17 | }; 18 | -------------------------------------------------------------------------------- /api/server/src/models/index.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import Sequelize from 'sequelize'; 4 | import configJson from '../config/config'; 5 | 6 | const basename = path.basename(__filename); 7 | const env = process.env.NODE_ENV ? process.env.NODE_ENV : 'development'; 8 | 9 | const config = configJson[env]; 10 | 11 | console.log('this is the environment: ', env); 12 | 13 | const db = {}; 14 | 15 | let sequelize; 16 | if (config.environment === 'production') { 17 | sequelize = new Sequelize(process.env[config.use_env_variable], config); 18 | sequelize = new Sequelize( 19 | process.env.DB_NAME, 20 | process.env.DB_USER, 21 | process.env.DB_PASS, { 22 | host: process.env.DB_HOST, 23 | port: process.env.DB_PORT, 24 | dialect: 'postgres', 25 | dialectOption: { 26 | ssl: true, 27 | native: true 28 | }, 29 | logging: true 30 | } 31 | ); 32 | } else { 33 | sequelize = new Sequelize(config.database, config.username, config.password, config); 34 | } 35 | 36 | fs 37 | .readdirSync(__dirname) 38 | .filter((file) => { 39 | return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js'); 40 | }) 41 | .forEach((file) => { 42 | const model = sequelize.import(path.join(__dirname, file)); 43 | db[model.name] = model; 44 | }); 45 | 46 | Object.keys(db).forEach((modelName) => { 47 | if (db[modelName].associate) { 48 | db[modelName].associate(db); 49 | } 50 | }); 51 | 52 | db.sequelize = sequelize; 53 | db.Sequelize = Sequelize; 54 | 55 | export default db; 56 | -------------------------------------------------------------------------------- /api/server/utils/Utils.js: -------------------------------------------------------------------------------- 1 | export default class Util { 2 | constructor() { 3 | this.statusCode = null; 4 | this.type = null; 5 | this.data = null; 6 | this.message = null; 7 | } 8 | 9 | setSuccess(statusCode, message, data) { 10 | this.statusCode = statusCode; 11 | this.message = message; 12 | this.data = data; 13 | this.type = 'success'; 14 | } 15 | 16 | setError(statusCode, message) { 17 | this.statusCode = statusCode; 18 | this.message = message; 19 | this.type = 'error'; 20 | } 21 | 22 | send(res) { 23 | const result = { 24 | status: this.type, 25 | message: this.message, 26 | data: this.data, 27 | }; 28 | 29 | if (this.type === 'success') { 30 | return res.status(this.statusCode).json(result); 31 | } 32 | return res.status(this.statusCode).json({ 33 | status: this.type, 34 | message: this.message, 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /api/test/test.js: -------------------------------------------------------------------------------- 1 | import chai from 'chai'; 2 | import chatHttp from 'chai-http'; 3 | import 'chai/register-should'; 4 | import app from '../index'; 5 | 6 | chai.use(chatHttp); 7 | const { expect } = chai; 8 | 9 | describe('Testing the book endpoints:', () => { 10 | it('It should create a book', (done) => { 11 | const book = { 12 | title: 'First Awesome book', 13 | price: '$9.99', 14 | description: 'This is the awesome book' 15 | }; 16 | chai.request(app) 17 | .post('/api/v1/books') 18 | .set('Accept', 'application/json') 19 | .send(book) 20 | .end((err, res) => { 21 | expect(res.status).to.equal(201); 22 | expect(res.body.data).to.include({ 23 | id: 1, 24 | title: book.title, 25 | price: book.price, 26 | description: book.description 27 | }); 28 | done(); 29 | }); 30 | }); 31 | 32 | it('It should not create a book with incomplete parameters', (done) => { 33 | const book = { 34 | price: '$9.99', 35 | description: 'This is the awesome book' 36 | }; 37 | chai.request(app) 38 | .post('/api/v1/books') 39 | .set('Accept', 'application/json') 40 | .send(book) 41 | .end((err, res) => { 42 | expect(res.status).to.equal(400); 43 | done(); 44 | }); 45 | }); 46 | 47 | it('It should get all books', (done) => { 48 | chai.request(app) 49 | .get('/api/v1/books') 50 | .set('Accept', 'application/json') 51 | .end((err, res) => { 52 | expect(res.status).to.equal(200); 53 | res.body.data[0].should.have.property('id'); 54 | res.body.data[0].should.have.property('title'); 55 | res.body.data[0].should.have.property('price'); 56 | res.body.data[0].should.have.property('description'); 57 | done(); 58 | }); 59 | }); 60 | 61 | it('It should get a particular book', (done) => { 62 | const bookId = 1; 63 | chai.request(app) 64 | .get(`/api/v1/books/${bookId}`) 65 | .set('Accept', 'application/json') 66 | .end((err, res) => { 67 | expect(res.status).to.equal(200); 68 | res.body.data.should.have.property('id'); 69 | res.body.data.should.have.property('title'); 70 | res.body.data.should.have.property('price'); 71 | res.body.data.should.have.property('description'); 72 | done(); 73 | }); 74 | }); 75 | 76 | it('It should not get a particular book with invalid id', (done) => { 77 | const bookId = 8888; 78 | chai.request(app) 79 | .get(`/api/v1/books/${bookId}`) 80 | .set('Accept', 'application/json') 81 | .end((err, res) => { 82 | expect(res.status).to.equal(404); 83 | res.body.should.have.property('message').eql(`Cannot find book with the id ${bookId}`); 84 | done(); 85 | }); 86 | }); 87 | 88 | it('It should not get a particular book with non-numeric id', (done) => { 89 | const bookId = 'aaa'; 90 | chai.request(app) 91 | .get(`/api/v1/books/${bookId}`) 92 | .set('Accept', 'application/json') 93 | .end((err, res) => { 94 | expect(res.status).to.equal(400); 95 | res.body.should.have.property('message').eql('Please input a valid numeric value'); 96 | done(); 97 | }); 98 | }); 99 | 100 | it('It should update a book', (done) => { 101 | const bookId = 1; 102 | const updatedBook = { 103 | id: bookId, 104 | title: 'Updated Awesome book', 105 | price: '$10.99', 106 | description: 'We have updated the price' 107 | }; 108 | chai.request(app) 109 | .put(`/api/v1/books/${bookId}`) 110 | .set('Accept', 'application/json') 111 | .send(updatedBook) 112 | .end((err, res) => { 113 | expect(res.status).to.equal(200); 114 | expect(res.body.data.id).equal(updatedBook.id); 115 | expect(res.body.data.title).equal(updatedBook.title); 116 | expect(res.body.data.price).equal(updatedBook.price); 117 | expect(res.body.data.description).equal(updatedBook.description); 118 | done(); 119 | }); 120 | }); 121 | 122 | it('It should not update a book with invalid id', (done) => { 123 | const bookId = '9999'; 124 | const updatedBook = { 125 | id: bookId, 126 | title: 'Updated Awesome book again', 127 | price: '$11.99', 128 | description: 'We have updated the price' 129 | }; 130 | chai.request(app) 131 | .put(`/api/v1/books/${bookId}`) 132 | .set('Accept', 'application/json') 133 | .send(updatedBook) 134 | .end((err, res) => { 135 | expect(res.status).to.equal(404); 136 | res.body.should.have.property('message').eql(`Cannot find book with the id: ${bookId}`); 137 | done(); 138 | }); 139 | }); 140 | 141 | it('It should not update a book with non-numeric id value', (done) => { 142 | const bookId = 'ggg'; 143 | const updatedBook = { 144 | id: bookId, 145 | title: 'Updated Awesome book again', 146 | price: '$11.99', 147 | description: 'We have updated the price' 148 | }; 149 | chai.request(app) 150 | .put(`/api/v1/books/${bookId}`) 151 | .set('Accept', 'application/json') 152 | .send(updatedBook) 153 | .end((err, res) => { 154 | expect(res.status).to.equal(400); 155 | res.body.should.have.property('message').eql('Please input a valid numeric value'); 156 | done(); 157 | }); 158 | }); 159 | 160 | 161 | it('It should delete a book', (done) => { 162 | const bookId = 1; 163 | chai.request(app) 164 | .delete(`/api/v1/books/${bookId}`) 165 | .set('Accept', 'application/json') 166 | .end((err, res) => { 167 | expect(res.status).to.equal(200); 168 | expect(res.body.data).to.include({}); 169 | done(); 170 | }); 171 | }); 172 | 173 | it('It should not delete a book with invalid id', (done) => { 174 | const bookId = 777; 175 | chai.request(app) 176 | .delete(`/api/v1/books/${bookId}`) 177 | .set('Accept', 'application/json') 178 | .end((err, res) => { 179 | expect(res.status).to.equal(404); 180 | res.body.should.have.property('message').eql(`Book with the id ${bookId} cannot be found`); 181 | done(); 182 | }); 183 | }); 184 | 185 | it('It should not delete a book with non-numeric id', (done) => { 186 | const bookId = 'bbb'; 187 | chai.request(app) 188 | .delete(`/api/v1/books/${bookId}`) 189 | .set('Accept', 'application/json') 190 | .end((err, res) => { 191 | expect(res.status).to.equal(400); 192 | res.body.should.have.property('message').eql('Please provide a numeric value'); 193 | done(); 194 | }); 195 | }); 196 | }); 197 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "book-app", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "nodemon --exec babel-node ./api/index.js", 8 | "test": "export NODE_ENV=test && sequelize db:migrate:undo:all && sequelize db:migrate && nyc --require @babel/register mocha ./api/test/test.js --timeout 20000 --exit", 9 | "build": "rm -rf ./build && babel -d ./build ./api -s", 10 | "generate-lcov": "nyc report --reporter=text-lcov > lcov.info", 11 | "coveralls-coverage": "coveralls < lcov.info", 12 | "codeclimate-coverage": "codeclimate-test-reporter < lcov.info", 13 | "coverage": "nyc npm test && npm run generate-lcov && npm run coveralls-coverage && npm run codeclimate-coverage" 14 | }, 15 | "keywords": [], 16 | "author": "", 17 | "license": "ISC", 18 | "dependencies": { 19 | "body-parser": "^1.18.3", 20 | "chai": "^4.2.0", 21 | "chai-http": "^4.2.1", 22 | "dotenv": "^7.0.0", 23 | "express": "^4.16.4", 24 | "nodemon": "^1.18.11", 25 | "path": "^0.12.7", 26 | "pg": "^7.10.0", 27 | "pg-hstore": "^2.3.2", 28 | "sequelize": "^5.7.0" 29 | }, 30 | "devDependencies": { 31 | "@babel/cli": "^7.4.3", 32 | "@babel/core": "^7.4.3", 33 | "@babel/node": "^7.2.2", 34 | "@babel/plugin-transform-runtime": "^7.4.3", 35 | "@babel/preset-env": "^7.4.3", 36 | "@babel/register": "^7.4.0", 37 | "@babel/runtime": "^7.4.3", 38 | "babel-loader": "^8.0.5", 39 | "codeclimate-test-reporter": "^0.5.1", 40 | "coveralls": "^3.0.2", 41 | "eslint": "^5.16.0", 42 | "eslint-config-airbnb-base": "^13.1.0", 43 | "eslint-plugin-import": "^2.17.2", 44 | "mocha": "^6.1.4", 45 | "nyc": "^13.3.0" 46 | } 47 | } 48 | --------------------------------------------------------------------------------