GUIDERouters

Routers

Group commands, components, and modals into modular routers. Each router is a self-contained unit that you can compose, test, and reuse across applications.

Command router

Define slash commands with full type-safe option handling, autocomplete, and subcommand support.

typescript
import { command, CommandRouter } from "@theminesastudios/mini-interaction";
import { ApplicationCommandOptionType } from "discord-api-types/v10";

export const greetCommand = command({
  name: "greet",
  description: "Greet a user",
  options: [
    {
      type: ApplicationCommandOptionType.User,
      name: "target",
      description: "Who to greet",
      required: true,
    },
    {
      type: ApplicationCommandOptionType.String,
      name: "message",
      description: "Custom message",
    },
  ],
  async handler(ctx) {
    const target = ctx.getOption("target");
    const msg = ctx.getOption("message") ?? "Hello!";
    return ctx.respond({ content: `<@${target}> ${msg}` });
  },
});

// Compose into a router module
export const funRouter = new CommandRouter()
  .add(greetCommand)
  .add(pingCommand)
  .add(rollCommand);

Subcommand groups

typescript
import { subcommand, subcommandGroup } from "@theminesastudios/mini-interaction";

const configShow = subcommand({
  name: "show",
  parent: "config",
  handler: async (ctx) => ctx.respond({ content: "Config display" }),
});

const configSet = subcommand({
  name: "set",
  parent: "config",
  options: [{ type: 3, name: "key", description: "Setting key" }],
  handler: async (ctx) => ctx.respond({ content: "Config updated" }),
});

export const configRouter = new CommandRouter()
  .add(subcommandGroup("config", "Manage configuration", [configShow, configSet]));

Component router

Route button clicks, select menu selections, and other component interactions by custom ID pattern matching.

typescript
import { component, ComponentRouter } from "@theminesastudios/mini-interaction";
import { ButtonStyle, ComponentType } from "discord-api-types/v10";

const ticketCreate = component({
  // Custom ID with captured parameter
  pattern: /^ticket:(?<type>support|billing)$/,
  async handler(ctx) {
    const { type } = ctx.params;
    return ctx.respond({
      type: MessageComponentType.ChannelMessageWithSource,
      data: {
        content: `Creating ${type} ticket...`,
        flags: MessageFlags.Ephemeral,
      },
    });
  },
});

const ticketClose = component({
  pattern: /^ticket:close:(?<id>\d+)$/,
  async handler(ctx) {
    const { id } = ctx.params;
    await ctx.deferUpdate();
    // ... close ticket logic
  },
});

export const ticketRouter = new ComponentRouter()
  .add(ticketCreate)
  .add(ticketClose);

Handle modal submissions with type-safe field extraction and validation.

typescript
import { modal, ModalRouter } from "@theminesastudios/mini-interaction";

const feedbackModal = modal({
  customId: "feedback_submit",
  async handler(ctx) {
    const name = ctx.getField("feedback_name");
    const rating = ctx.getField("feedback_rating");
    const comment = ctx.getField("feedback_comment");

    await ctx.respond({
      content: `Thanks, ${name}! Your feedback has been recorded.`,
      flags: MessageFlags.Ephemeral,
    });
  },
});

export const feedbackRouter = new ModalRouter()
  .add(feedbackModal);

Composing routers

Compose multiple routers into your application. Each router registers its handlers with the framework's middleware pipeline.

typescript
import { createApp } from "@theminesastudios/mini-interaction";
import { funRouter } from "./routers/fun";
import { ticketRouter } from "./routers/tickets";
import { feedbackRouter } from "./routers/feedback";
import { configRouter } from "./routers/config";

const app = createApp({
  publicKey: process.env.DISCORD_PUBLIC_KEY!,
  applicationId: process.env.DISCORD_CLIENT_ID!,
});

app.use(funRouter);
app.use(ticketRouter);
app.use(feedbackRouter);
app.use(configRouter);

export const POST = app.handle();

Router middleware

Attach middleware to individual routers for scoped logic like permission checks, rate limiting, or logging.

typescript
import { command, CommandRouter, type Middleware } from "@theminesastudios/mini-interaction";

const requireMod: Middleware = async (ctx, next) => {
  const member = ctx.interaction.member;
  if (!member?.permissions?.includes("ModerateMembers")) {
    return ctx.respond({
      content: "You need the Moderate Members permission.",
      flags: MessageFlags.Ephemeral,
    });
  }
  return next(ctx);
};

export const modRouter = new CommandRouter()
  .use(requireMod)
  .add(warnCommand)
  .add(muteCommand)
  .add(kickCommand);

Router configuration

Property
Type
publicKeyrequired
string

Discord application public key for interaction verification

applicationIdrequired
string

Discord application client ID

onError
(err) => Response

Global error handler for uncaught router errors

logger
Logger

Custom logger instance (defaults to console)