Database Integration Pattern
Database Integration Pattern
Section titled “Database Integration Pattern”When your bot grows, you will inevitably need to connect to a database to store user preferences, statistics, or application state. A common mistake is to import a global database singleton directly into your commands. This tightly couples your commands to the database implementation, making them hard to test and maintain.
Instead, we recommend using an Abstract Base Command that implements WhatsBotCord’s ICommand interface. This base class will act as a bridge, handling the database connection and injecting it into your specialized commands.
The Abstract Bridge Command
Section titled “The Abstract Bridge Command”Here is a real-world example of an abstract CustomCommand that receives a Database (AppDB) and a File System (IFileSystem) via dependency injection.
It intercepts the run execution to fetch the user’s preferred language from the database, configures the IChatContext with translated messages, and finally delegates the execution to a custom RUN method that your actual commands will implement.
import type { AdditionalAPI, CommandArgs, IChatContext, ICommand } from "whatsbotcord";
export abstract class CustomCommand implements ICommand { // Required fields by ICommand abstract name: string;
// Custom fields for our app abstract minimumPrivilege: string;
// Dependencies protected DB: any; protected FS: any;
constructor(databaseToUse: any, fileSystemToUse: any) { this.DB = databaseToUse; this.FS = fileSystemToUse; }
// We override the original run() method provided by Whatsbotcord public async run(ctx: IChatContext, api: AdditionalAPI, args: CommandArgs): Promise<void> {
// 1. Fetch user data from our injected Database const foundUser = await this.DB.Players.FindUser( args.participantIdLID, args.participantIdPN, args.chatId );
// 2. Set context wait messages overriding defaults based on user's lang if (foundUser) { ctx.Config.cancelFeedbackMsg = `Cancelled in ${foundUser.PreferredLanguage}`; ctx.Config.wrongTypeFeedbackMsg = `Wrong type in ${foundUser.PreferredLanguage}`; }
// 3. Delegate to the concrete command implementation await this.RUN(ctx, api, args); }
// The method subclasses must implement abstract RUN(ctx: IChatContext, api: AdditionalAPI, args: CommandArgs): Promise<void>;}Creating a Concrete Command
Section titled “Creating a Concrete Command”Now, whenever you want to create a new command, you simply extend your base CustomCommand. The database is already available via this.DB!
export default class ProfileCommand extends CustomCommand { name = "profile"; minimumPrivilege = "USER";
public async RUN(ctx: IChatContext, api: AdditionalAPI, args: CommandArgs): Promise<void> { // We can safely use this.DB because it was injected! const stats = await this.DB.Stats.GetByChatId(ctx.FixedChatId);
await ctx.SendText(`Your current stats: ${stats.score}`); }}By abstracting the run method, you ensure that every command automatically executes the required pre-requisites (like fetching the user or verifying roles) without duplicating code across all your files.