JavaScript 데이터베이스 통합: 원활한 데이터 관리와 성능을 위한 8가지 필수 메서드
Source: Dev.to
풀(Pool)로 데이터베이스 연결 관리하기
요청마다 새 연결을 만드는 것은 느리고 비효율적입니다. 연결 풀은 사용 가능한 연결을 미리 만들어 두었다가 체크아웃하고 사용한 뒤 다시 풀에 반환할 수 있게 합니다.
import mysql from 'mysql2/promise';
class DatabasePool {
constructor() {
this.pool = mysql.createPool({
host: 'localhost',
user: 'app_user',
password: 'secure_password',
database: 'my_app_db',
waitForConnections: true,
connectionLimit: 20,
queueLimit: 0
});
}
async query(sql, values) {
let connection;
try {
connection = await this.pool.getConnection();
const [results] = await connection.execute(sql, values);
return results;
} catch (err) {
console.error('Database query failed:', err.message);
// Add retry logic for specific errors if needed
throw err;
} finally {
if (connection) connection.release();
}
}
}
export default new DatabasePool();
객체 지향 데이터 접근을 위한 ORM 사용
객체‑관계 매퍼(ORM)를 사용하면 데이터베이스 행을 JavaScript 객체와 클래스 형태로 다룰 수 있어, 순수 SQL 문자열을 직접 작성할 필요가 줄어듭니다.
// user.model.js
import { Model, DataTypes } from 'sequelize';
import sequelize from '../config/database.js';
import Post from './post.model.js';
class User extends Model {}
User.init({
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: { isEmail: true }
},
fullName: {
type: DataTypes.STRING,
allowNull: false
},
isActive: {
type: DataTypes.BOOLEAN,
defaultValue: true
}
}, {
sequelize,
modelName: 'User',
tableName: 'users'
});
// Relationships
User.hasMany(Post, { foreignKey: 'authorId' });
Post.belongsTo(User, { foreignKey: 'authorId' });
export default User;
예시 작업
// Create a new user
const newUser = await User.create({
email: 'hello@example.com',
fullName: 'Jane Developer'
});
console.log(newUser.id);
// Find a user with their posts
const user = await User.findOne({
where: { email: 'hello@example.com' },
include: Post
});
console.log(user.fullName);
user.Posts.forEach(post => console.log(post.title));
// Update a user
await user.update({ isActive: false });
유연하고 안전한 SQL 생성을 위한 Query Builder
ORM이 너무 무겁게 느껴질 때, 쿼리 빌더는 체이닝 가능한 API를 제공해 파라미터화된 SQL을 생성하고, 인젝션 공격을 방지합니다.
import knex from 'knex';
const db = knex({
client: 'mysql2',
connection: {
host: 'localhost',
user: 'app_user',
password: 'secure_password',
database: 'my_app_db'
}
});
// Dynamic user search
async function searchUsers(filters = {}) {
let query = db('users')
.select('id', 'fullName', 'email', 'created_at');
if (filters.name) {
query = query.where('fullName', 'like', `%${filters.name}%`);
}
if (filters.active !== undefined) {
query = query.where('isActive', filters.active);
}
if (filters.minDate) {
query = query.where('created_at', '>=', filters.minDate);
}
return await query
.orderBy('created_at', 'desc')
.limit(50);
}
// Usage
const activeUsers = await searchUsers({ active: true, name: 'Jane' });
마이그레이션으로 스키마 변경 관리하기
마이그레이션은 버전‑관리된 스크립트를 제공해 데이터베이스 스키마를 안전하게 진화시킬 수 있게 합니다.
// migrations/20230915_add_bio_to_users.js
export async function up(knex) {
await knex.schema.table('users', (table) => {
table.text('bio').nullable().after('fullName'); // Add column
table.index(['isActive'], 'idx_users_active'); // Add index
});
}
export async function down(knex) {
await knex.schema.table('users', (table) => {
table.dropIndex('idx_users_active');
table.dropColumn('bio');
});
}
마이그레이션을 실행하면 새로운 컬럼과 인덱스가 적용되고, 롤백하면 변경 사항이 되돌아갑니다.
영속화 전 데이터 검증하기
데이터베이스에 도달하기 전에 JavaScript에서 입력을 검증하면 더 명확한 오류 메시지를 제공하고 비즈니스 규칙을 초기에 강제할 수 있습니다.
import Joi from 'joi';
const userSchema = Joi.object({
email: Joi.string().email().required(),
fullName: Joi.string().min(2).max(100).required(),
age: Joi.number().integer().min(13).max(120).optional(),
website: Joi.string().uri().allow('').optional()
});
레코드를 생성하거나 업데이트하기 전에 userSchema.validate(data)(또는 validateAsync)를 사용하세요.