Skip to content

Lesson 1 #52

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
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
27 changes: 21 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -2,8 +2,9 @@
> Scott Moss & Frontend Masters
- [Resources](#resources)
- [Requirements](#requirements)
- [Course](#course)
- [Excercises](#excercises)
- [Exercises](#exercises)
- [Hello world Express](#hello-world-express)
- [Routing](#routing)
- [Create Schemas](#create-schemas)
@@ -12,15 +13,29 @@
- [Testing](#testing)

## Resources
* [Slides](https://slides.com/scotups/api-design-in-node-with-express-v3/fullscreen)
* [Slides](https://slides.com/scotups/api-design-in-node-with-express-v3)
* [Nodejs](https://nodejs.org/en/)
* [Express](https://expressjs.com/)
* [MongoDB](https://www.mongodb.com/)

## Suggested Tools
The following are suggested to be installed on your machine before beginning the course
* [VSCode](https://code.visualstudio.com/)
* [Nodejs](https://nodejs.org/en/)
* [Yarn](https://yarnpkg.com/lang/en/docs/install/)
* [MongoDB](https://docs.mongodb.com/manual/administration/install-community/)
* [Insomnia](https://insomnia.rest/)
* [Nodemon](https://nodemon.io/)

## Course
This course has two parts, slides and excercises. The slides describe the excerices in detail. Each excercise has a starting branch and solution branch. Example `lesson-1` and `lesson-1-solution`.
## Excercises
This course has two parts, slides and exercises. The slides describe the exerices in detail. Each exercise has a starting branch and solution branch. Example `lesson-1` and `lesson-1-solution`.
## Exercises
** Important: Please check out to each exercise branch as the course progresses.

### Hello world Express

Note: Before running the tests for the exercise, ensure that mongoDB is running. Please run `mongo` in a new terminal.

* branch - `lesson-1`

In this lesson you'll be creating a simple Express based API in node, just to get your feet wet.
@@ -41,7 +56,7 @@ This exercise will have you creating routes and sub routers for our soon the be

### Create Schemas
* branch - `lesson-3`
* test command - `yarn test-model` or `npm run test-model`
* test command - `yarn test-models` or `npm run test-models`

In this exercise, you'll be taking what you learned about Mongoose and MongoDb to create a schema and model for the Item resource.

@@ -73,4 +88,4 @@ In this exercise you'll be locking down our API using JWT's.
- [ ] ensure all tests pass by running test command

### Testing
THe other resources don't have any test, go ahead and write some!
The other resources don't have any test, go ahead and write some!
9,381 changes: 9,381 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

17 changes: 9 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
@@ -6,17 +6,17 @@
"license": "MIT",
"scripts": {
"build": "babel src --out-dir dist",
"test": "NODE_ENV=testing jest --forceExit --detectOpenHandles --silent",
"test-routes": "yarn test -t router",
"test-models": "yarn test -t model",
"test-controllers": "yarn test -t controllers",
"test-auth": "yarn test -t Authentication:",
"dev": "nodemon --exec yarn restart",
"restart": "rimraf dist && yarn build && yarn start",
"test": "cross-env NODE_ENV=testing jest --forceExit --detectOpenHandles --silent",
"test-routes": "npm run test -t router",
"test-models": "npm run test -t model",
"test-controllers": "npm run test -t controllers",
"test-auth": "npm run test -t Authentication:",
"dev": "nodemon --exec npm run restart",
"restart": "rimraf dist && npm run build && npm run start",
"start": "node dist/index.js"
},
"dependencies": {
"bcrypt": "^3.0.2",
"bcryptjs": "^2.4.3",
"body-parser": "^1.18.3",
"cors": "^2.8.5",
"cuid": "^2.1.4",
@@ -37,6 +37,7 @@
"babel-core": "7.0.0-bridge.0",
"babel-eslint": "^8.2.1",
"babel-jest": "^23.4.2",
"cross-env": "^7.0.2",
"eslint": "^4.15.0",
"eslint-config-prettier": "^2.9.0",
"eslint-config-standard": "^11.0.0",
2 changes: 1 addition & 1 deletion src/__tests__/server.spec.js
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ import { User } from '../resources/user/user.model'
import { newToken } from '../utils/auth'
import mongoose from 'mongoose'

describe('server', () => {
describe('API Authentication:', () => {
let token
beforeEach(async () => {
const user = await User.create({ email: 'a@a.com', password: 'hello' })
5 changes: 1 addition & 4 deletions src/resources/item/item.controllers.js
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
import { crudControllers } from '../../utils/crud'
import { Item } from './item.model'

export default crudControllers(Item)
export default {}
33 changes: 1 addition & 32 deletions src/resources/item/item.model.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,4 @@
import mongoose from 'mongoose'

const itemSchema = new mongoose.Schema(
{
name: {
type: String,
required: true,
trim: true,
maxlength: 50
},
status: {
type: String,
required: true,
enum: ['active', 'complete', 'pastdue'],
default: 'active'
},
notes: String,
due: Date,
createdBy: {
type: mongoose.SchemaTypes.ObjectId,
ref: 'user',
required: true
},
list: {
type: mongoose.SchemaTypes.ObjectId,
ref: 'list',
required: true
}
},
{ timestamps: true }
)

itemSchema.index({ list: 1, name: 1 }, { unique: true })

const itemSchema = new mongoose.Schema({}, { timestamps: true })
export const Item = mongoose.model('item', itemSchema)
14 changes: 0 additions & 14 deletions src/resources/item/item.router.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,5 @@
import { Router } from 'express'
import controllers from './item.controllers'

const router = Router()

// /api/item
router
.route('/')
.get(controllers.getOne)
.post(controllers.createOne)

// /api/item/:id
router
.route('/:id')
.get(controllers.getOne)
.put(controllers.updateOne)
.delete(controllers.removeOne)

export default router
2 changes: 1 addition & 1 deletion src/resources/user/user.model.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import mongoose from 'mongoose'
import bcrypt from 'bcrypt'
import bcrypt from 'bcryptjs'

const userSchema = new mongoose.Schema(
{
25 changes: 1 addition & 24 deletions src/server.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import express from 'express'
import { json, urlencoded } from 'body-parser'
import morgan from 'morgan'
import config from './config'
import cors from 'cors'
import { signup, signin, protect } from './utils/auth'
import { connect } from './utils/db'
import userRouter from './resources/user/user.router'
import itemRouter from './resources/item/item.router'
import listRouter from './resources/list/list.router'

export const app = express()

@@ -18,21 +12,4 @@ app.use(json())
app.use(urlencoded({ extended: true }))
app.use(morgan('dev'))

app.post('/signup', signup)
app.post('/signin', signin)

app.use('/api', protect)
app.use('/api/user', userRouter)
app.use('/api/item', itemRouter)
app.use('/api/list', listRouter)

export const start = async () => {
try {
await connect()
app.listen(config.port, () => {
console.log(`REST API on http://localhost:${config.port}/api`)
})
} catch (e) {
console.error(e)
}
}
export const start = () => {}
2 changes: 2 additions & 0 deletions src/utils/__tests__/crud.spec.js
Original file line number Diff line number Diff line change
@@ -95,6 +95,7 @@ describe('crud controllers', () => {

describe('createOne', () => {
test('creates a new doc', async () => {
expect.assertions(2)
const user = mongoose.Types.ObjectId()
const body = { name: 'name' }

@@ -117,6 +118,7 @@ describe('crud controllers', () => {
})

test('createdBy should be the authenticated user', async () => {
expect.assertions(2)
const user = mongoose.Types.ObjectId()
const body = { name: 'name' }

68 changes: 2 additions & 66 deletions src/utils/auth.js
Original file line number Diff line number Diff line change
@@ -16,74 +16,10 @@ export const verifyToken = token =>
})
})

export const signup = async (req, res) => {
if (!req.body.email || !req.body.password) {
return res.status(400).send({ message: 'need email and password' })
}
export const signup = async (req, res) => {}

try {
const user = await User.create(req.body)
const token = newToken(user)
return res.status(201).send({ token })
} catch (e) {
return res.status(500).end()
}
}

export const signin = async (req, res) => {
if (!req.body.email || !req.body.password) {
return res.status(400).send({ message: 'need email and password' })
}

const invalid = { message: 'Invalid email and passoword combination' }

try {
const user = await User.findOne({ email: req.body.email })
.select('email password')
.exec()

if (!user) {
return res.status(401).send(invalid)
}

const match = await user.checkPassword(req.body.password)

if (!match) {
return res.status(401).send(invalid)
}

const token = newToken(user)
return res.status(201).send({ token })
} catch (e) {
console.error(e)
res.status(500).end()
}
}
export const signin = async (req, res) => {}

export const protect = async (req, res, next) => {
const bearer = req.headers.authorization

if (!bearer || !bearer.startsWith('Bearer ')) {
return res.status(401).end()
}

const token = bearer.split('Bearer ')[1].trim()
let payload
try {
payload = await verifyToken(token)
} catch (e) {
return res.status(401).end()
}

const user = await User.findById(payload.id)
.select('-password')
.lean()
.exec()

if (!user) {
return res.status(401).end()
}

req.user = user
next()
}
86 changes: 5 additions & 81 deletions src/utils/crud.js
Original file line number Diff line number Diff line change
@@ -1,88 +1,12 @@
export const getOne = model => async (req, res) => {
try {
const doc = await model
.findOne({ createdBy: req.user._id, _id: req.params.id })
.lean()
.exec()
export const getOne = model => async (req, res) => {}

if (!doc) {
return res.status(400).end()
}
export const getMany = model => async (req, res) => {}

res.status(200).json({ data: doc })
} catch (e) {
console.error(e)
res.status(400).end()
}
}
export const createOne = model => async (req, res) => {}

export const getMany = model => async (req, res) => {
try {
const docs = await model
.find({ createdBy: req.user._id })
.lean()
.exec()
export const updateOne = model => async (req, res) => {}

res.status(200).json({ data: docs })
} catch (e) {
console.error(e)
res.status(400).end()
}
}

export const createOne = model => async (req, res) => {
const createdBy = req.user._id
try {
const doc = await model.create({ ...req.body, createdBy })
res.status(201).json({ data: doc })
} catch (e) {
console.error(e)
res.status(400).end()
}
}

export const updateOne = model => async (req, res) => {
try {
const updatedDoc = await model
.findOneAndUpdate(
{
createdBy: req.user._id,
_id: req.params.id
},
req.body,
{ new: true }
)
.lean()
.exec()

if (!updatedDoc) {
return res.status(400).end()
}

res.status(200).json({ data: updatedDoc })
} catch (e) {
console.error(e)
res.status(400).end()
}
}

export const removeOne = model => async (req, res) => {
try {
const removed = await model.findOneAndRemove({
createdBy: req.user._id,
_id: req.params.id
})

if (!removed) {
return res.status(400).end()
}

return res.status(200).json({ data: removed })
} catch (e) {
console.error(e)
res.status(400).end()
}
}
export const removeOne = model => async (req, res) => {}

export const crudControllers = model => ({
removeOne: removeOne(model),
1,705 changes: 1,257 additions & 448 deletions yarn.lock

Large diffs are not rendered by default.