해당 글은, 제가 작성한 Discord.js 보일러플레이트를 기반으로 합니다. 해당 보일러픝레이트는 다음에라도 봇을 빠르게 만들고 싶으실 때 사용하실 수 있습니다. Discord.js TypeScript Boilerplate
안녕하세요! 지난 시간에는 역할과 권한 체크를 통해 봇의 보안을 강화하는 방법을 알아봤습니다. 이번 시간에는 드디어 많은 분들이 기다리셨을 데이터베이스 연동, 그중에서도 Prisma를 사용해서 SQLite와 MySQL을 다루는 방법을 배워보겠습니다.
봇을 운영하다 보면 데이터를 저장하고 불러와야 하는 경우가 정말 많습니다. 예를 들어 유저별 레벨 시스템, 경고 횟수, 서버별 설정 등등... 이런 데이터를 효과적으로 관리하려면 데이터베이스가 필수적이죠. Prisma는 타입스크립트와 아주 잘 맞는 ORM(Object-Relational Mapper)이라서, 복잡한 SQL 쿼리 없이도 자바스크립트/타입스크립트 코드로 데이터베이스를 다룰 수 있게 해줍니다.
Prisma란 무엇일까요?
Prisma는 데이터베이스 작업을 쉽게 만들어주는 도구입니다. 우리가 직접 SQL 쿼리문을 작성하는 대신, Prisma가 제공하는 API를 사용해서 마치 객체를 다루듯이 데이터베이스와 상호작용할 수 있게 해줘요. 특히 타입스크립트와 함께 사용하면 자동완성 기능이나 타입 체크 같은 이점을 누릴 수 있어서 개발 경험이 훨씬 좋아집니다.
Prisma는 크게 세 가지 요소로 구성됩니다:
- Prisma Client: 자동 생성되는 쿼리 빌더입니다. 타입스크립트 환경에서 데이터베이스에 접근할 때 사용합니다.
- Prisma Migrate: 데이터베이스 스키마를 관리하고 마이그레이션하는 도구입니다. 스키마 변경 사항을 안전하게 데이터베이스에 적용할 수 있게 도와줍니다.
- Prisma Studio: 데이터베이스의 데이터를 시각적으로 보고 편집할 수 있는 GUI 도구입니다.
Prisma 프로젝트에 추가하기
자, 그럼 우리 봇 프로젝트에 Prisma를 설치하고 설정해봅시다.
먼저 필요한 패키지들을 설치해야 합니다. 터미널을 열고 프로젝트 루트 디렉토리에서 다음 명령어를 실행해주세요.
npm install prisma --save-dev
npm install @prisma/client
prisma
는 Prisma CLI 도구를 설치하는 것이고, @prisma/client
는 실제로 코드에서 사용할 Prisma Client 라이브러리입니다.
설치가 완료되면 Prisma를 초기화해줍니다.
npx prisma init
이 명령어를 실행하면 프로젝트 루트에 prisma
라는 폴더가 생기고, 그 안에 schema.prisma
파일과 .env
파일이 생성됩니다. .env
파일에는 데이터베이스 연결 정보가 들어갈 거예요. schema.prisma
파일은 데이터베이스 스키마를 정의하는 곳입니다.
schema.prisma
파일 설정하기
이제 prisma/schema.prisma
파일을 열어서 데이터베이스 연결 설정을 해봅시다. Prisma는 다양한 데이터베이스를 지원하는데요, 이번 튜토리얼에서는 개발 편의성을 위해 SQLite를 먼저 사용해보고, 나중에 MySQL로 변경하는 방법도 알아볼게요.
SQLite 설정
SQLite는 별도의 서버 설치 없이 파일 기반으로 동작하는 가벼운 데이터베이스입니다. 간단한 테스트나 로컬 개발 환경에 아주 유용하죠.
schema.prisma
파일의 datasource db
부분을 다음과 같이 수정합니다.
// schema.prisma
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
그리고 프로젝트 루트의 .env
파일을 열어서 DATABASE_URL
을 설정해줍니다. SQLite의 경우, 데이터베이스 파일의 경로를 지정해주면 됩니다.
# .env
DATABASE_URL="file:./dev.db"
이렇게 하면 프로젝트 루트에 dev.db
라는 파일로 SQLite 데이터베이스가 생성될 거예요.
MySQL 설정 (나중에 해볼 것)
MySQL을 사용하고 싶다면 datasource db
부분을 이렇게 바꿀 수 있습니다.
// schema.prisma (MySQL 예시)
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
그리고 .env
파일에는 MySQL 연결 정보를 넣어주면 됩니다.
# .env (MySQL 예시)
DATABASE_URL="mysql://USER:PASSWORD@HOST:PORT/DATABASE"
예를 들어 사용자 이름이 root
, 비밀번호가 password
, 호스트가 localhost
, 포트가 3306
, 데이터베이스 이름이 mybotdb
라면 이렇게 되겠죠.
DATABASE_URL="mysql://root:password@localhost:3306/mybotdb"
일단은 SQLite로 진행하고, MySQL 설정은 참고만 해주세요.
첫 번째 모델 만들기
Prisma에서는 데이터베이스 테이블을 '모델(Model)'이라는 개념으로 정의합니다. 예를 들어, 유저의 정보를 저장하는 User
모델을 만들어볼까요?
schema.prisma
파일에 다음 내용을 추가합니다.
// schema.prisma
// ... (datasource db, generator client 부분은 그대로 둡니다)
model User {
id String @id @default(cuid()) // 고유 ID (기본키)
discordId String @unique // 디스코드 유저 ID (고유값)
username String // 디스코드 유저 이름
level Int @default(1) // 레벨 (기본값 1)
xp Int @default(0) // 경험치 (기본값 0)
createdAt DateTime @default(now()) // 생성 시각
updatedAt DateTime @updatedAt // 업데이트 시각
}
각 필드에 대한 설명은 주석을 참고해주세요. @id
는 기본키, @unique
는 고유값, @default()
는 기본값을 의미합니다. @updatedAt
은 레코드가 업데이트될 때마다 자동으로 현재 시각으로 갱신해줍니다.
데이터베이스 마이그레이션
스키마를 정의했으니, 이제 이 변경사항을 실제 데이터베이스에 적용해야 합니다. 이걸 '마이그레이션(Migration)'이라고 해요. Prisma Migrate는 이 과정을 아주 쉽게 만들어줍니다.
터미널에서 다음 명령어를 실행하세요.
npx prisma migrate dev --name init
--name init
은 이번 마이그레이션의 이름을 init
으로 지정한다는 의미입니다. 처음 마이그레이션할 때는 보통 init
이라고 많이 씁니다.
이 명령어를 실행하면 Prisma가 schema.prisma
파일의 변경사항을 감지하고, SQL 마이그레이션 파일을 생성한 다음, 데이터베이스에 적용해줍니다. SQLite를 사용하고 있다면 prisma
폴더 아래에 migrations
폴더가 생기고, 그 안에 마이그레이션 파일과 함께 dev.db
파일도 생성된 것을 볼 수 있을 거예요.
Prisma Client 생성 및 사용 준비
마이그레이션까지 마쳤다면, 이제 Prisma Client를 생성할 차례입니다. Prisma Client는 우리가 정의한 모델(예: User
모델)을 기반으로 타입 세이프한 데이터베이스 접근 코드를 자동으로 만들어줍니다.
터미널에서 다음 명령어를 실행하세요.
npx prisma generate
이 명령어를 실행하면 node_modules/@prisma/client
에 우리 스키마에 맞는 Prisma Client 코드가 생성(또는 업데이트)됩니다. 보통 prisma migrate dev
를 실행하면 자동으로 prisma generate
도 같이 실행되지만, 스키마만 수정하고 마이그레이션을 하지 않았을 경우 등 수동으로 실행해야 할 때도 있습니다.
이제 코드에서 Prisma Client를 사용할 준비가 거의 다 됐습니다!
봇 코드에서 Prisma Client를 사용하려면, 먼저 클라이언트 인스턴스를 만들어야 합니다. 보통 src
폴더 아래에 prisma.ts
같은 파일을 만들어서 관리하는 것이 좋습니다.
src/prisma.ts
파일을 만들고 다음 내용을 작성해주세요.
// src/prisma.ts
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
export default prisma;
이렇게 하면 프로젝트 어디에서든 prisma
객체를 import 해서 사용할 수 있게 됩니다.
봇 명령어에서 Prisma 사용하기
자, 이제 실제로 봇 명령어에서 Prisma를 사용해서 데이터를 읽고 써봅시다. 간단하게 유저가 처음으로 특정 명령어를 사용했을 때 유저 정보를 데이터베이스에 저장하고, 다시 사용하면 정보를 보여주는 명령어를 만들어볼게요.
src/commands
폴더에 profile.ts
라는 이름으로 새 명령어 파일을 만들어봅시다.
// src/commands/profile.ts
import { SlashCommandBuilder } from "discord.js";
import prisma from "../prisma"; // Prisma Client 인스턴스 가져오기
import { Command } from "../interfaces/Command"; // Command 인터페이스 (만약 사용하고 있다면)
export const profile: Command = {
data: new SlashCommandBuilder()
.setName("프로필")
.setDescription("당신의 프로필을 보여주거나 생성합니다."),
async execute(interaction) {
const userId = interaction.user.id;
const username = interaction.user.username;
try {
let user = await prisma.user.findUnique({
where: { discordId: userId },
});
if (!user) {
// 유저가 없으면 새로 생성
user = await prisma.user.create({
data: {
discordId: userId,
username: username,
// level과 xp는 기본값이 설정되어 있으므로 생략 가능
},
});
await interaction.reply(
`환영합니다, ${username}님! 프로필이 생성되었어요. 현재 레벨: ${user.level}, XP: ${user.xp}`
);
} else {
// 유저가 있으면 정보 보여주기
// 경험치를 조금 올려볼까요?
user = await prisma.user.update({
where: { discordId: userId },
data: { xp: user.xp + 10 }, // XP 10 증가
});
await interaction.reply(
`${username}님의 프로필 - 레벨: ${user.level}, XP: ${user.xp}`
);
}
} catch (error) {
console.error("프로필 명령어 처리 중 오류 발생:", error);
await interaction.reply({
content:
"프로필을 처리하는 중 오류가 발생했어요. 나중에 다시 시도해주세요.",
ephemeral: true,
});
}
},
};
이 코드에서는 다음 작업들을 수행합니다:
prisma
인스턴스를 import 합니다.- 명령어를 실행한 유저의
discordId
와username
을 가져옵니다. prisma.user.findUnique()
를 사용해서discordId
로 유저를 검색합니다.- 만약 유저가 존재하지 않으면 (
!user
),prisma.user.create()
를 사용해서 새로운 유저 정보를 데이터베이스에 저장합니다. - 유저가 이미 존재하면,
prisma.user.update()
를 사용해서 XP를 10 증가시키고 업데이트된 정보를 보여줍니다. - 혹시 모를 오류에 대비해
try...catch
블록으로 감싸줍니다.
새로운 명령어를 만들었으니 src/commands/index.ts
파일에도 추가해줘야겠죠?
// src/commands/index.ts
// ... 다른 명령어들 import
import { profile } from "./profile";
export const commands = [
// ... 다른 명령어들
profile,
];
그리고 src/deploy-commands.ts
를 실행해서 슬래시 명령어를 디스코드 서버에 등록하는 것도 잊지 마세요!
npm run deploy
# 또는
# npx ts-node src/deploy-commands.ts
이제 봇을 실행하고 디스코드에서 /프로필
명령어를 사용해보세요. 처음 실행하면 프로필이 생성되었다는 메시지가 나오고, 다시 실행하면 XP가 오른 것을 확인할 수 있을 겁니다! prisma/dev.db
파일을 Prisma Studio나 다른 SQLite 뷰어로 열어보면 실제 데이터가 저장된 것도 볼 수 있어요.
Prisma Studio를 사용하려면 터미널에서 다음 명령어를 실행하면 됩니다.
npx prisma studio
웹 브라우저에서 Prisma Studio가 열리고, User
모델과 저장된 데이터를 확인할 수 있습니다.
MySQL로 전환하기 (선택 사항)
만약 SQLite 대신 MySQL을 사용하고 싶다면, 앞서 언급했던 것처럼 schema.prisma
파일과 .env
파일을 수정하면 됩니다.
MySQL 서버 준비: 로컬이나 원격에 MySQL 서버가 설치되어 있고, 접속 정보(사용자, 비밀번호, 호스트, 포트, 데이터베이스 이름)를 알고 있어야 합니다.
.env
파일 수정:DATABASE_URL
을 MySQL 연결 문자열로 변경합니다.DATABASE_URL="mysql://USER:PASSWORD@HOST:PORT/DATABASE"
schema.prisma
파일 수정:datasource db
의provider
를"mysql"
로 변경합니다.datasource db { provider = "mysql" url = env("DATABASE_URL") }
마이그레이션 재실행: 새로운 데이터베이스에 스키마를 적용하기 위해 마이그레이션을 다시 실행해야 합니다.
npx prisma migrate dev --name init_mysql
(기존 SQLite 마이그레이션과 구분하기 위해 다른 이름을 사용했습니다.)
만약 기존
prisma/migrations
폴더와dev.db
파일이 충돌을 일으킨다면, 해당 폴더와 파일을 삭제하거나 백업한 후 진행하는 것이 안전할 수 있습니다. (주의: 실제 운영 중인 데이터베이스에서는 매우 신중해야 합니다!)Prisma Client 재생성:
npx prisma generate
를 실행합니다 (보통migrate dev
에 포함되어 실행됩니다).
이렇게 하면 Prisma가 MySQL 데이터베이스에 연결되고, 기존에 작성했던 profile.ts
명령어 코드는 변경 없이 그대로 MySQL에서 동작할 겁니다. 이게 바로 ORM의 장점 중 하나죠!
마무리
이번 시간에는 Prisma를 사용해서 Discord.js 봇에 데이터베이스 연동 기능을 추가하는 방법을 알아봤습니다. SQLite로 간편하게 시작해서, 필요하다면 MySQL 같은 다른 데이터베이스로 확장할 수 있는 유연성도 확인했죠.
Prisma를 사용하면 데이터 모델링, 마이그레이션, 실제 데이터 CRUD 작업까지 타입스크립트 환경에서 매우 편리하게 처리할 수 있습니다. 앞으로 여러분의 봇에 다양한 기능을 추가할 때 데이터 저장이 필요하다면 Prisma를 적극적으로 활용해보세요!
다음 시간에는 봇을 실제로 운영 환경에 배포하고 호스팅하는 방법에 대해 알아보겠습니다. 기대해주세요!
'DiscordJS 개발 튜토리얼' 카테고리의 다른 글
[DiscordJS 봇 개발 튜토리얼] 10. 대화형 UI: 셀렉트 메뉴와 모달 활용하기 (6) | 2025.06.11 |
---|---|
[DiscordJS 봇 개발 튜토리얼] 9. 봇 배포 및 호스팅하기: 내 봇을 세상에 내보내자! (2) | 2025.06.11 |
[DiscordJS 봇 개발 튜토리얼] 7. 역할과 권한 체크 구현하기: 봇에게 질서를 부여하자! (1) | 2025.06.09 |
[DiscordJS 봇 개발 튜토리얼] 6. 이벤트 핸들링 마스터하기: 봇을 살아 움직이게 만드는 비법 (1) | 2025.06.08 |
[DiscordJS 봇 개발 튜토리얼] 5. 임베드 메시지와 버튼 만들기: 봇과의 소통을 더 풍부하게! (1) | 2025.06.06 |