☑️ What I Learn
미들웨어 배열
콜백함수 이전 인자에 유효성 검사 및 요청 처리하기 위한 일반적인 패턴이다.
각 배열 인자는 미들웨어 함수를 나타내며, 배열 작성 순으로 실행된다.
마지막에는 반복되는 예외처리를 함수 `validateInput`로 추출하여 사용하였다.
//예시 코드
const express = require('express');
const { body, validationResult } = require('express-validator');
const app = express();
// POST 요청을 처리하는 라우트 핸들러
app.post('/user', [
body("userId").notEmpty().isInt().withMessage("숫자 입력 필요"),
body("name").notEmpty().isString().withMessage("문자 입력 필요"),
validateInput,
], (req, res) => {
// 유효성 검사가 통과되었을 때 실행되는 코드
res.send('Validation passed');
});
// 커스텀 미들웨어 함수 - 유효성 검사 결과 처리
function validateInput(req, res, next) {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
next(); // 다음 미들웨어로 전달
}
app.listen(3000, () => console.log('Server started on port 3000'));
여기에서 `next()` 함수는 미들웨어 체인 내에서 다음 미들웨어 함수를 호출하는 역할을 한다. 만약에 next가 없다면, 현재 미들웨어에서 요청 처리가 멈추게 된다. `미들웨어 체인`
JWT 토큰
구조 - 헤더, 페이로드, 서명
💻 백엔드
jsonwebtoken 라이브러리로 생성하고 검증한다.
const jwt = require('jsonwebtoken');
// 비밀 키 설정 (실제 서비스에서는 환경 변수로 관리)
const ACCESS_TOKEN_SECRET = 'youraccesstokensecret';
const REFRESH_TOKEN_SECRET = 'yourrefreshtokensecret';
// JWT 생성 함수
const generateAccessToken = (user) => {
return jwt.sign(user, ACCESS_TOKEN_SECRET, { expiresIn: '15m' });
};
const generateRefreshToken = (user) => {
return jwt.sign(user, REFRESH_TOKEN_SECRET, { expiresIn: '7d' });
};
// 로그인 엔드포인트
app.post('/login', (req, res) => {
const { username, password } = req.body;
const user = users.find(u => u.username === username && u.password === password);
if (user) {
const accessToken = generateAccessToken({ id: user.id, username: user.username });
const refreshToken = generateRefreshToken({ id: user.id, username: user.username });
res.json({ accessToken, refreshToken });
} else {
res.status(401).send('Username or password incorrect');
}
});
// 토큰 갱신 엔드포인트
app.post('/token', (req, res) => {
const { token } = req.body;
if (!token) return res.sendStatus(401);
jwt.verify(token, REFRESH_TOKEN_SECRET, (err, user) => {
if (err) return res.sendStatus(403);
const accessToken = generateAccessToken({ id: user.id, username: user.username });
res.json({ accessToken });
});
});
// 보호된 라우트 > 로그인된 사용자만 접근가능한 라우트
app.get('/protected', authenticateToken, (req, res) => {
res.json({ message: 'This is a protected route', user: req.user });
});
💻 프론트엔드
localStorage 로컬 스토리지, Cookie 쿠키에 토큰을 저장하는 방법이 있다.
//로그인 유효하다고 응답이 오는지 서버에 요청 보낸다
const response = await fetch('http://localhost:3000/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username, password })
});
//응답이 되면, 토큰들을 로컬스토리지에 저장한다.
if (response.ok) {
const data = await response.json();
localStorage.setItem('accessToken', data.accessToken);
localStorage.setItem('refreshToken', data.refreshToken);
document.getElementById('message').textContent = 'Login successful!';
document.getElementById('logout-button').style.display = 'block';
} else {
document.getElementById('message').textContent = 'Login failed!';
}
});
//로그아웃 버튼을 누르면, 로컬 스토리지의 토큰들을 지운다.
document.getElementById('logout-button').addEventListener('click', () => {
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
document.getElementById('message').textContent = 'Logged out!';
document.getElementById('logout-button').style.display = 'none';
});
//인증이 유효한 사용자 인지 확인하기 위해 헤더에 토큰을 포함해 요청하고 접근제어 한다.
async function fetchProtectedResource() {
const accessToken = localStorage.getItem('accessToken');
const response = await fetch('http://localhost:3000/protected', {
method: 'GET',
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
//제어
if (response.ok) {
const data = await response.json();
console.log(data);
} else if (response.status === 401) {
// Access token might be expired
await refreshAccessToken();
await fetchProtectedResource(); // Retry with new token
} else {
document.getElementById('message').textContent = 'Failed to fetch protected resource';
}
}
//refresh 토큰을 사용해 새로운 access 토큰을 발급받는다.
async function refreshAccessToken() {
const refreshToken = localStorage.getItem('refreshToken');
const response = await fetch('http://localhost:3000/token', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ token: refreshToken })
});
if (response.ok) {
const data = await response.json();
localStorage.setItem('accessToken', data.accessToken);
} else {
document.getElementById('message').textContent = 'Failed to refresh token';
logout();
}
}
node.js 로 api 구현하기
시간 내에 다 못풀었다는게 아쉽고, 이후 일정이 있어서 풀이에 올인을 못했다는게 아쉽다. 아쉽게 못풀은 뒤 4문제들을 풀어내는 글을 작성했다. 글로 작성된 요구사항을 코드로 구현하는 경험을 해서 좋았다.
Swagger > API를 문서화하는 툴
이전에 플젝할때 백엔드에서 사용했었는데 유용했던 기억이 난다.
☑️Liked, Learned, Lacked, Longer for
JWT 구현에서 백엔드 + 프론트엔드 로직 이해할 수 있어서 좋았다. 지금 프로젝트에서 JWT 토큰을 LocalStorage에 저장하는 방식을 사용하자고 하는데, 백에서 어디까지 해주면 프론트에서 어떤 처리를 해야하는지 이해할 수 있었다.
유효성 검사 시 미들웨어 활용하는 방법은 몰랐는데 배우게 되었다.
요구사항 설계 풀이 글에도 작성했지만, 자바스크립트 메서드에 많이 익숙해졌다고 생각했는데 적재적소에 함수를 사용하는 데에는 아직 부족하다고 느꼈다. 프로젝트에 적극 반영 하면서 빨리 완벽하게 익숙해지고 싶다.
프로젝트에서 router 구현하기로 했는데, 문제없이 개발해내면 좋겠당... 이번주 목요일, 주말에 일정이 있어서 미리미리 학습해야한다. 미루지말고 열심히 시간을 투자하자! 그리고 강사님 말씀대로 혼자 미리 고민하고, 개발해보고, 강의듣기! 실천하자!