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: een type-veilige opzet

Een Discord-bot met TypeScript schrijven betaalt zich uit zodra het project begint te groeien: je editor kent de autocompletion, vangt een argument van het verkeerde type op nog voordat je de code uitvoert, en bij een grote refactor vertelt hij je tijdens het compileren precies welke bestanden breken. Omdat discord.js v14 zelf volledig in TypeScript is geschreven, krijg je met TS alle type-informatie van de bibliotheek gratis. In deze gids bouwen we stap voor stap een type-veilig discord.js-project op dat compileert en draait.

Waarom TypeScript?

Een bot in pure JavaScript geeft geen problemen zolang hij klein is, maar zodra je voorbij 20-30 commando's, een paar events en een databaselaag komt, stapelen stille bugs zich op. TypeScript brengt de meeste ervan aan het licht nog voordat je opslaat:

  • Autocompletion: typ interaction. en de editor toont elke geldige methode en eigenschap — geen voortdurend openen van de documentatie meer.
  • Vroege foutdetectie: een niet-bestaand veld benaderen of het verkeerde type aan een functie doorgeven faalt tijdens het compileren, niet voor de neus van een gebruiker.
  • Veilig refactoren: verander een command-interface en elk bestand dat hem gebruikt wordt door de compiler gemarkeerd.
  • Zelfdocumenterende code: types fungeren ook als levende documentatie; je teamgenoten kunnen lezen wat elk onderdeel verwacht.

Het project opzetten

Eerst maken we een lege map en installeren we de benodigde pakketten. discord.js is een runtime-afhankelijkheid, terwijl TypeScript en de type-pakketten alleen voor ontwikkeling nodig zijn.

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

Hier is tsx een snel hulpmiddel waarmee we TypeScript-bestanden direct kunnen draaien zonder aparte buildstap; tijdens ontwikkeling is het handiger dan ts-node. Voor productie doen we een echte tsc-build.

tsconfig.json configureren

Het hart van typeveiligheid is het bestand tsconfig.json. Een solide, strikt startpunt voor een moderne Node.js-bot ziet er zo uit:

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

De meest kritieke regel is "strict": true; die schakelt in één keer null-controles, het verbod op impliciete any en meer in. Met deze vlag haal je de volledige waarde uit TypeScript — hem uitzetten is als rijden met de handrem erop.

De eerste verbinding en een getypte Client

Laat het startpunt van de bot src/index.ts zijn. De Client-klasse van discord.js is al volledig getypt, dus als je een ongeldige waarde in de intents-array probeert door te geven, waarschuwt de editor je meteen.

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

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

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

client.login(process.env.DISCORD_TOKEN);

Hier wordt het type van de parameter c automatisch afgeleid als Client<true>, zodat TypeScript garandeert dat c.user niet null is en je veilig .tag kunt benaderen.

Een interface voor commando's definiëren

Elk Slash-commando moet dezelfde vorm volgen zodat de handler ze uniform kan behandelen. Dat leggen we vast in een contract met een interface:

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

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

Nu moet elk commandobestand dit Command-type implementeren. Vergeet je de execute-functie in een commando te schrijven of gebruik je het verkeerde parametertype, dan zie je de rode onderstreping zodra je het bestand opslaat.

// 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('Meet de latency van de bot'),

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

De Client uitbreiden: declaration merging

We willen commando's opslaan in een collectie zoals client.commands, maar het Client-type van discord.js heeft zo'n eigenschap niet. De declaration merging van TypeScript lost precies dit op: we breiden de typedefinitie van de bibliotheek uit met ons eigen veld.

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

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

Dankzij dit blok kent de editor het juiste type wanneer je client.commands.get('ping') schrijft; je hoeft geen nep-any te gebruiken of bij elke toegang te casten.

Commando's laden en uitvoeren

Bij het starten van de bot vullen we de collectie met commandobestanden en routeren we daarna elke binnenkomende interactie naar het juiste commando. Fouten centraal opvangen binnen interactionCreate is de gezondste aanpak.

// src/index.ts (toevoegingen)
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: 'Er is een fout opgetreden bij het uitvoeren van dit commando.',
      ephemeral: true,
    });
  }
});

De controle interaction.isChatInputCommand() is een type guard: na die regel versmalt TypeScript interaction tot ChatInputCommandInteraction, zodat je velden als commandName en reply veilig kunt benaderen.

Scripts voor ontwikkeling en productie

De laatste stap is twee scripts toevoegen aan package.json: direct draaien met tsx voor ontwikkeling, en compileren naar de map dist/ met tsc voor productie.

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

Tijdens het ontwikkelen volgt npm run dev bestandswijzigingen en herstart hij de bot automatisch. Bij het uitrollen naar een server produceer je pure JavaScript met npm run build en draai je die met npm start — in productie is TypeScript tijdens runtime niet nodig.

Veelgestelde vragen

Moet ik ESM of CommonJS gebruiken?

ESM met "type": "module" wordt aanbevolen voor nieuwe projecten; discord.js v14 ondersteunt beide. Vergeet bij ESM niet dat je de extensie .js moet schrijven bij imports van lokale bestanden (bijvoorbeeld './types.js') — dit is nodig zodat de gecompileerde output correct wordt opgelost.

Waarom tsx in plaats van ts-node?

tsx is gebaseerd op esbuild, dus het start veel sneller en vereist vrijwel geen configuratie voor ESM. Het controleert geen types, het draait alleen; daarom is tsx voor snelheid tijdens ontwikkeling en echte typecontrole met tsc bij de build de ideale combinatie.

Kan ik TypeScript direct in productie draaien?

Technisch mogelijk met tsx, maar niet aanbevolen. Vooraf compileren met tsc in productie versnelt het opstarten en vangt typefouten op tijdens de build, wat voorkomt dat kapotte code je server bereikt.

Wil je je bot op een type-veilig, onderhoudbaar fundament zetten? Van een TypeScript-opzet vanaf nul tot de architectuur van de command handler en de uitrol kunnen we je project samen op stevige grond brengen. Neem contact met me op en laten we het rond jouw behoeften plannen.

Bu kategorideki tüm yazılar →

Devamı için