회원가입/로그인은 거의 모든 웹 서비스에서 제공하는 아주 기본적인 기능이다.
예를 들어 인스타그램같이 사진 공유 소셜 네트워크 서비스를 제공한다면, 사용자가 사진을 업로드하고 다른 사용자를 팔로우 하는 등, 각 사용자의 데이터가 지속적으로 누적될 것이다.
이렇게 쌓인 데이터를 어떤 사용자가 소유하는지 구분하기 위해서 회원가입/로그인 기능이 반드시 수반되어야 한다.
이번 장은 회원가입/로그인 기능을 구현해본 적이 없는 사람이 이 글을 읽더라도 이해할 수 있도록 단계별로 조금씩 기능을 추가하면서 기능을 고도화 하는 방식으로 회원가입/로그인 기능을 구현해볼 것이다.
실제 서비스에서는 사용자의 데이터를 데이터베이스에 저장하지만 이 글의 목적은 DB 사용법을 익히기 위한 것이 아니기 때문에 이 부분은 JS에 내장된 Map과 json 파일을 이용하여 대체할 것이다.
우선 server라는 디렉토리를 생성하고 npm init -y
명령어를 통해 초기화한다.
그리고 npm i express cookie-parser
명령어로 2가지 모듈을 설치한 뒤, main.js
파일과 public
폴더를 만들고 public
폴더 내에 login.html
, signup.html
파일을 생성하자.
완성된 폴더 구조는 위와 같을 것이다.(main.js
가 보이지 않지만 만들어주자.)
우선 html 파일부터 만들어보자.
<!-- signup.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<title>Sign Up</title>
</head>
<body>
<form method="POST" action="/signup">
<label for="username">Username: </label><input type="text" name="username" />
<br/>
<label for="name">Name: </label><input type="text" name="name" />
<br/>
<label for="password">Password: </label><input type="password" name="password" />
<br/>
<button>Sign Up</button>
</form>
</body>
</html>
회원가입을 위한 html 파일이다.
username
, name
, password
을 입력할 수 있는 input 태그가 존재하며, Sign Up 버튼을 누르면 입력된 데이터를 body에 담아 POST /signup
으로 요청을 보낸다.(username
이 아이디를 뜻한다고 보면 된다.)
사용 서비스에는 성별, 출생연도 등 더 많은 정보를 요구하겠지만, 학습 목적이기 때문에 필요한 회원 정보를 최대한 간단하게 구성했다.
나중에 구현할 POST /signup
라우터에서 데이터를 받아 회원가입 로직을 작성할 것이다.
<!-- login.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<title>Log In</title>
</head>
<body>
<form method="POST" action="/login">
<label for="username">Username: </label><input type="text" name="username" />
<br/>
<label for="password">Password: </label><input type="password" name="password" />
<br/>
<button>Log In</button>
</form>
</body>
</html>
회원가입과 코드가 크게 다르지 않지만, 로그인은 보통 어떤 서비스라도 아이디(username), 비밀번호만 요구하는 경우가 많다.
그래서 똑같이 구성하였고, POST /login
으로 username
, password
를 body에 첨부하여 전송하면 해당 라우터에서 로그인 로직이 작성되어 이를 처리할 것이다.
처음에는 Map, Cookie를 이용해 기능을 구현하고 코드를 점차 리팩토링 하면서 기능을 개선해 나갈 것이다.
처음에는 사용자 정보를 Map에 저장할 것인데, 이 때문에 서버가 재시작되면 모든 데이터가 날아갈 것이다.
상용 서비스에서는 사용자 데이터를 안정적으로 저장하는 것이 중요하기 때문에 절대로 이런식으로 구현하지 않는다는 점을 기억하자.
우선 사용할 모듈을 모두 불러온 다음 서버 인스턴스를 생성하고, 미들웨어를 등록해보자.
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const app = express();
// 사용자 정보를 저장할 데이터베이스
const db = new Map();
// KEY=VALUE 형태로 브라우저에 저장되는 쿠키의 KEY
const USER_COOKIE_KEY = 'USER';
// 위에서 작성한 html을 클라이언트에 제공하기 위한 미들웨어
app.use(express.static(path.join(__dirname, 'public')));
// 쿠키를 파싱하기 위한 미들웨어
app.use(cookieParser());
// x-www-form-urlencoded 타입의 form 데이터를 파싱하기 위한 미들웨어
app.use(express.urlencoded({ extended: true }));
app.listen(3000, () => {
console.log('server is running at 3000');
});
앞 장에서 모두 봤던 내용이지만 주석을 통해 간단히 각 코드의 역할을 정리해 두었으니 기억이 잘 안난다면 한번 읽어보도록 하자.
app.post('/signup', (req, res) => {
const { username, name, password } = req.body;
const exists = db.get(username);
// 이미 존재하는 username일 경우 회원 가입 실패
if (exists) {
res.status(400).send(`duplicate username: ${username}`);
return;
}
// 아직 가입되지 않은 username인 경우 db에 저장
// KEY = username, VALUE = { name, password }
const newUser = {
username,
name,
password,
};
db.set(username, newUser);
// db에 저장된 user 객체를 문자열 형태로 변환하여 쿠키에 저장
res.cookie(USER_COOKIE_KEY, JSON.stringify(newUser));
// 가입 완료 후, 루트 페이지로 이동
res.redirect('/');
});
우선 회원가입을 위한 라우터를 먼저 작성한다.
회원가입이 수반되어야 로그인이 가능하기 때문이다.
아까 우리가 작성한 /public/signup.html
페이지에 username
, name
, password
를 입력하고 Sign Up 버튼을 누르면 이 라우터가 실행될 것이다.
express.urlencoded()
미들웨어가 입력된 데이터를 파싱하여 req.body
에 JS 객체 형태로 저장해줄 것인데, 구조 분해 할당 문법을 통해 데이터를 가져왔다.
이는 자바스크립트 문법이므로 자세히 설명하지 않겠다.
해당 문법을 모른다면 아래 문서를 참조하자.
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment
그리고 db.get(username)
을 통해 사용자의 아이디가 이미 가입이 되었는지 확인한다.
(사용자를 Map에 저장할 때 Key를 username
, value를 { username, name, password }
로 저장할 것이다.)
가입이 이미 된 아이디라면 상태 코드 400으로 duplicate username
라는 메세지를 보내준다.
가입이 되지 않았다면 Map에 저장하기 위한 사용자 객체를 만든다.
아까 본 { username, name, password }
형태의 객체다.
그리고 해당 객체를 Map에 저장한다.
Key는 username
이다.
그리고 다음이 중요한데, JSON.stringify(newUser)
로 사용자 정보를 담은 객체를 문자열로 변환한다.
해당 데이터를 쿠키에 저장할 것이기 때문이다.
(쿠키는 문자열로 이루어진 KEY=VALUE 형태의 데이터라는 것을 2장에서 봤었다.)
그래서 username
이 "abc"
, name
이 "xyz"
, password
가 "123"
인 사용자가 있을 때, 문자열로 변환된 데이터는 "{ "username": "abc", "name": "xyz", "password": "123" }"
와 같은 형태일 것이다.
res.cookie()
의 인자로 쿠키의 KEY, VALUE를 입력하면 된다.
그래서 아래와 같은 코드가 되는 것이다.
res.cookie(USER_COOKIE_KEY, JSON.stringify(newUser));
첨언하면 USER_COOKIE_KEY
는 사용자의 데이터를 저장하기 위한 쿠키의 Key다.
'USER'
라는 단순 문자열인데, 이를 코드 여기 저기에 반복적으로 사용하다 보면 오타가 날 수도 있고, 'USER'
보다는 USER_COOKIE_KEY
라는 이름이 쿠키의 Key라는 의미를 좀 더 분명하게 전달할 수 있기 때문에 변수에 저장하여 사용하는 것이다.
그리고 마지막으로 res.redirect('/')
가 실행되는데, 이는 미들웨어 실행 순서를 무시하고 app.get('/')
라우터로 처리를 넘기게 된다.
이제 app.get('/')
라우터를 작성해보자.
app.get('/', (req, res) => {
// 'user'라는 쿠키 데이터를 가져옴
// 쿠키가 존재하지 않을 경우 로그인이 되지 않았다는 뜻
const user = req.cookies[USER_COOKIE_KEY];
if (user) {
// 쿠키가 존재하는 경우, 쿠키 VALUE를 JS 객체로 변환
const userData = JSON.parse(user);
// user 객체에 저장된 username이 db에 존재하는 경우,
// 유효한 user이며 로그인이 잘 되어 있다는 뜻.
if (db.get(userData.username)) {
// JS 객체로 변환된 user 데이터에서 username, name, password를 추출하여 클라이언트에 렌더링
res.status(200).send(`
<a href="/logout">Log Out</a>
<h1>id: ${userData.username}, name: ${userData.name}, password: ${userData.password}</h1>
`);
return;
}
}
// 쿠키가 존재하지 않는 경우, 로그인 되지 않은 것으로 간주
res.status(200).send(`
<a href="/login.html">Log In</a>
<a href="/signup.html">Sign Up</a>
<h1>Not Logged In</h1>
`);
});
이제 회원가입과 루트 페이지 구현이 완료되었다.
사실 회원가입을 할 때 기입된 정보로 쿠키를 발급하기 때문에, 로그인까지 자동으로 처리된다.
그러나 회원가입을 2번 할 수는 없기 때문에 쿠키가 유실되거나 다른 컴퓨터로 접속하는 경우엔 로그인을 할 방법이 없을 것이다.
일단 현재까지 구현한 것들만 가지고 회원가입 + 로그인이 잘 되는지 실험해보자.
GET /
으로 들어가면 위와 같은 화면이 나온다.
Sign Up
을 눌러 GET /signup.html
으로 이동하자.
가입 정보는 대략 위와 같으며 비밀번호는 1이다.
Application 탭에서 쿠키가 정상적으로 발급되었음을 확인할 수 있다.
그 뒤, res.redirect('/');
로 인해 루트 페이지로 이동하는데, 이제 쿠키에 담긴 사용자 정보를 파싱하여 가입 당시의 username
, name
, password
가 그대로 출력된다.
그리고 로그아웃 버튼이 있는데, 아직 로그아웃 기능은 구현하지 않았기 때문에 클릭해도 원하는 결과를 얻을 수 없을 것이다.
로그인 이전에 로그아웃 기능을 먼저 구현해보자.
app.get('/logout', (req, res) => {
// 쿠키 삭제 후 루트 페이지로 이동
res.clearCookie(USER_COOKIE_KEY);
res.redirect('/');
});
로그아웃은 매우 간단하다.
쿠키를 삭제한 뒤, 루트 페이지로 이동하면 끝이다.
위 코드를 추가하고 서버를 재실행 한 뒤, 아까와 똑같은 정보로 회원가입을 하자.
방금 봤던 화면이 그대로 나온다.
이제 로그아웃 버튼을 눌러보자.
다시 로그인 하기 전의 화면으로 이동되었다.
현재 우리는 방금 만든 아이디, 비밀번호로 로그인 할 방법이 없다.
아직 기능을 구현하지 않았기 때문이다.
이제 로그인 기능을 구현할 차례다.
app.post('/login', (req, res) => {
const { username, password } = req.body;
const user = db.get(username);
// 가입 안 된 username인 경우
if (!user) {
res.status(400).send(`not registered username: ${username}`);
return;
}
// 비밀번호가 틀렸을 경우
if (password !== user.password) {
res.status(400).send('incorrect password');
return;
}
// db에 저장된 user 객체를 문자열 형태로 변환하여 쿠키에 저장
res.cookie(USER_COOKIE_KEY, JSON.stringify(user));
// 로그인(쿠키 발급) 후, 루트 페이지로 이동
res.redirect('/');
});
3가지 시나리오가 있다.
로그인 하려는 아이디가 존재하지 않는 경우,
아이디는 있는데 비밀번호가 틀렸을 경우,
로그인에 성공하는 경우
가입이 안 된 경우 400번 상태 코드와 함께 not registered username
라는 메세지를 보내고 핸들러를 종료한다.
비밀번호가 틀렸을 경우, 400번 상태 코드와 함께 incorrect password
라는 메세지를 보내고 핸들러를 종료한다.
로그인에 성공한 경우, 쿠키를 발급하고 루트 페이지로 이동시킨다.
코드를 재실행하고 아까와 같은 사용자 정보로 회원가입 했다.
가입을 완료했으니 로그아웃하고 로그인 페이지로 이동한다.
3가지 시나리오가 제대로 수행되는지 알아보기 위해 일부러 아이디를 잘못 입력해보자.
myID
가 아닌 myIDD
를 입력했다.
등록되지 않은 아이디라는 메세지가 잘 출력된다.
이번에는 비밀번호를 일부러 틀리게 입력했다.
유효하지 않은 비밀번호라고 잘 나온다.
마지막으로 정확한 아이디, 비밀번호를 입력해보자
회원가입을 했을 때처럼 제대로 로그인이 처리되는 것을 볼 수 있다.
이렇게 간단한 회원가입/로그인 + 로그아웃 기능을 구현했다.
그러나 이 시스템에는 문제점이 너무 많다.
우선 서버를 종료하면 회원 정보가 모조리 삭제된다.
비밀번호에 어떠한 암호화도 가하지 않고 그대로 저장한다.
쿠키에 모든 정보를 그대로 담기 때문에 사용자의 컴퓨터에 해커가 접근하여 쿠키가 유출되면 정보가 모조리 털리게 된다.
간단한 예로, 브라우저의 Application 탭에 들어가서 쿠키를 직접 수정할 수 있는데, 만약 myID
를 myID2
로 수정했는데 실제로 myID2
라는 유저가 존재한다면 어떻게 될까.
실제로 myID2
라는 아이디를 만들고 쿠키의 username
을 myID2
로 바꾸기만 했는데도 myID2
로 로그인 된 것으로 인식된다.
루트 페이지에서는 쿠키에 담긴 username
와 일치하는 사용자를 db에서 그대로 가져오기 때문이다.(비밀번호 검증을 하지 않는다.)
쿠키를 살짝 변경했는데 아이디만 일치하면 그대로 다른 사람의 아이디를 사용할 수 있는 것이다.
우리가 작성한 코드의 경우 username
만 바뀌었고 name
, password
는 그대로기 때문에 아이디를 바꿔서 다른 계정으로 접근한다고 모든 정보가 털리는 것은 아니지만, 기능이 추가되어 사용자의 정보에 접근, 수정하는 것이 가능해진다면 엄청난 취약점이 될 것이다.
위에서 언급한 문제점들을 하나씩 리팩토링하면서 고쳐보도록 하겠다.
한 번에 완성된 프로그램을 만드는 것보다는, 문제가 많은 프로그램을 먼저 작성한 뒤, 하나씩 문제가 되는 부분을 고쳐보고, 해당 부분을 수정하는 정확한 이유를 알고 바로잡아 나가는 것이 이해하는데 더욱 도움이 된다고 생각하기 때문에 이런 식으로 진행하기로 결정했다.
사용자 정보를 Map에 저장하면 코드가 재실행 될 때마다 초기화된다.
하드디스크가 아니라 메모리에 저장되기 때문이다.
RAM이라고도 불리는 메모리는 휘발성 기억장치이기 때문에 컴퓨터가 꺼지면 모든 정보가 날아간다.
코드를 실행하면 해당 코드가 메모리 위에 적재되어 실행되는데, 코드를 중단해도 정보가 날아간다.
이를 방지하기 위해서 보조 기억장치라고도 부르는 HDD, SDD 등에 데이터를 저장하는데, 이를 persist(영속적 저장)라고도 표현한다.
보통은 서버를 만들 때는 데이터를 DB에 저장하지만, 여기서는 파일에 저장하는 방법을 사용할 것이다.
회원가입 할 때마다 users.json
이라는 파일에 모든 유저 정보를 저장하고, 로그인 할 때마다 유저 정보를 불러와서 가입된 아이디, 비밀번호가 맞는지 확인할 것이다.
우선 user.json
파일을 생성하자.
// users.json
[]
파일 안에 다음과 같이 빈 배열을 하나 넣으면 준비가 완료된다.
이 배열 속에 회원가입을 할 때마다 사용자 데이터가 하나씩 쌓이게 될 것이다.
const fs = require('fs').promises;
const USERS_JSON_FILENAME = 'users.json';
async function fetchAllUsers() {
const data = await fs.readFile(USERS_JSON_FILENAME);
const users = JSON.parse(data.toString());
return users;
}
async function fetchUser(username) {
const users = await fetchAllUsers();
const user = users.find((user) => user.username === username);
return user;
}
async function createUser(newUser) {
const users = await fetchAllUsers();
users.push(newUser);
await fs.writeFile(USERS_JSON_FILENAME, JSON.stringify(users));
}
콜백 함수를 기본으로 사용하는 fs
모듈을 Promise 기반으로 사용할 수 있도록 require('fs').promises
형태로 불러온다.
USERS_JSON_FILENAME
는 우리의 데이터베이스가 될 파일의 이름을 담고 있다.
3개의 async
함수를 작성했는데, users.json
파일을 읽어와서 그 안에 담겨있는 사용자 데이터를 JS 배열로 변환하여 사용하고, 사용자를 추가할 때는 배열에 새로운 사용자를 추가한 뒤, 다시 JSON.stringify()
로 문자열로 바꿔서 저장한다.
fetchUser()
의 경우 존재하지 않는 username
을 입력하면 undefined
가 반환된다.
특별한 건 없고, Map에 저장하던 것을 json
파일에 문자열로 변환해서 저장하고, 문자열을 JS 배열로 변환해서 불러오는 식으로 살짝 변경되었다.
데이터가 파일에 저장되기 때문에 메모리가 아닌 HDD, SDD 등에 저장되어 서버를 재시작해도 데이터가 사라지지 않고 유지된다는 장점이 있다.
전체 코드는 아래와 같다.
const express = require('express');
const path = require('path');
const fs = require('fs').promises;
const cookieParser = require('cookie-parser');
const app = express();
const USER_COOKIE_KEY = 'USER';
const USERS_JSON_FILENAME = 'users.json';
async function fetchAllUsers() {
const data = await fs.readFile(USERS_JSON_FILENAME);
const users = JSON.parse(data.toString());
return users;
}
async function fetchUser(username) {
const users = await fetchAllUsers();
const user = users.find((user) => user.username === username);
return user;
}
async function createUser(newUser) {
const users = await fetchAllUsers();
users.push(newUser);
await fs.writeFile(USERS_JSON_FILENAME, JSON.stringify(users));
}
app.use(express.static(path.join(__dirname, 'public')));
app.use(cookieParser());
app.use(express.urlencoded({ extended: true }));
app.get('/', async (req, res) => {
// 'user'라는 쿠키 데이터를 가져옴
// 쿠키가 존재하지 않을 경우 로그인이 되지 않았다는 뜻
const userCookie = req.cookies[USER_COOKIE_KEY];
if (userCookie) {
// 쿠키가 존재하는 경우, 쿠키 VALUE를 JS 객체로 변환
const userData = JSON.parse(userCookie);
// user 객체에 저장된 username이 db에 존재하는 경우,
// 유효한 user이며 로그인이 잘 되어 있다는 뜻.
const user = await fetchUser(userData.username);
if (user) {
// JS 객체로 변환된 user 데이터에서 username, name, password를 추출하여 클라이언트에 렌더링
res.status(200).send(`
<a href="/logout">Log Out</a>
<h1>id: ${userData.username}, name: ${userData.name}, password: ${userData.password}</h1>
`);
return;
}
}
// 쿠키가 존재하지 않는 경우, 로그인 되지 않은 것으로 간주
res.status(200).send(`
<a href="/login.html">Log In</a>
<a href="/signup.html">Sign Up</a>
<h1>Not Logged In</h1>
`);
});
app.post('/signup', async (req, res) => {
const { username, name, password } = req.body;
const user = await fetchUser(username);
// 이미 존재하는 username일 경우 회원 가입 실패
if (user) {
res.status(400).send(`duplicate username: ${username}`);
return;
}
// 아직 가입되지 않은 username인 경우 db에 저장
// KEY = username, VALUE = { name, password }
const newUser = {
username,
name,
password,
};
await createUser({
username,
name,
password,
});
// db에 저장된 user 객체를 문자열 형태로 변환하여 쿠키에 저장
res.cookie(USER_COOKIE_KEY, JSON.stringify(newUser));
// 가입 완료 후, 루트 페이지로 이동
res.redirect('/');
});
app.post('/login', (req, res) => {
const { username, password } = req.body;
const user = await fetchUser(username);
// 가입 안 된 username인 경우
if (!user) {
res.status(400).send(`not registered username: ${username}`);
return;
}
// 비밀번호가 틀렸을 경우
if (password !== user.password) {
res.status(400).send('incorrect password');
return;
}
// db에 저장된 user 객체를 문자열 형태로 변환하여 쿠키에 저장
res.cookie(USER_COOKIE_KEY, JSON.stringify(user));
// 로그인(쿠키 발급) 후, 루트 페이지로 이동
res.redirect('/');
});
app.get('/logout', (req, res) => {
// 쿠키 삭제 후 루트 페이지로 이동
res.clearCookie(USER_COOKIE_KEY);
res.redirect('/');
});
app.listen(3000, () => {
console.log('server is running at 3000');
});
코드를 실행하고 회원가입을 진행해보자.
아까와 동일하게 처음에 루트 페이지로 접속하면 로그인이 되지 않은 상태다.
아까와 똑같은 데이터를 입력한다.
Sign Up 버튼을 누르면 루트 페이지로 이동되고, 쿠키에 담긴 사용자 정보가 그대로 출력된다.
이제 서버를 재실행해보자.
아까와 달리, 서버를 재실행 했음에도 불구하고 로그인이 된 상태로 나오는 것을 볼 수 있다.
users.json
[{"username":"myID","name":"abc","password":"1"}]
users.json
파일을 확인해보면 회원가입에 사용된 사용자 정보가 그대로 담겨 있다.
덕분에 서버를 재실행해도 사용자 정보가 유지될 수 있었다.
더이상 사용하고 싶지 않은 웹 서비스가 있을 때, 회원탈퇴 기능이 매우 어렵게 되어 있는 경우가 있다.
메일을 보내고, 답장이 오면 본인이 맞다고 인증하고 또 수십일을 기다려야 하는 그런 귀찮은 경험이 떠오른다.
이런 번거로움을 없애기 위해 즉시 회원탈퇴를 할 수 있는 기능도 한 번 만들어보자.
// ...
app.get('/', async (req, res) => {
// 'user'라는 쿠키 데이터를 가져옴
// 쿠키가 존재하지 않을 경우 로그인이 되지 않았다는 뜻
const userCookie = req.cookies[USER_COOKIE_KEY];
if (userCookie) {
// 쿠키가 존재하는 경우, 쿠키 VALUE를 JS 객체로 변환
const userData = JSON.parse(userCookie);
// user 객체에 저장된 username이 db에 존재하는 경우,
// 유효한 user이며 로그인이 잘 되어 있다는 뜻.
const user = await fetchUser(userData.username);
if (user) {
// JS 객체로 변환된 user 데이터에서 username, name, password를 추출하여 클라이언트에 렌더링
res.status(200).send(`
<a href="/logout">Log Out</a>
<a href="/withdraw">Withdraw</a>
<h1>id: ${userData.username}, name: ${userData.name}, password: ${userData.password}</h1>
`);
return;
}
}
// ...
app.get('/')
라우터에서 로그인이 성공했을 때 보내주는 html 코드 부분에 <a href="/withdraw">Withdraw</a>
를 삽입한다.
단순히 GET /withdraw
로 이동되는 a
태그다.
async function removeUser(username, password) {
const user = await fetchUser(username);
if (user.password === password) {
const users = await fetchAllUsers();
const index = users.findIndex(u => u.username === username);
users.splice(index, 1);
await fs.writeFile(USERS_JSON_FILENAME, JSON.stringify(users));
}
}
그리고 username
, password
를 받아 사용자를 users.json
에서 삭제하는 함수를 작성한다.
app.get('/withdraw', async (req, res) => {
const userCookie = req.cookies[USER_COOKIE_KEY];
const user = JSON.parse(userCookie);
await removeUser(user.username, user.password);
res.clearCookie(USER_COOKIE_KEY);
res.redirect('/');
});
마지막으로 app.get('/withdraw')
라우터 핸들러를 작성한다.
사용자 정보가 담긴 쿠키를 가져와 JS 객체로 파싱한 다음, username
, password
를 추출하여 users.json
파일에서 사용자 정보를 삭제한다.
그리고 쿠키를 제거한 뒤 루트 페이지로 이동시킨다.
사용자 삭제, 로그아웃, 리디렉트가 한 번에 실행되는 로직이다.
Withdraw 버튼을 눌러보자.
// users.json
[{"username":"userID","name":"abc","password":"1"}]
참고로 users.json
은 현재 위와 같이 아까 가입한 한 개의 사용자 정보가 들어있는 상태다.
루트 페이지로 이동되며 로그아웃 된 상태로 표시된다.
// users.json
[]
users.json
도 사용자 정보가 삭제되며 빈 배열이 되었다.
회원탈퇴 기능을 성공적으로 구현했다.
글이 너무 길어졌기 때문에 다음 장으로 넘기도록 하겠다.
전체 코드에서 93번째 글에 async 안 넣으면 에러 납니다