Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module.exports = {
"env": {
"commonjs": true,
"es2021": true,
"node": true
},
"extends": "eslint:recommended",
"overrides": [
{
"env": {
"node": true
},
"files": [
".eslintrc.{js,cjs}"
],
"parserOptions": {
"sourceType": "script"
}
}
],
"parserOptions": {
"ecmaVersion": "latest"
},
"rules": {
}
}
89 changes: 89 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Mac cruft
.DS_Store

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# TypeScript v1 declaration files
typings/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env
.env.test

# parcel-bundler cache (https://parceljs.org/)
.cache

# next.js build output
.next

# nuxt.js build output
.nuxt

# vuepress build output
.vuepress/dist

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# VSCode debugging
.vscode/launch.json

# databases
*.db3
26 changes: 13 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@ For this project you will create a RESTful API using Node and Express, containin

Here is a checklist of tasks to help you put your project together:

- Generate a `.gitignore` file.
- Install express, [email protected], sqlite3 as plain dependencies.
- Alternatively install express, knex, @vscode/sqlite3 as plain dependencies.
- Install jest, eslint, nodemon, supertest, cross-env as dev-dependencies.
- Configure jest and eslint using `npx <libname> --init`.
- Create a `knexfile.js` with "development" and "testing" configurations.
- Create a `db-config.js` file that selects the correct configuration using the value of `process.env.NODE_ENV`.
- Create migration and seed files.
- Put together "start", "server", "rollback", "migrate" and "seed" scripts in your `package.json`.
- Create a "test" script in your `package.json` using cross-env to inject a `NODE_ENV` of "testing".
- Create a basic express application with a few database access functions and a few endpoints.
- Test your endpoints manually using Postman, HTTPie or similar.
- Test your endpoints with supertest.
x - Generate a `.gitignore` file.
x - Install express, [email protected], sqlite3 as plain dependencies.
o - Alternatively install express, knex, @vscode/sqlite3 as plain dependencies.
x - Install jest, eslint, nodemon, supertest, cross-env as dev-dependencies.
x - Configure jest and eslint using `npx <libname> --init`.
x - Create a `knexfile.js` with "development" and "testing" configurations.
x - Create a `db-config.js` file that selects the correct configuration using the value of `process.env.NODE_ENV`.
x - Create migration and seed files.
x - Put together "start", "server", "rollback", "migrate" and "seed" scripts in your `package.json`.
x - Create a "test" script in your `package.json` using cross-env to inject a `NODE_ENV` of "testing".
x - Create a basic express application with a few database access functions and a few endpoints.
x - Test your endpoints manually using Postman, HTTPie or similar.
x - Test your endpoints with supertest.
37 changes: 37 additions & 0 deletions api/dogs/dogs-middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const db = require('../../data/db-config')

function checkDogObjectComplete(req, res, next) {
const {breed, countryOrigin, avgWeightPounds} = req.body
if(!breed || !countryOrigin || !avgWeightPounds) {
next({status: 400, message: 'new dogs require unique breed, country of origin, and average weight'})
} else {
req.dog = {
breed,
countryOrigin,
avgWeightPounds
}
next()
}
}
async function checkBreedUnique(req, res, next) {
const { breed } = req.body
const dog = await db('dogs').where('breed', breed).first()
if(dog === undefined) {
next()
} else {
next({status: 400, message: 'dog breed must be unique'})
}
}
async function checkIdValid(req, res, next) {
const dog = await db('dogs').where('id', req.params.id).first()
if(!dog) {
next({status: 404, message: `dog with id ${req.params.id} not found`})
} else {
next()
}
}
module.exports = {
checkBreedUnique,
checkDogObjectComplete,
checkIdValid
}
28 changes: 28 additions & 0 deletions api/dogs/dogs-model.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const db = require('../../data/db-config')

function getAll() {
return db('dogs')
}
function getById(id) {
return db('dogs').where('id', id).first()
}
async function add(dog) {
const [dogId] = await db('dogs').insert(dog)
return getById(dogId)
}
async function remove(id) {
const dog = await getById(id)
const deleted = await db('dogs').where('id', id).del()
return dog
}
async function update(id, changes) {
const updateDog = await db('dogs').where('id', id).update(changes)
return getById(updateDog)
}
module.exports = {
getAll,
getById,
remove,
add,
update
}
41 changes: 41 additions & 0 deletions api/dogs/dogs-router.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const Dogs = require('./dogs-model')
const { checkBreedUnique, checkDogObjectComplete, checkIdValid} = require('./dogs-middleware')

const express = require('express')
const router = express.Router()

router.get('/', (req, res, next) => {
Dogs.getAll()
.then(dogs => {
res.status(200).json(dogs)
})
.catch(next)
})
router.get('/:id', (req, res, next) => {
Dogs.getById(req.params.id)
.then(dog => {
res.status(200).json(dog)
})
.catch(next)
})
router.delete('/:id', checkIdValid, (req, res, next) => {
Dogs.remove(req.params.id)
.then(dog => {
res.status(200).json(dog)
})
.catch(next)
})
router.post('/', checkDogObjectComplete, checkBreedUnique, (req, res, next) => {
Dogs.add(req.dog)
.then(dog => {
res.status(201).json(dog)
})
.catch(next)
})
router.put('/:id', checkIdValid, checkDogObjectComplete, checkBreedUnique, (req, res, next) => {
Dogs.update(req.params.id, req.body)
.then(dog => {
res.status(200).json(dog)
})
})
module.exports = router
18 changes: 18 additions & 0 deletions api/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const express = require('express')
const dogsRouter = require('./dogs/dogs-router')

const server = express()

server.use(express.json())
server.use('/api/dogs', dogsRouter)

server.use((err, req, res, next) => { // eslint-disable-line
res.status(err.status || 500).json({
message: err.message,
stack: err.stack,
})
})
server.get('/', (req, res) => {
res.status(200).json({api: 'running'})
})
module.exports = server
100 changes: 100 additions & 0 deletions api/server.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
const db = require('../data/db-config')
const request = require('supertest')
const server = require('./server')

beforeAll(async () => {
await db.migrate.rollback()
await db.migrate.latest()
})
beforeEach(async () => {
await db.seed.run()
})
describe('sanity', () => {
test('using testing environment', () => {
expect(process.env.NODE_ENV).toBe('testing')
})
test('[Get] to index returns api running', async () => {
const result = await request(server).get('/')
expect(result.body).toMatchObject({api: 'running'})
})
})
describe('[GET] /dogs', () => {
test('[GET] returns all dogs', async () => {
const res = await request(server).get('/api/dogs')
expect(res.body).toHaveLength(3)
expect(res.body[0].breed).toBe('mastiff')
})
})
describe('[GET] getById', () => {
test('getById/1 returns correct dog object', async () => {
const res = await request(server).get('/api/dogs/1')
expect(res.body).toMatchObject({breed: 'mastiff'})
})
test('getById/2 returns correct dog object', async () => {
const res = await request(server).get('/api/dogs/2')
expect(res.body).toMatchObject({breed: 'labrador'})
})
test('getById/3 returns correct dog object', async () => {
const res = await request(server).get('/api/dogs/3')
expect(res.body).toMatchObject({breed: 'komondor'})
})
})
describe('[DELETE]', () => {
test('delete function removes dog from database', async () => {
const deleted = await request(server).delete('/api/dogs/1')
const res = await request(server).get('/api/dogs')
expect(res.body).toHaveLength(2)
})
test('delete function returns deleted dog', async () => {
const res = await request(server).delete('/api/dogs/1')
expect(res.body).toMatchObject({breed: 'mastiff'})
})
})
describe('[POST]', () => {
test('adds new dog to database', async () => {
const newDog = {breed: 'chihuahua', countryOrigin: 'Mexico', avgWeightPounds: 5}
const res = await request(server).post('/api/dogs').send(newDog)
expect(res.body).toMatchObject({breed: 'chihuahua'})
expect(await db('dogs')).toHaveLength(4)
})
})
describe('checkIdValid middleware', () => {
test('checkIdValid returns correct error message on bad id', async () => {
const res = await request(server).delete('/api/dogs/4')
expect(res.body.message).toContain('dog with id 4 not found')
})
})
describe('checkDogObjectComplete middleware', () => {
const dog1 = {breed: "", countryOrigin: "Denmark", avgWeightPounds: 68}
const dog2 = {breed: "Great Dane", countryOrigin: "", avgWeightPounds: 150}
const dog3 = {breed: "Great Dane", avgWeightPounds: 90}
test('checkDogObjectComplete returns correct error on missing breed', async () => {
const res = await request(server).post('/api/dogs').send(dog1)
expect(res.body.message).toMatch(/new dogs require unique breed/i)
})
test('checkDogObjectComplete returns correct error on missing country', async () => {
const res = await request(server).post('/api/dogs').send(dog2)
expect(res.body.message).toMatch(/new dogs require unique breed/i)
})
test('checkDogObjectComplete returns correct error in incomplete object', async () => {
const res = await request(server).post('/api/dogs').send(dog3)
expect(res.body.message).toMatch(/new dogs require unique breed/i)
})
})
describe('checkBreedUnique middleware', () => {
const dog = {breed: "Great Dane", countryOrigin: "Germany", avgWeightPounds: 150}
test('checkBreedUnique throws an error when posting a dog with an existing breed', async () => {
const insert = await request(server).post('/api/dogs').send(dog) // creating dog
const res = await request(server).post('/api/dogs').send(dog) // trying to repost dog
expect(res.body.message).toMatch(/dog breed must be unique/i)
})
})
describe('[PUT]', () => {
const dog = {breed: "Great Dane", countryOrigin: "Germany", avgWeightPounds: 150}
test('put method updates dog in the database', async () => {
const res = await request(server).put('/api/dogs/1').send(dog)
expect(res.body).toMatchObject({breed: "Great Dane"})
const updatedDog = await request(server).get('/api/dogs/1')
expect(updatedDog.body).toMatchObject({breed: "Great Dane"})
})
})
7 changes: 7 additions & 0 deletions data/db-config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const knex = require('knex')

const config = require('../knexfile')

const environment = process.env.NODE_ENV || 'testing'

module.exports = knex(config[environment])
Loading