1. MongoDB와 Mongoose: 유연한 NoSQL의 세계
1.1 MongoDB란 무엇인가?
MongoDB는 문서 지향(Document-Oriented) NoSQL 데이터베이스입니다. JSON과 유사한 BSON(Binary JSON) 형식으로 데이터를 저장하며, 스키마가 고정되지 않아 데이터 구조의 변경이 자유롭다는 장점이 있습니다.
주요 특징:
- 스키마리스(Schemaless) 구조: 각 문서(Document)는 서로 다른 필드를 가질 수 있어 유연한 데이터 모델링이 가능합니다.
- 예: 사용자 정보를 저장하는 컬렉션에서 어떤 사용자는 "주소" 필드를, 다른 사용자는 "전화번호" 필드를 가지고 있어도 무방합니다.
- 수평적 확장성(Scalability): 샤딩(Sharding)을 통해 여러 서버에 데이터를 분산 저장하고 관리할 수 있어 대규모 애플리케이션에 적합합니다.
- 고성능: 인덱싱, 복제(Replication) 등의 기능을 통해 빠른 읽기/쓰기 성능을 제공합니다.
1.2 Mongoose: Node.js에서 MongoDB를 더 쉽게
Mongoose는 Node.js 환경에서 MongoDB를 편리하게 사용할 수 있도록 도와주는 ODM(Object Data Modeling) 라이브러리입니다. 스키마 정의, 데이터 유효성 검사, 쿼리 빌딩 등을 간편하게 수행할 수 있습니다.
주요 기능:
- 스키마 정의: 데이터의 구조를 미리 정의하여 데이터의 일관성을 유지합니다.
- 예: 사용자 모델에 "이름"은 문자열, "나이"는 숫자로 정의합니다.
- 데이터 유효성 검사: 입력된 데이터가 스키마에 정의된 형식과 일치하는지 검사합니다.
- 예: "이메일" 필드에 이메일 형식이 아닌 값이 입력되면 오류를 발생시킵니다.
- 미들웨어: 데이터 저장, 수정, 삭제 등의 작업 전후에 특정 함수를 실행하도록 설정합니다.
- 예: 사용자 정보가 저장되기 전에 비밀번호를 암호화하는 미들웨어를 추가합니다.
- 쿼리 빌더: 직관적이고 가독성 높은 쿼리를 작성하도록 돕습니다.
1.3 MongoDB와 Mongoose 사용 예제
1.3.1 설치
npm install mongoose
1.3.2 MongoDB 연결
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/mydatabase', {
useNewUrlParser: true, // MongoDB 연결 문자열 파싱 방식 설정 (최신 방식 사용)
useUnifiedTopology: true, // 새로운 서버 디스커버리 및 모니터링 엔진 사용
})
.then(() => console.log("MongoDB connected")) // 연결 성공 시 메시지 출력
.catch(err => console.error("MongoDB connection error:", err)); // 연결 실패 시 에러 메시지 출력
mongoose.connect()
함수는 MongoDB 서버에 연결합니다.- 첫 번째 인자는 MongoDB 연결 문자열(Connection String)입니다.
mongodb://localhost/mydatabase
는 로컬 서버의mydatabase
데이터베이스에 연결한다는 의미입니다. - 두 번째 인자는 연결 옵션을 설정합니다.
useNewUrlParser
와useUnifiedTopology
는 새로운 연결 방식을 사용하도록 설정하는 옵션입니다. .then()
은 연결이 성공했을 때 실행할 콜백 함수를 정의합니다..catch()
는 연결 중 에러가 발생했을 때 실행할 콜백 함수를 정의합니다.
1.3.3 스키마 및 모델 생성
// 사용자 스키마 정의
const userSchema = new mongoose.Schema({
name: { type: String, required: true }, // 이름 (문자열, 필수)
email: { type: String, required: true, unique: true }, // 이메일 (문자열, 필수, 고유값)
age: { type: Number }, // 나이 (숫자)
address : {type:String}, // 주소 (문자열)
phoneNumber: {type: String} // 전화번호 (문자열)
});
// 사용자 모델 생성 (스키마를 기반으로 모델을 생성)
const User = mongoose.model('User', userSchema);
mongoose.Schema
를 사용하여 데이터의 구조를 정의합니다.name
,email
,age
,address
,phoneNumber
는 필드 이름이고, 객체는 필드의 타입과 제약 조건을 정의합니다.required: true
는 해당 필드가 필수임을 의미합니다.unique: true
는 해당 필드의 값이 고유해야 함을 의미합니다.mongoose.model()
함수는 스키마를 기반으로 모델을 생성합니다. 모델은 데이터베이스 컬렉션과 상호작용하는 데 사용됩니다. 첫 번째 인자는 컬렉션 이름(단수형으로 지정하면 Mongoose가 자동으로 복수형으로 변환합니다. 예: User -> users), 두 번째 인자는 스키마입니다.
1.3.4 데이터 추가 (Create)
const createUser = async () => {
// 새로운 사용자 객체 생성
const user = new User({ name: 'John Doe', email: 'john@example.com', age: 30 });
try {
// 사용자 객체를 데이터베이스에 저장
const savedUser = await user.save();
console.log("User created:", savedUser); // 저장된 사용자 정보 출력
} catch (error) {
console.error("Error creating user:", error); // 에러 발생 시 에러 메시지 출력
}
};
const createAdditionalUser = async () => {
const user = new User({name:'Jane Smith', email:'jane.smith@example.com', age:25, address:'123 Main St'});
try{
const savedUser = await user.save();
console.log('User created:', savedUser);
}catch(error){
console.error('Error creating user:', error);
}
}
const createAnotherUser = async () => {
const user = new User({name:'Peter Jones', email:'peter.jones@example.com', phoneNumber: '555-1234'});
try{
const savedUser = await user.save();
console.log('User created:', savedUser);
}catch(error){
console.error('Error creating user:', error);
}
}
createUser(); // createUser 함수 실행
createAdditionalUser(); // createAdditionalUser 함수 실행
createAnotherUser(); // createAnotherUser 함수 실행
async
함수는 비동기 작업을 처리하기 위해 사용됩니다.new User()
는 사용자 모델의 인스턴스를 생성합니다.user.save()
는 사용자 객체를 데이터베이스에 저장합니다.await
키워드는save()
메서드가 완료될 때까지 기다립니다.try...catch
블록은 에러 처리를 위해 사용됩니다.
1.3.5 데이터 조회 (Read)
const getUsers = async () => {
try {
// 모든 사용자 조회
const users = await User.find({}); // find() 메서드는 조건에 맞는 모든 문서를 찾습니다. 빈 객체 {}는 모든 문서를 의미합니다.
console.log("All users:", users); // 조회된 사용자 정보 출력
} catch (error) {
console.error("Error fetching users:", error); // 에러 발생 시 에러 메시지 출력
}
};
// 특정 이메일을 가진 사용자 조회
const findUserByEmail = async(email) => {
try{
const user = await User.findOne({email: email}); // findOne() 메서드는 조건에 맞는 첫 번째 문서를 찾습니다.
if(user){
console.log('User found by email:', user);
}else{
console.log('User not found with the given email.');
}
}catch(error){
console.error('Error finding user by email:', error);
}
}
// 나이가 30살 이상인 사용자들 조회
const findUsersByAge = async(age) => {
try{
const users = await User.find({age: {$gte: age}}); // $gte는 '보다 크거나 같음(greater than or equal to)'을 의미하는 연산자입니다.
console.log(`Users ${age} and older:`, users);
}catch(error){
console.error(`Error finding users ${age} and older:`, error);
}
}
getUsers(); // getUsers 함수 실행
findUserByEmail('jane.smith@example.com'); // findUserByEmail 함수 실행
findUsersByAge(30); // findUsersByAge 함수 실행
User.find({})
는users
컬렉션의 모든 문서를 조회합니다.await
키워드는find()
메서드가 완료될 때까지 기다립니다.User.findOne({email: email})
는email
필드가 일치하는 첫 번째 문서를 찾습니다.User.find({age: {$gte: age}})
는age
필드가 주어진age
보다 크거나 같은 모든 문서를 찾습니다.
1.3.6 데이터 수정 (Update)
const updateUserAge = async (userId, newAge) => {
try {
// 특정 사용자의 나이 수정
const updatedUser = await User.findByIdAndUpdate(
userId, // 수정할 사용자의 ID
{ age: newAge }, // 수정할 필드와 값
{ new: true } // 업데이트된 문서를 반환하도록 설정
);
console.log("Updated user:", updatedUser); // 수정된 사용자 정보 출력
} catch (error) {
console.error("Error updating user:", error); // 에러 발생 시 에러 메시지 출력
}
};
const updateUserAddress = async (userId, newAddress) => {
try{
const updatedUser = await User.findByIdAndUpdate(
userId,
{address: newAddress},
{new: true}
);
console.log('Updated user:', updatedUser);
}catch(error){
console.error('Error updating user:', error);
}
}
const updateUserNameAndAge = async(userId, newName, newAge) => {
try{
const updatedUser = await User.findByIdAndUpdate(
userId,
{name: newName, age: newAge},
{new: true}
);
console.log('Updated user:', updatedUser);
}catch(error){
console.error('Error updating user:', error);
}
}
// updateUserAge('<USER_ID>', 35); // 실제 USER_ID로 교체하세요.
// updateUserAddress('<USER_ID>', '456 Park Ave'); // updateUserAddress 함수 실행
// updateUserNameAndAge('<USER_ID>', 'Updated Name', 40); // updateUserNameAndAge 함수 실행
User.findByIdAndUpdate()
는 특정 ID를 가진 문서를 찾아 업데이트합니다.- 첫 번째 인자는 업데이트할 문서의 ID입니다.
- 두 번째 인자는 업데이트할 필드와 값을 포함하는 객체입니다.
- 세 번째 인자는 옵션을 설정합니다.
new: true
는 업데이트된 문서를 반환하도록 설정합니다.
1.3.7 데이터 삭제 (Delete)
const deleteUser = async (userId) => {
try {
// 특정 사용자 삭제
const deletedUser = await User.findByIdAndDelete(userId); // findByIdAndDelete() 메서드는 ID를 기준으로 문서를 찾아 삭제합니다.
if (deletedUser) {
console.log(`Deleted User with ID ${userId}`); // 삭제된 사용자 ID 출력
} else {
console.log(`No User found with ID ${userId}`); // 해당 ID를 가진 사용자가 없을 경우 메시지 출력
}
} catch (error) {
console.error(`Error deleting the user: ${error}`); // 에러 발생 시 에러 메시지 출력
}
};
const deleteUserByEmail = async(email) => {
try{
const deletedUser = await User.findOneAndDelete({email: email});
if(deletedUser){
console.log(`Deleted User with email ${email}`);
}else{
console.log(`No user found with email ${email}`);
}
}catch(error){
console.error(`Error deleting user by email: ${error}`);
}
}
const deleteAllUsers = async() => {
try{
const result = await User.deleteMany({});
console.log(`${result.deletedCount} user(s) deleted.`);
}catch(error){
console.error('Error deleting all users:', error);
}
}
// deleteUser('<USER_ID>'); // 실제 USER_ID로 교체하세요.
// deleteUserByEmail('peter.jones@example.com'); // deleteUserByEmail 함수 실행
// deleteAllUsers(); // deleteAllUsers 함수 실행
User.findByIdAndDelete()
는 특정 ID를 가진 문서를 삭제합니다.User.findOneAndDelete({email: email})
는 주어진email
과 일치하는 첫 번째 문서를 찾아 삭제합니다.User.deleteMany({})
는 모든 문서를 삭제합니다.
2. MySQL: 관계형 데이터베이스의 정석
2.1 MySQL 소개
MySQL은 세계에서 가장 널리 사용되는 오픈 소스 관계형 데이터베이스 관리 시스템(RDBMS)입니다. 테이블, 행, 열로 구성된 구조화된 데이터를 저장하고 관리하는 데 사용됩니다.
주요 특징:
- 관계형 모델: 데이터를 테이블 간의 관계로 표현합니다.
- SQL(Structured Query Language): 표준화된 쿼리 언어를 사용하여 데이터를 조작합니다.
- ACID 속성: 트랜잭션의 무결성을 보장하는 원자성(Atomicity), 일관성(Consistency), 격리성(Isolation), 영속성(Durability)을 지원합니다.
- 성숙도와 안정성: 오랜 기간 동안 개발되고 사용되어 안정성과 신뢰성이 높습니다.
2.2 Node.js에서 MySQL 사용하기
2.2.1 설치 및 설정
- MySQL 설치: 운영체제에 맞는 MySQL 설치 파일을 다운로드하여 설치합니다.
- 데이터베이스 및 사용자 생성:
-- 새로운 데이터베이스 생성
CREATE DATABASE my_database;
-- 사용자 생성 및 권한 부여
CREATE USER 'my_user'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON my_database.* TO 'my_user'@'localhost';
-- users 테이블 생성
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
age INT
);
2.2.2 mysql
패키지 설치
npm install mysql
2.2.3 MySQL 연결
const mysql = require('mysql');
// 데이터베이스 연결 설정
const connection = mysql.createConnection({
host: 'localhost', // MySQL 서버 주소
user: 'my_user', // 사용자 이름
password: 'password', // 비밀번호
database: 'my_database' // 데이터베이스 이름
});
// 연결 시도
connection.connect((err) => {
if (err) {
console.error('데이터베이스에 연결할 수 없습니다:', err.stack);
return;
}
console.log('데이터베이스에 성공적으로 연결되었습니다.');
});
mysql.createConnection()
함수는 MySQL 서버에 연결하기 위한 연결 객체를 생성합니다.host
,user
,password
,database
는 연결에 필요한 정보를 설정합니다.connection.connect()
함수는 연결을 시도합니다. 콜백 함수는 연결 결과에 따라 실행됩니다.
2.3 쿼리 실행
2.3.1 SELECT 쿼리 (데이터 조회)
// users 테이블에서 모든 데이터 조회
connection.query('SELECT * FROM users', (error, results) => {
if (error) throw error; // 에러 발생 시 에러 처리
// 결과 출력 (results는 조회된 데이터를 배열 형태로 담고 있습니다.)
console.log(results);
});
// 특정 ID를 가진 사용자 조회
connection.query('SELECT * FROM users WHERE id = ?', [1], (error, results) => {
if(error) throw error;
console.log('User with ID 1:', results);
});
// 이름에 'John'이 포함된 사용자 조회
connection.query("SELECT * FROM users WHERE name LIKE '%John%'", (error, results) => {
if(error) throw error;
console.log("Users with 'John' in their name:", results);
});
connection.query()
함수는 SQL 쿼리를 실행합니다.- 첫 번째 인자는 실행할 SQL 쿼리 문자열입니다.
- 두 번째 인자는 쿼리 실행 결과에 따라 실행될 콜백 함수입니다.
error
는 에러 객체,results
는 쿼리 결과입니다. ?
는 플레이스홀더로, 두 번째 인자로 전달된 배열의 값으로 대체됩니다.LIKE
연산자는 패턴 매칭을 위해 사용됩니다.%
는 임의의 문자열을 의미합니다.
2.3.2 INSERT 쿼리 (데이터 추가)
// users 테이블에 새로운 사용자 추가
const newUser = { name: 'John Doe', email: 'john.doe@example.com', age: 30 };
connection.query('INSERT INTO users SET ?', newUser, (error, results) => {
if (error) throw error; // 에러 발생 시 에러 처리
console.log(`사용자가 추가되었습니다! ID: ${results.insertId}`); // 추가된 사용자의 ID 출력
});
// 여러 사용자 한 번에 추가
const newUsers = [
{name: 'Alice Smith', email: 'alice.smith@example.com', age: 25},
{name: 'Bob Johnson', email: 'bob.johnson@example.com', age: 35}
];
connection.query('INSERT INTO users (name, email, age) VALUES ?', [newUsers.map(user => [user.name, user.email, user.age])], (error, results) => {
if(error) throw error;
console.log(`${results.affectedRows} users added.`);
});
// 이메일 주소를 입력받아 사용자 추가
const insertUserByEmail = (email) => {
const user = {name: 'Unknown', email: email, age: null};
connection.query('INSERT INTO users SET ?', user, (error, results) => {
if(error) throw error;
console.log(`User added with email ${email}. ID: ${results.insertId}`);
});
}
// insertUserByEmail('test@example.com');
INSERT INTO users SET ?
구문은?
부분에newUser
객체의 값을 매핑하여 데이터를 추가합니다.results.insertId
는 새로 추가된 데이터의 자동 생성된 ID(auto-increment ID)를 포함합니다.- 여러 사용자를 한 번에 추가하려면
VALUES
절에 2차원 배열을 사용합니다. insertUserByEmail
함수는 이메일 주소를 입력받아 사용자를 추가하는 예시입니다.
2.3.3 UPDATE 쿼리 (데이터 수정)
// name이 'John Doe'인 사용자의 나이를 31로 수정
const updatedUser = { age: 31 };
connection.query('UPDATE users SET ? WHERE name = ?', [updatedUser, 'John Doe'], (error, results) => {
if (error) throw error; // 에러 발생 시 에러 처리
console.log(`수정된 행의 개수: ${results.changedRows}`); // 수정된 행의 개수 출력
});
// 특정 ID를 가진 사용자의 이메일 수정
connection.query('UPDATE users SET email = ? WHERE id = ?', ['new.email@example.com', 2], (error, results) => {
if(error) throw error;
console.log(`User with ID 2's email updated. Rows affected: ${results.changedRows}`);
});
// 나이가 30 이상인 모든 사용자의 나이를 1 증가
connection.query('UPDATE users SET age = age + 1 WHERE age >= 30', (error, results) => {
if(error) throw error;
console.log(`Ages updated for users 30 and older. Rows affected: ${results.changedRows}`);
});
UPDATE users SET ? WHERE name = ?
구문은?
부분에 각각updatedUser
객체와'John Doe'
값을 매핑하여 데이터를 수정합니다.results.changedRows
는 수정된 행의 개수를 포함합니다.
2.3.4 DELETE 쿼리 (데이터 삭제)
// name이 'John Doe'인 사용자 삭제
connection.query('DELETE FROM users WHERE name = ?', ['John Doe'], (error, results) => {
if (error) throw error; // 에러 발생 시 에러 처리
console.log(`삭제된 행의 개수: ${results.affectedRows}`); // 삭제된 행의 개수 출력
});
// 특정 ID를 가진 사용자 삭제
connection.query('DELETE FROM users WHERE id = ?', [3], (error, results) => {
if(error) throw error;
console.log(`User with ID 3 deleted. Rows affected: ${results.affectedRows}`);
});
// 나이가 35 이상인 모든 사용자 삭제
connection.query('DELETE FROM users WHERE age >= 35', (error, results) => {
if(error) throw error;
console.log(`Users 35 and older deleted. Rows affected: ${results.affectedRows}`);
});
DELETE FROM users WHERE name = ?
구문은?
부분에'John Doe'
값을 매핑하여 데이터를 삭제합니다.results.affectedRows
는 삭제된 행의 개수를 포함합니다.
2.4 연결 종료
// 데이터베이스 연결 종료
connection.end((err) => {
if (err) {
return console.error('종료 중 오류 발생:', err.message);
}
console.log('데이터베이스와의 연결이 종료되었습니다.');
});
connection.end()
함수는 데이터베이스 연결을 종료합니다.- 모든 쿼리 실행이 완료된 후 연결을 종료하는 것이 좋습니다.
3. PostgreSQL: 강력하고 유연한 오픈 소스 RDBMS
3.1 PostgreSQL 소개
PostgreSQL은 객체 관계형 데이터베이스 관리 시스템(ORDBMS)입니다. 강력한 기능, 안정성, 그리고 오픈 소스 라이선스로 인해 엔터프라이즈급 애플리케이션에 자주 사용됩니다.
주요 특징:
- 객체 관계형 모델: 관계형 데이터베이스의 특징에 객체 지향 개념을 더하여 복잡한 데이터 구조를 표현할 수 있습니다.
- 확장성: 다양한 데이터 타입, 함수, 연산자 등을 추가하여 기능을 확장할 수 있습니다.
- 고급 기능: JSON 데이터 타입 지원, 지리 정보 시스템(GIS) 기능, 전문 검색(Full-Text Search) 등 다양한 고급 기능을 제공합니다.
- ACID 속성: 트랜잭션의 무결성을 보장합니다.
3.2 Node.js에서 PostgreSQL 사용하기
3.2.1 설치 및 설정
- PostgreSQL 설치: 운영체제에 맞는 PostgreSQL 설치 파일을 다운로드하여 설치합니다.
- 데이터베이스 생성:
CREATE DATABASE mydatabase;
3.2.2 pg
패키지 설치
npm install pg
3.2.3 PostgreSQL 연결
const { Client } = require('pg');
// PostgreSQL 클라이언트 생성
const client = new Client({
user: 'your_username', // 사용자 이름
host: 'localhost', // PostgreSQL 서버 주소
database: 'mydatabase', // 데이터베이스 이름
password: 'your_password', // 비밀번호
port: 5432, // 포트 번호 (기본값: 5432)
});
// 연결 시도
client.connect()
.then(() => console.log("Connected to the database")) // 연결 성공 시 메시지 출력
.catch(err => console.error("Connection error", err.stack)); // 연결 실패 시 에러 메시지 출력
new Client()
는 PostgreSQL 서버에 연결하기 위한 클라이언트 객체를 생성합니다.user
,host
,database
,password
,port
는 연결에 필요한 정보를 설정합니다.client.connect()
는 연결을 시도합니다..then()
과.catch()
는 각각 연결 성공과 실패 시 실행할 콜백 함수를 정의합니다.
3.3 CRUD 작업 수행
3.3.1 데이터 삽입 (Create)
const insertData = async () => {
// 사용자 삽입 쿼리 (플레이스홀더 사용)
const queryText = "INSERT INTO users(name, age) VALUES($1, $2)";
const values = ['Alice', 30]; // 플레이스홀더에 매핑할 값
try {
const res = await client.query(queryText, values); // 쿼리 실행
console.log("Data inserted:", res.rowCount); // 삽입된 행의 개수 출력
} catch (err) {
console.error(err); // 에러 발생 시 에러 메시지 출력
}
};
const insertMultipleUsers = async() => {
const queryText = "INSERT INTO users(name, age) VALUES($1, $2), ($3, $4), ($5, $6)";
const values = ['Bob', 28, 'Charlie', 35, 'David', 40];
try{
const res = await client.query(queryText, values);
console.log(`${res.rowCount} users inserted.`);
}catch(err){
console.error(err);
}
}
const insertUserWithEmail = async(name, email) => {
const queryText = "INSERT INTO users(name, email) VALUES($1, $2)";
const values = [name, email];
try{
const res = await client.query(queryText, values);
console.log(`User ${name} with email ${email} inserted.`);
}catch(err){
console.error(err);
}
}
// insertData(); // insertData 함수 실행
// insertMultipleUsers();
// insertUserWithEmail('Emily', 'emily@example.com');
$1
,$2
는 플레이스홀더로,values
배열의 값들이 순서대로 매핑됩니다.client.query()
는 쿼리를 실행합니다.await
키워드는 쿼리가 완료될 때까지 기다립니다.res.rowCount
는 삽입된 행의 개수를 포함합니다.insertMultipleUsers
함수는 여러 사용자를 한 번에 삽입하는 예시입니다.insertUserWithEmail
함수는 이름과 이메일을 입력받아 사용자를 추가하는 예시입니다.
3.3.2 데이터 조회 (Read)
const getUsers = async () => {
// 모든 사용자 조회 쿼리
const queryText = "SELECT * FROM users";
try {
const res = await client.query(queryText); // 쿼리 실행
console.log(res.rows); // 모든 사용자 정보 출력 (res.rows는 조회된 데이터를 배열 형태로 담고 있습니다.)
} catch (err) {
console.error(err); // 에러 발생 시 에러 메시지 출력
}
};
const getUserById = async(userId) => {
const queryText = "SELECT * FROM users WHERE id = $1";
const values = [userId];
try{
const res = await client.query(queryText, values);
if(res.rows.length > 0){
console.log('User found:', res.rows[0]);
}else{
console.log(`No user found with ID ${userId}`);
}
}catch(err){
console.error(err);
}
}
const getUsersByAgeRange = async(minAge, maxAge) => {
const queryText = "SELECT * FROM users WHERE age BETWEEN $1 AND $2";
const values = [minAge, maxAge];
try{
const res = await client.query(queryText, values);
console.log(`Users between ${minAge} and ${maxAge}:`, res.rows);
}catch(err){
console.error(err);
}
}
// getUsers(); // getUsers 함수 실행
// getUserById(1);
// getUsersByAgeRange(25, 35);
SELECT * FROM users
는users
테이블의 모든 데이터를 조회하는 쿼리입니다.res.rows
는 조회된 데이터를 배열 형태로 포함합니다.getUserById
함수는 특정 ID를 가진 사용자를 조회하는 예시입니다.getUsersByAgeRange
함수는 특정 나이 범위에 속하는 사용자를 조회하는 예시입니다.
3.3.3 데이터 수정 (Update)
const updateUserAge = async (name, newAge) => {
// 사용자 나이 수정 쿼리 (플레이스홀더 사용)
const queryText = "UPDATE users SET age=$1 WHERE name=$2";
const values = [newAge, name]; // 플레이스홀더에 매핑할 값
try {
const res = await client.query(queryText, values); // 쿼리 실행
console.log(`Updated ${res.rowCount} user(s).`); // 수정된 행의 개수 출력
} catch (err) {
console.error(err); // 에러 발생 시 에러 메시지 출력
}
};
const updateUserName = async(oldName, newName) => {
const queryText = "UPDATE users SET name=$1 WHERE name=$2 RETURNING *";
const values = [newName, oldName];
try{
const res = await client.query(queryText, values);
if(res.rows.length > 0){
console.log('Updated user:', res.rows[0]);
}else{
console.log(`No user found with name ${oldName}`);
}
}catch(err){
console.error(err);
}
}
const incrementUserAgeById = async(userId) => {
const queryText = "UPDATE users SET age = age + 1 WHERE id = $1";
const values = [userId];
try{
const res = await client.query(queryText, values);
console.log(`Incremented age for ${res.rowCount} user(s).`);
}catch(err){
console.error(err);
}
}
// updateUserAge('Alice', 31); // updateUserAge 함수 실행 (Alice의 나이를 31로 수정)
// updateUserName('Bob', 'Robert');
// incrementUserAgeById(2);
UPDATE users SET age=$1 WHERE name=$2
는name
이$2
인 사용자의age
를$1
로 수정하는 쿼리입니다.updateUserName
함수는 이름을 변경하고 변경된 사용자 정보를 반환하는 예시입니다.RETURNING *
은 수정된 행을 반환하도록 합니다.incrementUserAgeById
함수는 특정 ID를 가진 사용자의 나이를 1 증가시키는 예시입니다.
3.3.4 데이터 삭제 (Delete)
const deleteUserByName = async (name) => {
// 사용자 삭제 쿼리 (플레이스홀더 사용)
const queryText = "DELETE FROM users WHERE name=$1";
const values = [name]; // 플레이스홀더에 매핑할 값
try {
const res = await client.query(queryText, values); // 쿼리 실행
console.log(`Deleted ${res.rowCount} user(s).`); // 삭제된 행의 개수 출력
} catch (err) {
console.error(err); // 에러 발생 시 에러 메시지 출력
}
};
const deleteUserById = async(userId) => {
const queryText = "DELETE FROM users WHERE id=$1 RETURNING *";
const values = [userId];
try{
const res = await client.query(queryText, values);
if(res.rows.length > 0){
console.log('Deleted user:', res.rows[0]);
}else{
console.log(`No user found with ID ${userId}`);
}
}catch(err){
console.error(err);
}
}
const deleteAllUsers = async() => {
const queryText = "DELETE FROM users";
try{
const res = await client.query(queryText);
console.log(`Deleted all users. Total: ${res.rowCount}`);
}catch(err){
console.error(err);
}
}
// deleteUserByName('Alice'); // deleteUserByName 함수 실행 (Alice 삭제)
// deleteUserById(4);
// deleteAllUsers();
DELETE FROM users WHERE name=$1
는name
이$1
인 사용자를 삭제하는 쿼리입니다.deleteUserById
함수는 특정 ID를 가진 사용자를 삭제하고 삭제된 사용자 정보를 반환하는 예시입니다.deleteAllUsers
함수는 모든 사용자를 삭제하는 예시입니다.
3.4 연결 종료 및 추가 고려 사항
3.4.1 연결 종료
client.end()
.then(()=> console.log('Client has disconnected'))
.catch(err => console.error('Error during disconnection', err.stack));
client.end()
함수는 클라이언트 연결을 종료합니다..then()
과.catch()
는 각각 연결 종료 성공과 실패 시 실행할 콜백 함수를 정의합니다.
3.4.2 환경 변수 관리
데이터베이스 연결 정보와 같은 민감한 정보는 코드에 직접 작성하지 않고, 환경 변수를 통해 안전하게 관리하는 것이 좋습니다.
.env
파일을 생성하고 환경 변수를 설정합니다.
DB_USER=your_username
DB_HOST=localhost
DB_DATABASE=mydatabase
DB_PASSWORD=your_password
DB_PORT=5432
dotenv
패키지를 설치합니다.
npm install dotenv
- Node.js 코드에서
dotenv
를 사용하여 환경 변수를 로드합니다.
require('dotenv').config(); // .env 파일에서 환경 변수 로드
const client = new Client({
user: process.env.DB_USER, // 환경 변수 사용
host: process.env.DB_HOST,
database: process.env.DB_DATABASE,
password: process.env.DB_PASSWORD,
port: process.env.DB_PORT,
});
3.4.3 커넥션 풀링(Connection Pooling)
빈번한 데이터베이스 연결 생성 및 해제는 성능 저하를 야기할 수 있습니다. pg
라이브러리의 Pool
을 사용하면 커넥션 풀을 생성하여 연결을 재사용함으로써 성능을 향상시킬 수 있습니다.
const { Pool } = require('pg');
const pool = new Pool({
user: process.env.DB_USER,
host: process.env.DB_HOST,
database: process.env.DB_DATABASE,
password: process.env.DB_PASSWORD,
port: process.env.DB_PORT,
max: 20, // 최대 커넥션 수 (기본값: 10)
idleTimeoutMillis: 30000, // 유휴 상태의 커넥션 유지 시간 (밀리초, 기본값: 30000)
connectionTimeoutMillis: 2000, // 커넥션 타임아웃 (밀리초, 기본값: 0, 무제한)
});
// 쿼리 실행 예제 (pool.query() 사용)
pool.query('SELECT NOW()', (err, res) => {
if (err) {
console.error(err);
} else {
console.log('Database time:', res.rows[0].now);
}
});
// 풀 종료 (애플리케이션 종료 시)
// pool.end();
3.4.4 트랜잭션 관리
여러 쿼리를 하나의 논리적 단위로 묶어서 실행해야 하는 경우, 트랜잭션을 사용해야 합니다. 트랜잭션을 사용하면 모든 쿼리가 성공적으로 완료되거나, 하나라도 실패할 경우 모든 쿼리가 롤백되어 데이터의 일관성을 보장할 수 있습니다.
const performTransaction = async () => {
const client = await pool.connect(); // 풀에서 클라이언트 가져오기
try {
await client.query('BEGIN'); // 트랜잭션 시작
// 트랜잭션 내에서 여러 쿼리 실행
await client.query("INSERT INTO users(name, age) VALUES($1, $2)", ['Transaction User 1', 25]);
await client.query("UPDATE users SET age = $1 WHERE name = $2", [30, 'Transaction User 2']);
await client.query('COMMIT'); // 트랜잭션 커밋 (모든 쿼리 성공 시)
console.log('Transaction completed successfully.');
} catch (err) {
await client.query('ROLLBACK'); // 트랜잭션 롤백 (하나라도 쿼리 실패 시)
console.error('Transaction failed:', err);
} finally {
client.release(); // 클라이언트를 풀에 반환
}
};
performTransaction();
3.4.5 보안
SQL 인젝션과 같은 보안 위협을 방지하기 위해 항상 사용자 입력을 검증하고, Prepared Statement (플레이스홀더 사용)를 사용하는 것이 중요합니다. 또한, 데이터베이스 사용자에게 필요한 최소한의 권한만 부여하는 것도 중요합니다.
결론
Node.js는 MongoDB, MySQL, PostgreSQL과 같은 다양한 데이터베이스와 효과적으로 연동할 수 있는 강력한 플랫폼입니다. 각 데이터베이스의 특징과 장단점을 이해하고, 애플리케이션의 요구 사항에 맞는 데이터베이스를 선택하는 것이 중요합니다. 본 가이드에서 제공된 예제 코드와 설명을 참고하여 Node.js와 데이터베이스를 연동하는 데 도움이 되셨기를 바랍니다.
'프로그래밍 > Node.js' 카테고리의 다른 글
Node.js 애플리케이션의 테스트와 디버깅 정복하기: 실전 가이드 (1) | 2025.02.20 |
---|---|
Express.js: 웹 애플리케이션 개발을 위한 강력한 도구 (0) | 2025.02.20 |
Node.js를 활용한 네트워킹: HTTP 서버, 요청 및 응답 처리, 웹 소켓 사용하기 (0) | 2025.02.19 |
Node.js 파일 시스템 정복: 파일, 디렉토리, 스트림, 버퍼까지! (0) | 2025.02.19 |
Node.js 모듈 시스템 정복 가이드: 개념부터 활용까지 (0) | 2025.02.19 |