Bir discord bot TypeScript ile yazmak, projen büyüdükçe geri dönüşü olmayan bir konfor sağlar: editör otomatik tamamlamayı bilir, yanlış tipte bir argümanı henüz çalıştırmadan yakalar ve büyük bir refactor'da hangi dosyaların kırıldığını derleme anında söyler. discord.js v14 zaten baştan sona TypeScript ile yazıldığı için, sen de TS kullandığında kütüphanenin tüm tip bilgisinden ücretsiz faydalanırsın. Bu rehberde sıfırdan tip güvenli, derlenip çalışan bir discord.js projesini adım adım kuracağız.
Neden TypeScript?
Saf JavaScript ile yazılan bir bot küçükken sorun çıkarmaz; ama 20-30 komutu, birkaç event'i ve bir veritabanı katmanını geçtiğinde sessiz hatalar birikmeye başlar. TypeScript bu hataların çoğunu daha sen kaydetmeden gösterir:
- Otomatik tamamlama:
interaction.yazdığında editör tüm geçerli metotları ve özellikleri listeler — dokümantasyonu sürekli açmana gerek kalmaz. - Erken hata yakalama: Var olmayan bir alana erişmek ya da bir fonksiyona yanlış tipte değer vermek derleme anında hata verir, kullanıcı önünde değil.
- Güvenli refactor: Bir komut arayüzünü değiştirdiğinde, onu kullanan her dosya derleyici tarafından işaretlenir.
- Belgeleyen kod: Tipler aynı zamanda canlı bir dokümantasyondur; ekip arkadaşların ne beklediğini okur.
Projeyi başlatmak
Önce boş bir klasör oluşturup gerekli paketleri kuruyoruz. discord.js çalışma zamanı bağımlılığı; TypeScript ve tip paketleri ise sadece geliştirme için gerekir.
npm init -y
npm install discord.js
npm install -D typescript @types/node tsx
Burada tsx, TypeScript dosyalarını ayrı bir derleme adımı olmadan doğrudan çalıştırmamızı sağlayan hızlı bir araçtır; geliştirme sırasında ts-node'a göre daha pratiktir. Üretim için ise gerçek bir tsc derlemesi yapacağız.
tsconfig.json yapılandırması
Tip güvenliğinin kalbi tsconfig.json dosyasıdır. Modern bir Node.js botu için sağlam ve katı bir başlangıç şu şekildedir:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"]
}
En kritik satır "strict": true'dur; null kontrolleri, örtük any yasağı ve daha fazlasını tek seferde açar. TypeScript'in tüm değerini bu bayraktan alırsın — kapatmak, arabayı el frenli sürmek gibidir.
İlk bağlantı ve tipli Client
Botun giriş noktası src/index.ts olsun. discord.js'in Client sınıfı zaten tamamen tiplidir, dolayısıyla intents dizisine geçersiz bir değer vermeye çalışırsan editör anında uyarır.
// src/index.ts
import { Client, GatewayIntentBits, Events } from 'discord.js';
const client = new Client({
intents: [GatewayIntentBits.Guilds],
});
client.once(Events.ClientReady, (c) => {
console.log(`Giriş yapıldı: ${c.user.tag}`);
});
client.login(process.env.DISCORD_TOKEN);
Burada c parametresinin tipi otomatik olarak Client<true> çıkarıldığı için c.user'ın null olmadığını TypeScript garanti eder ve .tag'e güvenle erişebilirsin.
Komutlar için bir arayüz tanımlamak
Her Slash komutunun aynı şekle uyması, handler'ın onları tek tip muamele edebilmesi için şarttır. Bunu bir interface ile sözleşmeye bağlarız:
// src/types.ts
import {
ChatInputCommandInteraction,
SlashCommandBuilder,
SlashCommandOptionsOnlyBuilder,
} from 'discord.js';
export interface Command {
data: SlashCommandBuilder | SlashCommandOptionsOnlyBuilder;
execute: (interaction: ChatInputCommandInteraction) => Promise<void>;
}
Artık her komut dosyası bu Command tipini uygulamak zorundadır. Bir komutta execute fonksiyonunu yazmayı unutursan ya da yanlış parametre tipi kullanırsan, dosyayı kaydeder kaydetmez kırmızı çizgiyi görürsün.
// src/commands/ping.ts
import { SlashCommandBuilder } from 'discord.js';
import type { Command } from '../types.js';
export const ping: Command = {
data: new SlashCommandBuilder()
.setName('ping')
.setDescription('Botun gecikmesini ölçer'),
async execute(interaction) {
await interaction.reply(`Pong! ${interaction.client.ws.ping}ms`);
},
};
Client'ı genişletmek: declaration merging
Komutları client.commands gibi bir koleksiyonda saklamak isteriz, ama discord.js'in Client tipinde böyle bir özellik yoktur. TypeScript'in declaration merging özelliği tam burada devreye girer: kütüphanenin tip tanımını kendi alanımızla genişletiriz.
// src/types.ts (devamı)
import { Collection } from 'discord.js';
declare module 'discord.js' {
interface Client {
commands: Collection<string, Command>;
}
}
Bu blok sayesinde artık client.commands.get('ping') yazdığında editör doğru tipi bilir; sahte bir any kullanmana ya da her erişimde tip dönüşümü yapmana gerek kalmaz.
Komutları yüklemek ve çalıştırmak
Botu başlatırken komut dosyalarını koleksiyona doldururuz, ardından gelen her etkileşimi ilgili komuta yönlendiririz. interactionCreate içinde hatayı merkezî olarak yakalamak en sağlıklısıdır.
// src/index.ts (ek)
import { Collection } from 'discord.js';
import { ping } from './commands/ping.js';
client.commands = new Collection();
client.commands.set(ping.data.name, ping);
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isChatInputCommand()) return;
const command = client.commands.get(interaction.commandName);
if (!command) return;
try {
await command.execute(interaction);
} catch (err) {
console.error(err);
await interaction.reply({
content: 'Komut çalışırken bir hata oluştu.',
ephemeral: true,
});
}
});
interaction.isChatInputCommand() kontrolü bir type guard'dır: bu satırdan sonra TypeScript interaction'ı ChatInputCommandInteraction olarak daraltır, böylece commandName ve reply gibi alanlara güvenle erişirsin.
Geliştirme ve üretim betikleri
Son adım, package.json'a iki betik eklemektir: geliştirme için tsx ile anında çalıştırma, üretim için tsc ile dist/ klasörüne derleme.
{
"type": "module",
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc",
"start": "node dist/index.js"
}
}
Geliştirirken npm run dev dosya değişikliklerini izleyip botu otomatik yeniden başlatır. Sunucuya çıkarken ise npm run build ile saf JavaScript üretip npm start ile çalıştırırsın — üretimde çalışma zamanında TypeScript'e ihtiyaç kalmaz.
Sık Sorulan Sorular
ESM mi CommonJS mu kullanmalıyım?
Yeni projeler için "type": "module" ile ESM önerilir; discord.js v14 her ikisini de destekler. ESM kullanırken yerel dosya importlarında .js uzantısını yazman gerektiğini unutma (örneğin './types.js') — bu, derlenen çıktının doğru çözümlenmesi için gereklidir.
ts-node yerine neden tsx?
tsx, esbuild tabanlı olduğu için çok daha hızlı başlar ve ESM ile yapılandırması nerdeyse sıfırdır. Tip kontrolü yapmaz, sadece çalıştırır; bu yüzden geliştirmede tsx ile hız, derlemede tsc ile gerçek tip denetimi kullanmak ideal kombinasyondur.
Üretimde TypeScript'i doğrudan çalıştırabilir miyim?
Teknik olarak tsx ile mümkün, ama önerilmez. Üretimde tsc ile önceden derlemek hem başlangıcı hızlandırır hem de derleme anında tip hatalarını yakalayarak kırık kodun sunucuya çıkmasını engeller.
Botunu tip güvenli ve sürdürülebilir bir temele oturtmak mı istiyorsun? Sıfırdan TypeScript kurulumundan komut handler mimarisine ve dağıtıma kadar projeni birlikte sağlam bir zemine taşıyabiliriz. Benimle iletişime geç, ihtiyacına göre planlayalım.