Web Infra HW 06 MAK - TheEvergreenStateCollege/upper-division-cs-23-24 GitHub Wiki

page index

Setup

To keep your AWS server up-to-date with new features from our in-class activities, follow our week-by-week, comprehensive AWS Server Setup

Lap 1: Cloud

For Lap 1 this week in Web Infrastructure, we have these goals allow users to authenticate to our REST API with JSON Web Tokens (JWTs) or passwords, and learn best practices for both handle

Readings and Watchings

Read and/or watch these two sections of the API design with NodeJS and Express course. Log into your AWS server and make changes to your API server from Infra HW 03 and HW 02, where you started your Prisma database schema and started making REST handlers for GET and POST methods for the Product / Update example. Section 5: Authentication Section 6: Route & Error Handlers

The entire course for reference, to give context to those sections, let you review previous sections, or look ahead to upcoming sections. https://frontendmasters.com/courses/api-design-nodejs-v4/

Some short notes accompanying the above. https://hendrixer.github.io/API-design-v4/

Code and Submit on GitHub

You should use this assignment to begin work on your final project. It is okay if you are still looking for teammates or only have a rough idea of your project. As you go, you will develop your idea further, attract teammates that are right for you, or grow your confidence as a solo programmer. You can apply the code that you type from the videos and in class, and adapt them for your project in the following directory. You will need to use the cd and mkdir commands to change to correct directory and create the new directory as necessary. <repo_dir>/web-24wi/projects/<your_project_name>

In either case, follow our Git Workflow . If you get stuck outside of class times or office hours, reach out on the class discord.

doing reading and watching

make codespace: https://probable-garbanzo-455q9pv54x52jvqj.github.dev/

https://frontendmasters.com/courses/api-design-nodejs-v4/creating-a-jwt/

JWT = JSON Web Token

install npm i jsonwebtoken bcrypt dotenv

@MKrause-code ➜ /workspaces/codespaces-blank $ npm i jsonwebtoken bcrypt dotenv
npm WARN deprecated [email protected]: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
npm WARN deprecated [email protected]: This package is no longer supported.
npm WARN deprecated [email protected]: This package is no longer supported.
npm WARN deprecated [email protected]: This package is no longer supported.
npm WARN deprecated [email protected]: Rimraf versions prior to v4 are no longer supported
npm WARN deprecated [email protected]: Glob versions prior to v9 are no longer supported

added 72 packages in 5s

5 packages are looking for funding
  run `npm fund` for details
npm notice
npm notice New minor version of npm available! 10.5.2 -> 10.8.1
npm notice Changelog: https://github.com/npm/cli/releases/tag/v10.8.1
npm notice Run npm install -g [email protected] to update!
npm notice

in src make new folder modules and in it make auth.ts

import jwt from 'jsonwebtoken'


export const createJWT = (user) => {
    const token = jwt.sign({id: user.id, username: user.username}, )
}

need secret

go to .env and make secret

JWT_SECRET ="cookies"

need to load .env to use secret add code in index.ts

import * as dotenv from 'dotenv'
dotenv.config()

add secret in auth.ts and return token

import jwt from 'jsonwebtoken'


export const createJWT = (user) => {
    const token = jwt.sign({
            id: user.id,
            username: user.username
        },
        process.env.JWT_SECRET
    )
    return token
}

https://hendrixer.github.io/API-design-v4/lessons/auth/auth-middleware create middleware to block unauthenticated

auth.ts

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

check if bearer

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


    if (!bearer) {
        res.status(401)
        res.json({message: 'not authorized'})
        return
    }


}

protect router in server.ts

app.use('/api', protect, router)

test server

@MKrause-code ➜ /workspaces/codespaces-blank $ npm run dev

> [email protected] dev
> ts-node src/index.ts

hello on http://localhost:3001

thunder client new request GET http://localhost:3001/api/product/

Status: 200 OK Size: 21 Bytes Time: 9 ms

didn't send 401 I think I made a mistype somewhere but can't find it, I'll fix this later

https://hendrixer.github.io/API-design-v4/lessons/auth/auth-middleware

grab the token if there is one split the bearer form token

auth.ts

    const [, token] = bearer.split(' ')

if no token give 401

auth.ts

    if (!token) {
        res.status(401)
        res.json({message: 'no token'})
        return
    }

check if real

    try {
        const user = jwt.verify(token, process.env.JWT_SECRET)
        req.user = user
        next()


    } catch (e) {
        console.error(e)
        res.status(401)
        res.json({message: 'not valid token'})
        return
    }

test server run server npm run dev

thunder client new request GET http://localhost:3001/api/product/

remembered I could open codespace on desktop VS code, so I'm going to be using that now, I has better colors

I have looked over my code multiple times and can't figure out why auth is not working I'm going to keep going

https://hendrixer.github.io/API-design-v4/lessons/auth/auth-middleware getting JSON web token https://hendrixer.github.io/API-design-v4/lessons/auth/users headers are key value, metadata of the request

let user sign up and sign in

https://hendrixer.github.io/API-design-v4/lessons/auth/users

in auth.ts add comparePasswords

import bcrypt from 'bcrypt'


//check if enter password matches saved hashed password
export const comparePasswords = (password, hash) => {
    return bycrypt.compare(password, hash)
}

hash the password

//hashes password
export const hashPassword = (password) => {
    return bcrypt.hash(password, 5) //5 is the salt
}

https://hendrixer.github.io/API-design-v4/lessons/auth/users

new folders and files src

  • db.ts
  • handlers
    • users.ts

db.ts code will be used in other

import { PrismaClient } from '@prisma/client'


const prisma = new PrismaClient()


export default prisma

user.ts code creates a new user

import prisma from "../db"
import { createJWT, hashPassword } from "../modules/auth"


export const createNewUser = async (req, res) => {
    const user = await prisma.user.create({
        data: {
            username: req.body.username,
            password: await hashPassword(req.body.password)
        }
    })


    const token = createJWT(user)
    res.json({ token })
}

https://hendrixer.github.io/API-design-v4/lessons/auth/users adding sign in to user.ts

//signs in
export const signin = async (req, res) => {
    const user = await prisma.user.findUnique({
        where: {
            username: req.body.username //check if username exists
        }
    })


    const isValid = await comparePasswords(req.body.password, user.password)


    if (!isValid) { //wrong password
        res.status(401)
        res.json({message: 'password incorect'})
        return
    }


    //correct password, give acsess
    const token = createJWT(user)
    res.json({ token })
}

https://hendrixer.github.io/API-design-v4/lessons/auth/users

add routes to server.ts

app.post('/user', createNewUser)
app.post('/signin', signin)

fixed some imports

test server

@MKrause-code ➜ /workspaces/codespaces-blank $ npm run dev

> [email protected] dev
> ts-node src/index.ts

auth
hello on http://localhost:3001

thunderclient POST http://localhost:3001/user body -> json

looks different, still need to fix protect

visual way to explore database npx prisma studio

@MKrause-code ➜ /workspaces/codespaces-blank $ npx prisma studio
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Prisma Studio is up on http://localhost:5555

Fixing protect

It looks like I was missing cors, let's add it

added cors to server.ts

import cors from 'cors'

app.user(cors())

installed cors

@MKrause-code ➜ /workspaces/codespaces-blank $ npm install cors

added 1 package, and audited 171 packages in 638ms

16 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

test server

@MKrause-code ➜ /workspaces/codespaces-blank $ npm run dev

> [email protected] dev
> ts-node src/index.ts

auth
/workspaces/codespaces-blank/src/server.ts:10
app.user(cors())
	^
TypeError: app.user is not a function
	at Object.<anonymous> (/workspaces/codespaces-blank/src/server.ts:10:5)
	at Module._compile (node:internal/modules/cjs/loader:1358:14)
	at Module.m._compile (/workspaces/codespaces-blank/node_modules/ts-node/src/index.ts:1618:23)
	at Module._extensions..js (node:internal/modules/cjs/loader:1416:10)
	at Object.require.extensions.<computed> [as .ts] (/workspaces/codespaces-blank/node_modules/ts-node/src/index.ts:1621:12)
	at Module.load (node:internal/modules/cjs/loader:1208:32)
	at Function.Module._load (node:internal/modules/cjs/loader:1024:12)
	at Module.require (node:internal/modules/cjs/loader:1233:19)
	at require (node:internal/modules/helpers:179:18)
	at Object.<anonymous> (/workspaces/codespaces-blank/src/index.ts:4:1)

ops app.user(cors()) is supposed to be app.use(cors()) fixed mistype test server


thunder client test new request http://localhost:3001/api/product/

Status: 401 Unauthorized Size: 28 Bytes Time: 8 ms

{ "message": "not authorized" }

no user, not authorized works

thunderclient POST http://localhost:3001/user body -> json { "username": "name", "password": "pass" }

Status: 200 OK Size: 200 Bytes Time: 347 ms

{ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImIwMGE3ZDA3LTg4NTQtNGRkZi04ZTFiLWI4ZGY2MWMzYTg5NSIsInVzZXJuYW1lIjoibmFtZSIsImlhdCI6MTcxNzM1ODI2Mn0.ztfA58AGhLwc1T3XmtjrVKAtrP-uIJrXKSwoHxIrBMw" }

sign up works

see if in database npx prisma studio

user in database

test beared token in thunderclient GET http://localhost:3001/api/product/ Auth -> Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImIwMGE3ZDA3LTg4NTQtNGRkZi04ZTFiLWI4ZGY2MWMzYTg5NSIsInVzZXJuYW1lIjoibmFtZSIsImlhdCI6MTcxNzM1ODI2Mn0.ztfA58AGhLwc1T3XmtjrVKAtrP-uIJrXKSwoHxIrBMw

Status: 200 OK Size: 2 Bytes Time: 11 ms

bearer token works

test sign in thunderclient POST http://localhost:3001/signin body -> json { "username": "name", "password": "pass" }

Status: 200 OK Size: 200 Bytes Time: 279 ms

{ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImIwMGE3ZDA3LTg4NTQtNGRkZi04ZTFiLWI4ZGY2MWMzYTg5NSIsInVzZXJuYW1lIjoibmFtZSIsImlhdCI6MTcxNzM1ODY2Mn0.Wp6oW8bf1g-pU9HHz_TH6V6JVj5R7K1xhlBEMQSnY0c" }

sign in test works

test sign up with existing username

thunderclient POST http://localhost:3001/user body -> json { "username": "name", "password": "pass" }

Status: ERROR Size: 0 B Time: 0 ms

Invalid `prisma.user.create()` invocation in
/workspaces/codespaces-blank/src/handlers/user.ts:6:24

  3
  4 //creates a new user
  5 export const createNewUser = async (req, res) => {
→ 6 	const user = await prisma.user.create(
Unique constraint failed on the fields: (`username`)
	at In.handleRequestError (/workspaces/codespaces-blank/node_modules/@prisma/client/runtime/library.js:122:6877)
	at In.handleAndLogRequestError (/workspaces/codespaces-blank/node_modules/@prisma/client/runtime/library.js:122:6211)
	at In.request (/workspaces/codespaces-blank/node_modules/@prisma/client/runtime/library.js:122:5919)
	at async l (/workspaces/codespaces-blank/node_modules/@prisma/client/runtime/library.js:127:11167) {
  code: 'P2002',
  clientVersion: '5.14.0',
  meta: { modelName: 'User', target: [ 'username' ] }
}

can't make new account with an existing username right now it crashes the server

https://hendrixer.github.io/API-design-v4/lessons/route-handlers/validating-inputs

never trust the user

install express-validator

@MKrause-code ➜ /workspaces/codespaces-blank $ npm i express-validator --save

added 3 packages, and audited 174 packages in 1s

16 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

use middleware as valedaters for post and put for routes

router.ts

import { body, validationResult } from "express-validator"

router.put('/product/:id', body('name'),(req, res) => {
    const errors = validationResult(req)


    if(!errors.isEmpty()) { //checks for error
        res.status(404)
        res.json({ errors: errors.array() })
    }
})

test it start server

@MKrause-code ➜ /workspaces/codespaces-blank $ npm run dev

> [email protected] dev
> ts-node src/index.ts

auth
hello on http://localhost:3001/

thunder client POST http://localhost:3001/signin body -> json { "username": "name", "password": "pass" }

Status: 200 OK Size: 200 Bytes Time: 324 ms { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImIwMGE3ZDA3LTg4NTQtNGRkZi04ZTFiLWI4ZGY2MWMzYTg5NSIsInVzZXJuYW1lIjoibmFtZSIsImlhdCI6MTcxNzM1OTYwNn0.FPcx7BbcslOyvKdvXaHJBY9YwlDYNC6oUhH-r--o1PE" }

use token to product thunder client PUT http://localhost:3001/api/product/12345 Auth -> bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImIwMGE3ZDA3LTg4NTQtNGRkZi04ZTFiLWI4ZGY2MWMzYTg5NSIsInVzZXJuYW1lIjoibmFtZSIsImlhdCI6MTcxNzM1OTYwNn0.FPcx7BbcslOyvKdvXaHJBY9YwlDYNC6oUhH-r--o1PE

just hangs

add console.log(errors) to router.ts

try again thunder client PUT http://localhost:3001/api/product/12345 Auth -> bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImIwMGE3ZDA3LTg4NTQtNGRkZi04ZTFiLWI4ZGY2MWMzYTg5NSIsInVzZXJuYW1lIjoibmFtZSIsImlhdCI6MTcxNzM1OTYwNn0.FPcx7BbcslOyvKdvXaHJBY9YwlDYNC6oUhH-r--o1PE

Result { formatter: [Function: formatter], errors: [] }

no error, still hangs

update body('name') to body('name').isString() in router.ts

router.put('/product/:id', body('name').isString(),(req, res) => {
    const errors = validationResult(req)
    console.log(errors)


    if(!errors.isEmpty()) { //checks for error
        res.status(404)
        res.json({ errors: errors.array() })
    }
})

thunder client PUT http://localhost:3001/api/product/12345 Auth -> bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImIwMGE3ZDA3LTg4NTQtNGRkZi04ZTFiLWI4ZGY2MWMzYTg5NSIsInVzZXJuYW1lIjoibmFtZSIsImlhdCI6MTcxNzM1OTYwNn0.FPcx7BbcslOyvKdvXaHJBY9YwlDYNC6oUhH-r--o1PE

Status: 404 Not Found Size: 83 Bytes Time: 17 ms

{ "errors": [ { "type": "field", "msg": "Invalid value", "path": "name", "location": "body" } ] }

Result {
  formatter: [Function: formatter],
  errors: [
	{
  	type: 'field',
  	value: undefined,
  	msg: 'Invalid value',
  	path: 'name',
  	location: 'body'
	}
  ]
}
PUT /api/product/12345 404 7.688 ms - 83

https://hendrixer.github.io/API-design-v4/lessons/route-handlers/validating-inputs

do by yourself identify where an input validation needs to exist, (put post) identify what the resource is, so a product or an update, look at schema.prisma create an input validator or many input validators for that route.

middleware comes after the route '/route' and before the handler (req, res)

my change update point also has name string

router.put('/updatepoint/:id', body('name').isString(),(req, res) => {
    const errors = validationResult(req)
    console.log(errors)


    if(!errors.isEmpty()) { //checks for error
        res.status(404)
        res.json({ errors: errors.array() })
    }
})

make error checker into middleware make middleware file

modules middleware.ts

move code to middleware.ts

import { validationResult } from "express-validator"


export const handleInputErrors = (req, res, next) => {
    const errors = validationResult(req)
    //console.log(errors)


    if(!errors.isEmpty()) { //checks for error
        res.status(404)
        res.json({ errors: errors.array() })
    } else {
        next()
    }
}

call handleInputErrors in router.ts

router.put('/product/:id', body('name').isString(), handleInputErrors,(req, res) => {
   
})

check if still works thunder client PUT http://localhost:3001/api/product/12345 Auth -> bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImIwMGE3ZDA3LTg4NTQtNGRkZi04ZTFiLWI4ZGY2MWMzYTg5NSIsInVzZXJuYW1lIjoibmFtZSIsImlhdCI6MTcxNzM1OTYwNn0.FPcx7BbcslOyvKdvXaHJBY9YwlDYNC6oUhH-r--o1PE

Status: 404 Not Found Size: 83 Bytes Time: 13 ms

{ "errors": [ { "type": "field", "msg": "Invalid value", "path": "name", "location": "body" } ] }

still works also change res.status(404) to res.status(400)

adding from schema to others

router.post('/product', body('name').isString(), handleInputErrors,(req, res) => {
})

router.put('/update/:id',
    body('title').optional(),
    body('body').optional(),
    body('status').isIn(['IN_PROGRESS', 'SHIPPED', 'DEPRECATED']),
    body('version').optional(),
    () => {}
)
router.post('/update',
body('title').exists().isString(),
body('body').exists().isString(),
() => {}
)

router.put('/updatepoint/:id',
    body('name').optional().isString(),
    body('description').optional().isString(),
    () => {}
)
router.post('/updatepoint',
    body('name').isString(),
    body('description').isString(),
    body('updateId').exists().isString,
    () => {}
)

https://hendrixer.github.io/API-design-v4/lessons/route-handlers/handlers making handlers

make file for product handlers product.ts

get all the products product.ts

import prisma from "../db"


//get all products
export const getProducts = async (req, res) => {
    const user = await prisma.user.findUnique({
        where: {
            id: req.user.id
        },
        include: {
            products: true
        }
    })


    res.json({data: user.products})
}

get one product

//get one product
export const getOneProduct =  async (req, res) => {
    const id = req.params.id


    const product = await prisma.product.findFirst({
       where: {
        id,
        belongsToId: req.user.id
       }
    })


    res.json({data: product})
}

product.ts

//create a product
export const createProduct = async (req, res) => {
    const product = await prisma.product.create({
        data: {
            name: req.body.name,
            belongsToId: req.user.id
        }
    })


    res.json({data: product})
}


//update a product
export const updateProduct = async (req, res) => {
    const updated = await prisma.product.update({
        where: {
            id: req.params.id
            belongsToId: req.user.id
        },
        data: {
            name: req.body.name
        }
    })


    res.json({data: updated})
}


//delete a product
export const deleteProduct = async (req, res) => {
    const deleted = await prisma.product.delete({
        where: {
            id: req.params.id,
            belongsToId: req.user.id
        }
    })


    res.json({data: deleted})
}

add unique to product model in schema.prisma

  @@unique([id, belongsToId])

need to migrate

@MKrause-code ➜ /workspaces/codespaces-blank $ npx prisma migrate dev
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Datasource "db": PostgreSQL database "mak_database", schema "public" at "dpg-cpbm7jun7f5s73ffehi0-a.oregon-postgres.render.com"


⚠️  Warnings for the current datasource:

  • A unique constraint covering the columns `[id,belongsToId]` on the table `Product` will be added. If there are existing duplicate values, this will fail.

✔ Are you sure you want to create and apply this migration? … yes
✔ Enter a name for the new migration: …
Applying migration `20240602212752_`

The following migration(s) have been created and applied from new schema changes:

migrations/
  └─ 20240602212752_/
	└─ migration.sql

Your database is now in sync with your schema.

✔ Generated Prisma Client (v5.14.0) to ./node_modules/@prisma/client in 80ms

change

id: req.params.id,
            id_belongsToId: req.user.id

to

id_belongsToId: {
                id: req.params.id,
                belongsToId: req.user.id
            }

in updateProduct and deleteProduct

router.ts

router.get('/product', getProducts)

test start server

@MKrause-code ➜ /workspaces/codespaces-blank $ npm run dev

> [email protected] dev
> ts-node src/index.ts

hello on http://localhost:3001/

thunder client GET http://localhost:3001/api/product Auth -> bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImIwMGE3ZDA3LTg4NTQtNGRkZi04ZTFiLWI4ZGY2MWMzYTg5NSIsInVzZXJuYW1lIjoibmFtZSIsImlhdCI6MTcxNzM1OTYwNn0.FPcx7BbcslOyvKdvXaHJBY9YwlDYNC6oUhH-r--o1PE

Status: 200 OK Size: 11 Bytes Time: 351 ms

{ "data": [] }

got all data in the product (which is none)

router.ts

router.post('/product',
    body('name').isString(),
    handleInputErrors,
    createProduct
)

thunder client Post http://localhost:3001/api/product Auth -> bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImIwMGE3ZDA3LTg4NTQtNGRkZi04ZTFiLWI4ZGY2MWMzYTg5NSIsInVzZXJuYW1lIjoibmFtZSIsImlhdCI6MTcxNzM1OTYwNn0.FPcx7BbcslOyvKdvXaHJBY9YwlDYNC6oUhH-r--o1PE

Status: 400 Bad Request Size: 83 Bytes Time: 16 ms

{ "errors": [ { "type": "field", "msg": "Invalid value", "path": "name", "location": "body" } ] }

can't post, nothing given to post

thunder client Post http://localhost:3001/api/product Auth -> bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImIwMGE3ZDA3LTg4NTQtNGRkZi04ZTFiLWI4ZGY2MWMzYTg5NSIsInVzZXJuYW1lIjoibmFtZSIsImlhdCI6MTcxNzM1OTYwNn0.FPcx7BbcslOyvKdvXaHJBY9YwlDYNC6oUhH-r--o1PE body -> Json { "name": "Note Stuff app" }

Status: 200 OK Size: 170 Bytes Time: 351 ms { "data": { "id": "8e1be90c-a851-4206-afaa-0cc0f3441778", "createdAt": "2024-06-02T21:41:03.738Z", "name": "Note Stuff app", "belongsToId": "b00a7d07-8854-4ddf-8e1b-b8df61c3a895" } }

try making update.ts and updatePoint.ts with above

handler update.ts updatePoint.ts

add rest of product to router.ts

get product

router.get('/product/:id', getOneProduct)

test get product

start server npm run dev

thunder client GET http://localhost:3001/api/product/4e296bf7-bffe-461f-bbab-bd96e1f5da26 Auth -> bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImIwMGE3ZDA3LTg4NTQtNGRkZi04ZTFiLWI4ZGY2MWMzYTg5NSIsInVzZXJuYW1lIjoibmFtZSIsImlhdCI6MTcxNzM1OTYwNn0.FPcx7BbcslOyvKdvXaHJBY9YwlDYNC6oUhH-r--o1PE

tatus: 200 OK Size: 170 Bytes Time: 57 ms { "data": { "id": "4e296bf7-bffe-461f-bbab-bd96e1f5da26", "createdAt": "2024-06-02T23:23:36.420Z", "name": "Note Stuff app", "belongsToId": "b00a7d07-8854-4ddf-8e1b-b8df61c3a895" } }

delete product

router.delete('/product/:id', deleteProduct)

thunder client DELETE http://localhost:3001/api/product/4e296bf7-bffe-461f-bbab-bd96e1f5da26 Auth -> bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImIwMGE3ZDA3LTg4NTQtNGRkZi04ZTFiLWI4ZGY2MWMzYTg5NSIsInVzZXJuYW1lIjoibmFtZSIsImlhdCI6MTcxNzM1OTYwNn0.FPcx7BbcslOyvKdvXaHJBY9YwlDYNC6oUhH-r--o1PE

Status: 200 OK Size: 170 Bytes Time: 84 ms { "data": { "id": "4e296bf7-bffe-461f-bbab-bd96e1f5da26", "createdAt": "2024-06-02T23:23:36.420Z", "name": "Note Stuff app", "belongsToId": "b00a7d07-8854-4ddf-8e1b-b8df61c3a895" } }

test if deleted

GET http://localhost:3001/api/product/4e296bf7-bffe-461f-bbab-bd96e1f5da26 Status: 200 OK Size: 13 Bytes Time: 54 ms { "data": null }

successfully deleted

fill in update.ts

import prisma from "../db"


export const getOneUpdate = async (req, res) => {
  const update = await prisma.update.findFirst({
    where: {
      id: req.params.id
    }
  })


  res.json({data: update})
}
export const getUpdates = async (req, res) => {
  const product = await prisma.product.findMany({
    where: {
      belongsToId: req.user.id
    },
    include: {
      updates: true
    }
  })


  const updates = product.reduce((allUpdates, product) => { //not the right why to do this
    return [...allUpdates, ...product.updates]
  }, [])


  res.json({data: updates})
}
export const createUpdate = async (req, res) => {}
export const updateUpdate = async (req, res) => {}
export const deleteUpdate = async (req, res) => {}

update router post update in router.ts

router.post('/update',
    body('title').exists().isString(),
    body('body').exists().isString(),
    body('productId').exists().isString(), //added
    () => {}
)

continue working on update.ts, make createUpdate

//createUpdate--------------------------------------------------
export const createUpdate = async (req, res) => {
  const product = await prisma.product.findUnique({
    where: {
      id: req.body.id
    }
  })


  if (!product) {
    //user don't not own product
    return res.json({message: 'you do not own this'})
  }


  const update = await prisma.update.create({
    data: req.body  //can trust, already valudated
  })


  res.json({data: update})
}

add .optional() to body('status').isIn(['IN_PROGRESS', 'SHIPPED', 'DEPRECATED']) in router.ts

updateUpdate update.ts

//updateUpdate--------------------------------------------------
export const updateUpdate = async (req, res) => {
  const products = await prisma.product.findMany({
    where: {
      belongsToId: req.user.id,
    },
    include: {
      updates: true
    }
  })


  const updates = products.reduce((allUpdates, product) => { //not the right why to do this
    return [...allUpdates, ...product.updates]
  }, [])


  const match = updates.find(update => update.id == req.params.id)


  if (!match){
    //handle this
    return res.json({message: 'no'})
  }


  //if match
  const updateUpdate = await prisma.update.update({
    where: {
      id: req.params.id
    },
    data: req.body
  })


  res.json({data: updateUpdate})
}

deleteUpdate update.ts

//deleteUpdate--------------------------------------------------
export const deleteUpdate = async (req, res) => {
  const products = await prisma.product.findMany({
    where: {
      belongsToId: req.user.id,
    },
    include: {
      updates: true
    }
  })


  const updates = products.reduce((allUpdates, product) => { //not the right why to do this
    return [...allUpdates, ...product.updates]
  }, [])


  const match = updates.find(update => update.id == req.params.id)


  if (!match){
    //handle this
    return res.json({message: 'no'})
  }


  const deleted = await prisma.update.delete({
    where: {
      id: req.params.id
    }
  })


  res.json({data: deleted})
}

add to the routes

router.ts

/*--------------------Update------------------*/
router.get('/update', getUpdates)
router.get('/update/:id', getOneUpdate)
router.put('/update/:id',
    body('title').optional(),
    body('body').optional(),
    body('status').isIn(['IN_PROGRESS', 'SHIPPED', 'DEPRECATED']).optional(),
    body('version').optional(),
    updateUpdate
)
router.post('/update',
    body('title').exists().isString(),
    body('body').exists().isString(),
    body('productId').exists().isString(),
    createUpdate
)
router.delete('/update/:id', deleteUpdate)

testing start server thunder client POST http://localhost:3001/api/product body -> json { "name": "My car app" }

Status: 200 OK Size: 166 Bytes Time: 315 ms { "data": { "id": "6ccc944a-cfee-4fba-85e4-540cbfe1aa0e", "createdAt": "2024-06-03T00:06:43.367Z", "name": "My car app", "belongsToId": "b00a7d07-8854-4ddf-8e1b-b8df61c3a895" } }

create update

thunder client POST http://localhost:3001/api/update body -> json { "title": "new feature", "body": "describe feature", "productId": "6ccc944a-cfee-4fba-85e4-540cbfe1aa0e" }

fixes in createUpdate id: req.body.id to id: productId added const{productId, ...rest} = res.body data: req.body //can trust, already valudated to data: rest

changes in schema.prisma in model Update UpdatedAt DateTime to UpdatedAt DateTime @updatedAt

run a migration

@MKrause-code ➜ /workspaces/codespaces-blank $ npx prisma migrate dev
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Datasource "db": PostgreSQL database "mak_database", schema "public" at "dpg-cpbm7jun7f5s73ffehi0-a.oregon-postgres.render.com"

Already in sync, no schema change or pending migration was found.

✔ Generated Prisma Client (v5.14.0) to ./node_modules/@prisma/c
lient in 68ms

changes in update.ts createUpdate

//createUpdate--------------------------------------------------
export const createUpdate = async (req, res) => {
  //const{productId, ...rest} = res.body
  const product = await prisma.product.findUnique({
    where: {
      id: req.body.productId
    }
  })


  if (!product) {
    //user don't not own product
    return res.json({message: 'you do not own this'})
  }


  const update = await prisma.update.create({
    data: {
      title: req.body.title,
      body: req.body.body,
      product: {connect: {id: product.id}}
    }
  })


  res.json({data: update})
}

!!!

https://hendrixer.github.io/API-design-v4/lessons/error-handlers/error-handler made similar to middleware but has to come after all the routes

add error handler to server.ts

//error handler
app.use((err, req, res, next) => {
    console.log(err)
    res.json({message: 'oops-daise there was an error'})
})

shows message to user and logs the error message in terminal

need to handle async error yourself, express can't catch it on it's own

add next to the handler

app.get('/', (req, res, next) => {
    setTimeout(() => {
        next(new Error('hello')) 
    }, 1)
})

now preves error handler can catch it

https://hendrixer.github.io/API-design-v4/lessons/error-handlers/custom-handler

adding async error handling

handling errors in server.ts

//error handler
app.use((err, req, res, next) => {
    //console.log(err)
    //res.json({message: 'oops-daise there was an error'})
    if (err.type == 'auth') {
        res.status(401).json({message: 'unauthorized'})
    } else if (err.type == 'input'){
        res.status(400).json({message: 'invalid input'})
    } else {
        res.status(500).json({message: 'oops, something went wrong on are side'})
    }
})

catching an error using try catch in createuser in user.ts

//creates a new user
export const createNewUser = async (req, res, next) => {
    try {
        const user = await prisma.user.create({
            data: {
                username: req.body.username,
                password: await hashPassword(req.body.password)
            }
        })
        const token = createJWT(user)
        res.json({ token })
    } catch (e) { //error catching
        e.type = 'input' //most likely, username already taken
        next(e)
    }
}

testing start server npm run dev

thunder client POST http://localhost:3001/user body -> json { "username": "rick", "password": "cheese" }

Status: 200 OK Size: 200 Bytes Time: 397 ms { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjQ3NTRmY2JiLTc1MjUtNDNiOC1iNWE3LTA0NGVmNjI5NGY5MyIsInVzZXJuYW1lIjoicmljayIsImlhdCI6MTcxNzQ1MjQ5NH0.sjvLFNyg2MnxPPVaLxpAOzRPXz0ynccDGFOvtAUm5ow" }

again thunder client POST http://localhost:3001/user body -> json { "username": "rick", "password": "cheese" }

Status: 400 Bad Request Size: 27 Bytes Time: 65 ms { "message": "invalid input" }

error handler works

adding error catcher to product.ts

//create a product
export const createProduct = async (req, res, next) => {
    try{
        const product = await prisma.product.create({
            data: {
                name: req.body.name,
                belongsToId: req.user.id
            }
        })
        res.json({data: product})
    } catch (e) { //database messed up
        next(e)
    }
}

is a case by case basis

test

thunder client POST http://localhost:3001/api/product Auth -> bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjQ3NTRmY2JiLTc1MjUtNDNiOC1iNWE3LTA0NGVmNjI5NGY5MyIsInVzZXJuYW1lIjoicmljayIsImlhdCI6MTcxNzQ1MjQ5NH0.sjvLFNyg2MnxPPVaLxpAOzRPXz0ynccDGFOvtAUm5ow body -> json { "name": "error testing" }

Status: 200 OK Size: 169 Bytes Time: 357 ms { "data": { "id": "ac9c3859-627e-48df-babe-2f0e6096ad32", "createdAt": "2024-06-03T22:16:04.371Z", "name": "error testing", "belongsToId": "4754fcbb-7525-43b8-b5a7-044ef6294f93" } }

test with name to long

thunder client POST http://localhost:3001/api/product Auth -> bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjQ3NTRmY2JiLTc1MjUtNDNiOC1iNWE3LTA0NGVmNjI5NGY5MyIsInVzZXJuYW1lIjoicmljayIsImlhdCI6MTcxNzQ1MjQ5NH0.sjvLFNyg2MnxPPVaLxpAOzRPXz0ynccDGFOvtAUm5ow body -> json { "name": "error testing name to long aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" }

Status: 500 Internal Server Error Size: 52 Bytes Time: 63 ms { "message": "oops, something went wrong on are side" }

my server is still up

add router error handler in router.ts

router.use((err, req, res, next) => {
    console.log(err)
    res.json({message: 'in router handler'})
})

test again

thunder client POST http://localhost:3001/api/product Auth -> bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjQ3NTRmY2JiLTc1MjUtNDNiOC1iNWE3LTA0NGVmNjI5NGY5MyIsInVzZXJuYW1lIjoicmljayIsImlhdCI6MTcxNzQ1MjQ5NH0.sjvLFNyg2MnxPPVaLxpAOzRPXz0ynccDGFOvtAUm5ow body -> json { "name": "error testing name to long aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" }

Status: 200 OK Size: 31 Bytes Time: 359 ms { "message": "in router handler" }

error in node outside express error.js

process.on('uncaughtException', () => {


})


process.on('unhandledRejection', () => {
   
})
⚠️ **GitHub.com Fallback** ⚠️