aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Discord-Bots

Discord Bot in TypeScript: ein typsicheres Setup

Ein Discord-Bot mit TypeScript zahlt sich in dem Moment aus, in dem das Projekt zu wachsen beginnt: Dein Editor kennt die Autovervollständigung, fängt ein Argument vom falschen Typ ab, bevor du den Code überhaupt ausführst, und bei einem großen Refactor sagt er dir schon zur Compile-Zeit genau, welche Dateien brechen. Da discord.js v14 selbst vollständig in TypeScript geschrieben ist, bekommst du mit TS die gesamte Typinformation der Bibliothek kostenlos. In diesem Leitfaden bauen wir Schritt für Schritt ein typsicheres discord.js-Projekt von Grund auf, das kompiliert und läuft.

Warum TypeScript?

Ein in reinem JavaScript geschriebener Bot macht keine Probleme, solange er klein ist, aber sobald du 20-30 Befehle, ein paar Events und eine Datenbankschicht überschreitest, häufen sich stille Fehler. TypeScript deckt die meisten davon auf, bevor du überhaupt speicherst:

  • Autovervollständigung: Tippe interaction. und der Editor listet jede gültige Methode und Eigenschaft auf — kein ständiges Öffnen der Dokumentation mehr.
  • Frühe Fehlererkennung: Der Zugriff auf ein nicht existierendes Feld oder das Übergeben des falschen Typs an eine Funktion schlägt zur Compile-Zeit fehl, nicht vor einem Nutzer.
  • Sicheres Refactoring: Ändere eine Befehls-Schnittstelle und jede Datei, die sie verwendet, wird vom Compiler markiert.
  • Selbstdokumentierender Code: Typen dienen zugleich als lebende Dokumentation; deine Teamkollegen können lesen, was jeder Teil erwartet.

Das Projekt aufsetzen

Zuerst erstellen wir einen leeren Ordner und installieren die benötigten Pakete. discord.js ist eine Laufzeit-Abhängigkeit, während TypeScript und die Typ-Pakete nur für die Entwicklung gebraucht werden.

npm init -y
npm install discord.js
npm install -D typescript @types/node tsx

Hier ist tsx ein schnelles Werkzeug, mit dem wir TypeScript-Dateien direkt ohne separaten Build-Schritt ausführen können; während der Entwicklung ist es praktischer als ts-node. Für die Produktion machen wir einen echten tsc-Build.

tsconfig.json konfigurieren

Das Herz der Typsicherheit ist die Datei tsconfig.json. Ein solider, strenger Startpunkt für einen modernen Node.js-Bot sieht so aus:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"]
}

Die kritischste Zeile ist "strict": true; sie aktiviert null-Prüfungen, das Verbot von implizitem any und mehr auf einen Schlag. Mit diesem Flag holst du den vollen Wert aus TypeScript heraus — es auszuschalten ist wie Fahren mit angezogener Handbremse.

Die erste Verbindung und ein typisierter Client

Der Einstiegspunkt des Bots sei src/index.ts. Die Client-Klasse von discord.js ist bereits vollständig typisiert, sodass der Editor dich sofort warnt, wenn du versuchst, einen ungültigen Wert in das intents-Array zu übergeben.

// src/index.ts
import { Client, GatewayIntentBits, Events } from 'discord.js';

const client = new Client({
  intents: [GatewayIntentBits.Guilds],
});

client.once(Events.ClientReady, (c) => {
  console.log(`Angemeldet als: ${c.user.tag}`);
});

client.login(process.env.DISCORD_TOKEN);

Hier wird der Typ des Parameters c automatisch als Client<true> abgeleitet, sodass TypeScript garantiert, dass c.user nicht null ist und du sicher auf .tag zugreifen kannst.

Eine Schnittstelle für Befehle definieren

Jeder Slash-Befehl muss derselben Form folgen, damit der Handler sie einheitlich behandeln kann. Das halten wir mit einem interface als Vertrag fest:

// src/types.ts
import {
  ChatInputCommandInteraction,
  SlashCommandBuilder,
  SlashCommandOptionsOnlyBuilder,
} from 'discord.js';

export interface Command {
  data: SlashCommandBuilder | SlashCommandOptionsOnlyBuilder;
  execute: (interaction: ChatInputCommandInteraction) => Promise<void>;
}

Nun muss jede Befehlsdatei diesen Command-Typ implementieren. Vergisst du, die execute-Funktion in einem Befehl zu schreiben, oder verwendest du den falschen Parametertyp, siehst du die rote Unterstreichung in dem Moment, in dem du die Datei speicherst.

// 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('Misst die Latenz des Bots'),

  async execute(interaction) {
    await interaction.reply(`Pong! ${interaction.client.ws.ping}ms`);
  },
};

Den Client erweitern: Declaration Merging

Wir möchten Befehle in einer Sammlung wie client.commands speichern, aber der Client-Typ von discord.js hat keine solche Eigenschaft. Das Declaration Merging von TypeScript löst genau das: Wir erweitern die Typdefinition der Bibliothek um unser eigenes Feld.

// src/types.ts (Fortsetzung)
import { Collection } from 'discord.js';

declare module 'discord.js' {
  interface Client {
    commands: Collection<string, Command>;
  }
}

Dank dieses Blocks kennt der Editor den richtigen Typ, wenn du client.commands.get('ping') schreibst; du musst kein falsches any verwenden oder bei jedem Zugriff casten.

Befehle laden und ausführen

Beim Start des Bots füllen wir die Sammlung mit Befehlsdateien und leiten dann jede eingehende Interaktion an den richtigen Befehl weiter. Fehler zentral innerhalb von interactionCreate abzufangen, ist der sauberste Ansatz.

// src/index.ts (Ergänzungen)
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: 'Beim Ausführen dieses Befehls ist ein Fehler aufgetreten.',
      ephemeral: true,
    });
  }
});

Die Prüfung interaction.isChatInputCommand() ist ein Type Guard: Nach dieser Zeile grenzt TypeScript interaction auf ChatInputCommandInteraction ein, sodass du sicher auf Felder wie commandName und reply zugreifen kannst.

Skripte für Entwicklung und Produktion

Der letzte Schritt ist, zwei Skripte zur package.json hinzuzufügen: sofortiges Ausführen mit tsx für die Entwicklung und Kompilieren in den Ordner dist/ mit tsc für die Produktion.

{
  "type": "module",
  "scripts": {
    "dev": "tsx watch src/index.ts",
    "build": "tsc",
    "start": "node dist/index.js"
  }
}

Während der Entwicklung überwacht npm run dev Dateiänderungen und startet den Bot automatisch neu. Beim Ausrollen auf einen Server erzeugst du mit npm run build reines JavaScript und führst es mit npm start aus — in der Produktion wird TypeScript zur Laufzeit nicht benötigt.

Häufige Fragen

Soll ich ESM oder CommonJS verwenden?

ESM mit "type": "module" wird für neue Projekte empfohlen; discord.js v14 unterstützt beides. Denk bei ESM daran, dass du bei Imports lokaler Dateien die Endung .js schreiben musst (zum Beispiel './types.js') — das ist nötig, damit die kompilierte Ausgabe korrekt aufgelöst wird.

Warum tsx statt ts-node?

tsx basiert auf esbuild, startet daher viel schneller und braucht für ESM nahezu keine Konfiguration. Es prüft keine Typen, es führt nur aus; deshalb ist tsx für Geschwindigkeit in der Entwicklung und echte Typprüfung mit tsc beim Build die ideale Kombination.

Kann ich TypeScript direkt in der Produktion ausführen?

Technisch mit tsx möglich, aber nicht empfohlen. Das Vorkompilieren mit tsc in der Produktion beschleunigt den Start und fängt Typfehler zur Build-Zeit ab, wodurch verhindert wird, dass fehlerhafter Code deinen Server erreicht.

Möchtest du deinen Bot auf ein typsicheres, wartbares Fundament stellen? Von einem TypeScript-Setup von Grund auf über die Architektur des Command Handlers bis zum Deployment können wir dein Projekt gemeinsam auf festen Boden bringen. Kontaktiere mich und lass es uns nach deinen Bedürfnissen planen.

Bu kategorideki tüm yazılar →

Devamı için