환경
- Rocky Linux 9.5
- Docker
- Node.js
- MySQL
1. 리눅스 및 도커 설치
록키 리눅스 설치
리눅스 기초 다지기 사전준비 - VirtualBox Rocky 9 설치
개요호스트 기반 가상화 프로그램 중 VirtualBox를 사용하여 실습환경을 구축한다. VirtualBox Download - 링크 Rocky Linux Download - 링크 VirtualBox NetworkVirtualBox는 여러 가상 네트워크 환경을 제공한다.
openstack.tistory.com
록키 리눅스 기본 환경구성
[Rocky Linux] 기본 환경구성
글쓴이가 해당 블로그 내 Rocky Linux 구축 후 기본적으로 설정하는 환경 설정이다. 기대하는 효과로 글쓴이와 동일한 환경으로 구성하여 조금 더 편안하게 실습을 따라올 수 있다. - SELinux, 방화
openstack.tistory.com
도커 설치
Docker 설치하기 [Rocky 9.4]
도커 설치 EPEL 저장소 추가dnf install -y epel-release Docker Repository 추가dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo Docker 패키지 설치dnf install -y docker-ce docker-ce-cli containerd.io Doc
openstack.tistory.com
2. Node.js 및 MySQL 컨테이너 설치
Node.js 및 MySQL 이미지 다운로드
docker pull node:22-alpine
docker pull mysql
Node.js 컨테이너 실행
docker run -it -d -p 80:3000 --name=app node:22-alpine
MySQL 컨테이너 실행
docker run -d --name=db -e MYSQL_ROOT_PASSWORD=password mysql:latest
실행확인

3. MySQL 회원가입 데이터베이스 및 테이블 생성
MySQL 컨테이너 접속
docker exec -it db bash
MySQL 접속
mysql -uroot -p
password

회원가입 데이터베이스 및 테이블 생성
CREATE DATABASE login_db;
USE login_db;
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
테이블 생성 확인

nodejs 에서 mysql 접근 위한 계정 생성
create user node@'%' identified by 'password';
grant all privileges on login_db.* to node@'%';
flush privileges;
mysql 컨테이너 IP 확인
docker inspect db | grep IPAddress

4. Node.js 회원가입 구현
Node.js 컨테이너 접속
docker exec -it app sh
Node.js 프로젝트 폴더 생성 및 초기화
mkdir -p /app/login-system
cd /app/login-system
npm init -y


필요 패키지 설치
npm install express mysql2 bcrypt express-session body-parser ejs
설치 확인
vi package.json

필요 디렉터리 생성
mkdir config
mkdir -p public/css
mkdir views
각 코드를 폴더 안에 넣어준다.
app.js
const express = require('express');
const session = require('express-session');
const bodyParser = require('body-parser');
const bcrypt = require('bcrypt');
const db = require('./config/database');
const app = express();
// 미들웨어 설정
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(session({
secret: 'your_secret_key',
resave: false,
saveUninitialized: true
}));
// View engine 설정
app.set('view engine', 'ejs');
app.use(express.static('public'));
// 라우트 설정
app.get('/', (req, res) => {
res.render('home', { user: req.session.user });
});
app.get('/login', (req, res) => {
res.render('login');
});
app.get('/register', (req, res) => {
res.render('register');
});
// 회원가입 API
app.post('/register', async (req, res) => {
try {
const { username, password, email } = req.body;
// 비밀번호 해시화
const hashedPassword = await bcrypt.hash(password, 10);
// 사용자 등록
const query = 'INSERT INTO users (username, password, email) VALUES (?, ?, ?)';
db.query(query, [username, hashedPassword, email], (err, results) => {
if (err) {
console.error('회원가입 에러:', err);
return res.status(500).json({ error: '회원가입 실패' });
}
res.json({ message: '회원가입 성공!' });
});
} catch (error) {
console.error('서버 에러:', error);
res.status(500).json({ error: '서버 에러' });
}
});
// 로그인 API
app.post('/login', (req, res) => {
const { username, password } = req.body;
const query = 'SELECT * FROM users WHERE username = ?';
db.query(query, [username], async (err, results) => {
if (err) {
console.error('로그인 에러:', err);
return res.status(500).json({ error: '로그인 실패' });
}
if (results.length === 0) {
return res.status(401).json({ error: '사용자를 찾을 수 없습니다' });
}
const user = results[0];
const validPassword = await bcrypt.compare(password, user.password);
if (!validPassword) {
return res.status(401).json({ error: '비밀번호가 일치하지 않습니다' });
}
req.session.user = {
id: user.id,
username: user.username,
email: user.email
};
res.json({ message: '로그인 성공!' });
});
});
// 로그아웃 API
app.post('/logout', (req, res) => {
req.session.destroy((err) => {
if (err) {
return res.status(500).json({ error: '로그아웃 실패' });
}
res.json({ message: '로그아웃 성공!' });
});
});
// 서버 시작
const port = 3000;
app.listen(port, () => {
console.log(`서버가 포트 ${port}에서 실행 중입니다.`);
});
config/database.js
const mysql = require('mysql2');
const connection = mysql.createConnection({
host: '172.17.0.3',
user: 'node', // MySQL 사용자 이름
password: 'password', // MySQL 비밀번호
database: 'login_db' // 사용할 데이터베이스 이름
});
connection.connect((err) => {
if (err) {
console.error('데이터베이스 연결 실패:', err);
return;
}
console.log('데이터베이스 연결 성공');
});
module.exports = connection;
위에 host IP 는 우리가 확인한 mysql 컨테이너의 IP 로 설정해야 한다.
views/home.ejs
<!DOCTYPE html>
<html>
<head>
<title>홈페이지</title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<div class="container">
<h1>환영합니다!</h1>
<% if (typeof user !== 'undefined' && user) { %>
<p>안녕하세요, <%= user.username %>님!</p>
<button onclick="logout()" class="btn">로그아웃</button>
<% } else { %>
<div class="button-group">
<a href="/login" class="btn">로그인</a>
<a href="/register" class="btn">회원가입</a>
</div>
<% } %>
</div>
<script>
async function logout() {
try {
const response = await fetch('/logout', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
const data = await response.json();
if (response.ok) {
alert('로그아웃 성공!');
window.location.href = '/'; // 홈페이지로 리다이렉트
} else {
alert(data.error || '로그아웃 실패');
}
} catch (error) {
alert('서버 오류가 발생했습니다.');
}
}
</script>
</body>
</html>
views/login.ejs
<!DOCTYPE html>
<html>
<head>
<title>로그인</title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<div class="container">
<h1>로그인</h1>
<form id="loginForm" class="form">
<div class="form-group">
<label for="username">아이디:</label>
<input type="text" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password">비밀번호:</label>
<input type="password" id="password" name="password" required>
</div>
<button type="submit" class="btn">로그인</button>
</form>
<p>계정이 없으신가요? <a href="/register">회원가입</a></p>
</div>
<script>
document.getElementById('loginForm').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = {
username: document.getElementById('username').value,
password: document.getElementById('password').value
};
try {
const response = await fetch('/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
});
const data = await response.json();
if (response.ok) {
alert('로그인 성공!');
window.location.href = '/';
} else {
alert(data.error || '로그인 실패');
}
} catch (error) {
alert('서버 오류가 발생했습니다.');
}
});
</script>
</body>
</html>
views/register.ejs
<!DOCTYPE html>
<html>
<head>
<title>회원가입</title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<div class="container">
<h1>회원가입</h1>
<form id="registerForm" class="form">
<div class="form-group">
<label for="username">아이디:</label>
<input type="text" id="username" name="username" required>
</div>
<div class="form-group">
<label for="email">이메일:</label>
<input type="email" id="email" name="email" required>
</div>
<div class="form-group">
<label for="password">비밀번호:</label>
<input type="password" id="password" name="password" required>
</div>
<button type="submit" class="btn">회원가입</button>
</form>
<p>이미 계정이 있으신가요? <a href="/login">로그인</a></p>
</div>
<script>
document.getElementById('registerForm').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = {
username: document.getElementById('username').value,
email: document.getElementById('email').value,
password: document.getElementById('password').value
};
try {
const response = await fetch('/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
});
const data = await response.json();
if (response.ok) {
alert('회원가입 성공!');
window.location.href = '/login';
} else {
alert(data.error || '회원가입 실패');
}
} catch (error) {
alert('서버 오류가 발생했습니다.');
}
});
</script>
</body>
</html>
public/css/style.css
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f5f5f5;
}
.container {
max-width: 500px;
margin: 50px auto;
padding: 20px;
background-color: white;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
h1 {
text-align: center;
color: #333;
}
.form {
display: flex;
flex-direction: column;
gap: 15px;
}
.form-group {
display: flex;
flex-direction: column;
gap: 5px;
}
label {
font-weight: bold;
color: #555;
}
input {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}
.btn {
background-color: #007bff;
color: white;
padding: 10px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
text-align: center;
text-decoration: none;
}
.btn:hover {
background-color: #0056b3;
}
.button-group {
display: flex;
gap: 10px;
justify-content: center;
margin-top: 20px;
}
p {
text-align: center;
margin-top: 20px;
}
a {
color: #007bff;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
서버 실행
/app/login-system # node app.js

이제 로컬OS 서버 IP의 80으로 접속해보자



회원가입 후 MySQL 테이블 확인
