The txArchive backend integrates Discord through the discord.js library (v14.26.4), not raw REST+fetch, to stay gateway-ready for planned deep Discord features. Foundation lives in src/services/discord.ts as DiscordService.

For Agents

This concerns the Fastify + Prisma + Postgres TypeScript backend implementation (txarchive_2025 working tree). Note that the higher-level txArchive 2025 Rewrite design doc framed the backend as Supabase Edge Functions + a separate discord.js bot — this DiscordService is the concrete in-process implementation path. Service lives at src/services/discord.ts; registered in src/services/binder.ts.

Decision

Use discord.js v14.26.4 for all Discord integration instead of hand-rolling REST calls with fetch.

Rationale: A “deep Discord integration” is anticipated — gateway events, bots, slash commands, voice. discord.js is gateway-ready out of the box, so adopting it now avoids a later rewrite. Raw REST would only cover the simplest current need (profile fetch) and would have to be torn out the moment gateway features land.

First feature: fetchProfile(id)

Returns { id, username, avatarUrl } for an arbitrary Discord user ID:

const user = await this.client.users.fetch(id)
return {
  id: user.id,
  username: user.username,
  avatarUrl: user.displayAvatarURL({ size: 256 }),
}

Patterns & gotchas

IoC service pattern

All services follow an inversion-of-control pattern:

  • A class with constructor(ioc: IocContainer) that pulls its dependencies via ioc.get(X).
  • Registered centrally in src/services/binder.ts via ioc.register(ServiceClass) (e.g. ioc.register(DiscordService), ioc.register(UsersService)).
  • All services are constructed at boot in src/index.ts through bindServices(ioc).

Lazy gateway login — by design

DiscordService does NOT call client.login() in its constructor (constructors run at boot). Instead, ensureReady() calls client.login(process.env.DISCORD_BOT_TOKEN) on the first fetchProfile call and caches the resulting promise.

Why: avoid crashing app boot when DISCORD_BOT_TOKEN is missing or invalid. The error surfaces only when the Discord feature is actually exercised, not at startup. Any new lazily-initialized service should follow this same ensureReady() + cached-promise shape rather than logging in eagerly in the constructor.

Env + intents

  • Requires env var DISCORD_BOT_TOKEN.
  • Client is created with GatewayIntentBits.Guilds only. client.users.fetch(id) resolves arbitrary user IDs over REST without privileged intents (no GuildMembers/MessageContent needed for profile fetches). Add intents only when a feature actually requires gateway events for them.