미들웨어
- 웹 서버에서 요청이나 응답에 대해 공통적인 관리가 가능
- ex)모든 요청에 대해 로그를 남겨 확인하고 싶은 경우
- 모든 로깅을 관리해주는 미들웨워
app.use(express.urlencoded({ extended: false }));
app.use(express.json());
유명한 웹서버
- Apache
- nginx
미들웨어 기본
- 위에서 부터 순차적으로 미들웨어를 통과함
- next()가 무조건 있어야 다음 미들웨어 실행
app.use((req, res, next) => {
console.log('첫번째 미들웨어');
next();
});
app.use((req, res, next) => {
console.log('두번째 미들웨어');
next();
});
app.use((req, res, next) => {
console.log('세번째 미들웨어');
next();
});
// print: 첫번째 미들웨어
// print: 두번째 미들웨어
// print: 세번째 미들웨어
쇼핑몰 구현
app.js
- 라우터 및 미들웨어 추가
const express = require('express');
const app = express();
const port = 3000;
const goodsRouter = require("./routes/goods");
const cartsRouter = require("./routes/carts.js");
const connect = require("./schemas");
connect();
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(express.static("assets"));
app.use("/api", [goodsRouter, cartsRouter]);
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(port, () => {
console.log(port, '포트로 서버가 열렸어요!');
});
user 모델
- email, nickname, password 필드 추가
- 몽고db _id값을 가상의 값을 할당함 virtual("적용할key")
const mongoose = require("mongoose");
const UserSchema = new mongoose.Schema({
email: { // email 필드
type: String,
required: true,
unique: true,
},
nickname: { // nickname 필드
type: String,
required: true,
unique: true,
},
password: { // password 필드
type: String,
required: true,
},
});
// 가상의 userId 값을 할당
UserSchema.virtual("userId").get(function () {
return this._id.toHexString();
});
// user 정보를 JSON으로 형변환 할 때 virtual 값이 출력되도록 설정
UserSchema.set("toJSON", {
virtuals: true,
});
module.exports = mongoose.model("User", UserSchema);
회원가입 API users.js
- 클라이언트로부터 email, nickname, password, confirmPassword 4개의 값을 받음
- password와 confirmPassword 값이 같은지 확인하는 조건문 작성
- db에서 email 또는 ($or) nickname이 일치하는 데이터가 있는지 확인
- email 또는 nickname이 사용중일때 return으로 아래코드가 더 이상 수행하지 않고 코드종료
- 패스워드 저장시 암호화 하여 저장, 복호화 불가능, crypto라는 라이브러리 사용
- user.saver()로 db에 저장후 201상태 반환
// routes/users.js
const express = require("express");
const router = express.Router();
const User = require("../schemas/user");
// 회원가입 API
router.post("/users", async (req, res) => {
const { email, nickname, password, confirmPassword } = req.body;
if (password !== confirmPassword) {
res.status(400).json({
errorMessage: "패스워드가 패스워드 확인란과 다릅니다.",
});
return;
}
// email 또는 nickname이 동일한 데이터가 있는지 확인하기 위해 가져온다.
const existsUsers = await User.findOne({
$or: [{ email }, { nickname }],
});
if (existsUsers) {
// NOTE: 보안을 위해 인증 메세지는 자세히 설명하지 않습니다.
res.status(400).json({
errorMessage: "이메일 또는 닉네임이 이미 사용중입니다.",
});
return;
}
const user = new User({ email, nickname, password });
await user.save();
res.status(201).json({});
});
module.exports = router;
로그인 API auth.js
- 입력받은 이메일, 패스워드 할당
- 이메일이 일치하는 유저를 db에서 findOne메서드으로 하나만 찾음
- 이메일이 일치하는 유저가 존재하지 않거나 일치하는 유저를 찾았지만 유저의 비밀번호와 입력한 비밀번호가 다를때를 구분하는 조건문작성
- 비밀번호가 일치 할때 jwt생성 jwt.sign( { userId : userid } , "customized secret key")
- Bearer 타입으로 "Authorization"이라는 값 jwt전달
- jwt를 쿠키로 할당 res.cookie( "Authorization" ,`Bearer ${token}` )
- jwt를 body로 할당 res.status(200).json({ token });
// routes/auth.js
const jwt = require("jsonwebtoken");
const express = require("express");
const router = express.Router();
const User = require("../schemas/user");
// 로그인 API
router.post("/auth", async (req, res) => {
const { email, password } = req.body;
const user = await User.findOne({ email });
// NOTE: 인증 메세지는 자세히 설명하지 않는것을 원칙으로 한다.
if (!user || password !== user.password) {
res.status(400).json({
errorMessage: "이메일 또는 패스워드가 틀렸습니다.",
});
return;
}
const token = jwt.sign(
{ userId: user.userId },
"custom-secret-key",
);
res.cookie("Authorization", `Bearer ${token}`); // JWT를 Cookie로 할당합니다!
res.status(200).json({ token }); // JWT를 Body로 할당합니다!
});
module.exports = router;
app.js 에 authRouter 추가
const authRouter = require("./routes/auth.js");
app.use("/api", [goodsRouter, cartsRouter, usersRouter, authRouter]);
사용자 인증 미들웨어
- 구현된 프론트엔드는 로그인이 성공했을때 받아온 토큰을 HTTP header에 넣어서 보내고 있음
Authorization : Bearer JWT토큰내용
- "Authorization" 헤더로 전달받는 토큰이 유효한지 검사, 유효하다면 토큰안에 있는 userId 데이터로 해당 사용자가 db에 존재하는지 체크.
cookie-parser 추가
const cookieParser = require("cookie-parser");
app.use(cookieParser());
사용자 인증 미들웨어 auth-middleware.js
- 전달받은 Autorization 이 쿠키정보를 가지고옴
- authType , authToken이 Authorization 변수가 undefined이거나 null 값일 경우 빈 문자열("")로 변경, null 병합 연산자를 사용해 " "값을 왼쪽에 넣어줌(오류가 나지 않게 하기 위함)
- try catch문으로 jwt.verify(authToken, "customized-secret-key") 토큰이 일치하는지 확인
- 미들웨어를 사용하는 라우터에서는 굳이 데이터베이스에서 사용자 정보를 가져오지 않게 할 수 있도록 express가 제공하는 안전한 변수인 res.locals에 담아두고, 언제나 꺼내서 사용할 수 있게 작성
// middlewares/auth-middleware.js
const jwt = require("jsonwebtoken");
const User = require("../schemas/user");
// 사용자 인증 미들웨어
module.exports = async (req, res, next) => {
const { Authorization } = req.cookies;
const [authType, authToken] = (Authorization ?? "").split(" ");
if (!authToken || authType !== "Bearer") {
res.status(401).send({
errorMessage: "로그인 후 이용 가능한 기능입니다.",
});
return;
}
try {
const { userId } = jwt.verify(authToken, "customized-secret-key");
const user = await User.findById(userId);
res.locals.user = user;
next();
} catch (err) {
console.error(err);
res.status(401).send({
errorMessage: "로그인 후 이용 가능한 기능입니다.",
});
}
};
내 정보 조회 API routes/users.js
- 사용자 인증 미들웨어추가.
- res.locals 에 저장된 email, nickname을 가져옴.
- 예시와 같이 출력
// routes/users.js
const authMiddleware = require("./middlewares/auth-middleware");
...
// 내 정보 조회 API
router.get("/users/me", authMiddleware, async (req, res) => {
const { email, nickname } = res.locals.user;
res.status(200).json({
user: { email, nickname }
});
});
...
예시
{
"user": {
"email": "test@email.com",
"nickname": "test-nickname",
}
}
상품 API 수정 routes/goods.js
상품 목록조회 API
- find(category ? { category } : {}) query param으로 category가 전달 되었을경우 해당하는 category로 조회하고 category가 존재하지 않는 경우 모든 상품을 조회하는 코드
// routes/goods.js
// 상품 목록 조회 API
router.get("/goods", async (req, res) => {
const { category } = req.query;
const goods = await Goods.find(category ? { category } : {})
.sort("-date")
.exec();
const results = goods.map((item) => {
return {
goodsId: item.goodsId,
name: item.name,
price: item.price,
thumbnailUrl: item.thumbnailUrl,
category: item.category,
};
});
res.json({ goods: results });
});
상품 상세조회 API
- 입력된 query param과 일치하는 goodsId를 1개만 가져옴. (데이터를 전체 조회할때는 [{ }], 1개만 조회할때는 { })
- 입력된 query param이 없을때는 404에러 반환
// routes/goods.js
// 상품 상세 조회 API
router.get("/goods/:goodsId", async (req, res) => {
const { goodsId } = req.params;
const goods = await Goods.findOne({ goodsId: goodsId }).exec();
if (!goods) return res.status(404).json({});
const result = {
goodsId: goods.goodsId,
name: goods.name,
price: goods.price,
thumbnailUrl: goods.thumbnailUrl,
category: goods.category,
}
res.json({ goods: result });
});
Cart 모델 수정
- userId필드 추가하여 어느 사용자의 장바구니 인지 구분
const mongoose = require("mongoose");
const cartSchema = new mongoose.Schema({
userId: {
type: String,
required: true,
},
goodsId: {
type: Number,
required: true,
},
quantity: {
type: Number,
required: true,
}
});
module.exports = mongoose.model("Cart", cartSchema);
장바구니 등록 API routes/goods.js
- authmiddleware로 인증 미들웨어 추가
- 저장된 jwt userId에 할당
// routes/goods.js
const Goods = require("../schemas/goods.js");
const Cart = require("../schemas/cart.js");
const authMiddleware = require("../middlewares/auth-middleware");
// 장바구니 등록 API
router.post("/goods/:goodsId/cart", authMiddleware, async (req, res) => {
const { userId } = res.locals.user;
const { goodsId } = req.params;
const { quantity } = req.body;
const existsCarts = await Cart.find({ userId, goodsId }).exec();
if (existsCarts.length) {
return res.status(400).json({
success: false,
errorMessage: "이미 장바구니에 해당하는 상품이 존재합니다.",
});
}
await Cart.create({ userId, goodsId, quantity });
res.json({ result: "success" });
});
장바구니 조회 API routes/goods.js
- 인증 미들웨어 추가
- jwt 가져오기
// routes/goods.js
const Goods = require("../schemas/goods.js");
const Cart = require("../schemas/cart.js");
const authMiddleware = require("../middlewares/auth-middleware");
// 장바구니 조회 API
router.get("/goods/cart", authMiddleware, async (req, res) => {
const { userId } = res.locals.user;
const carts = await Cart.find({ userId }).exec();
const goodsIds = carts.map((cart) => cart.goodsId);
const goods = await Goods.find({ goodsId: goodsIds });
const results = carts.map((cart) => {
return {
quantity: cart.quantity,
goods: goods.find((item) => item.goodsId === cart.goodsId),
};
});
res.json({
carts: results,
});
});
장바구니 상품 수정 API routes/goods.js
- 인증 미들웨어 추가
- jwt 가져오기
// routes/goods.js
const Goods = require("../schemas/goods.js");
const Cart = require("../schemas/cart.js");
const authMiddleware = require("../middlewares/auth-middleware");
// 장바구니 상품 수정 API
router.put("/goods/:goodsId/cart", authMiddleware, async (req, res) => {
const { userId } = res.locals.user;
const { goodsId } = req.params;
const { quantity } = req.body;
const existsCarts = await Cart.find({ userId, goodsId });
if (existsCarts.length) {
await Cart.updateOne(
{ userId, goodsId: goodsId },
{ $set: { quantity: quantity } }
);
}
res.status(200).json({ success: true });
});
장바구니 상품 삭제 API routes/goods.js
- 인증 미들웨어 추가
- jwt 가져오기
// routes/goods.js
const Goods = require("../schemas/goods.js");
const Cart = require("../schemas/cart.js");
const authMiddleware = require("../middlewares/auth-middleware");
// 장바구니 상품 삭제 API
router.delete("/goods/:goodsId/cart", authMiddleware, async (req, res) => {
const { userId } = res.locals.user;
const { goodsId } = req.params;
const existsCarts = await Cart.find({ userId, goodsId });
if (existsCarts.length) {
await Cart.deleteOne({ userId, goodsId });
}
res.json({ result: "success" });
});
회원가입 payload
유저가 입력한 정보를 기반을 생성된 쿠키
db에 저장된 email과 password
저장된 email과 password의 값이 똑같을때
생성된 토큰 반환
토큰의 정보
res.cookie("Authorization", `Beare ${token}`)
name:authorization value:Beare%20eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NDQwYzUyNGU1MjFkNWU0ODJkN2Q0OWQiLCJpYXQiOjE2ODE5NjY0NTZ9.iAoDC-vQXWGKd6mBWCbgu0MUolKnqn6sQdHglunq_pg
내일 할일: 오늘 너무 많이 놀아서 내일은 일어나자마자 lv2 과제 api명세 정리하고 바로 과제 수행해야겠다.