Web Infra HW 06 MAK - TheEvergreenStateCollege/upper-division-cs-23-24 GitHub Wiki
page index
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
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
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/
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', () => {
})