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_2025working tree). Note that the higher-level txArchive 2025 Rewrite design doc framed the backend as Supabase Edge Functions + a separate discord.js bot — thisDiscordServiceis the concrete in-process implementation path. Service lives atsrc/services/discord.ts; registered insrc/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 viaioc.get(X). - Registered centrally in
src/services/binder.tsviaioc.register(ServiceClass)(e.g.ioc.register(DiscordService),ioc.register(UsersService)). - All services are constructed at boot in
src/index.tsthroughbindServices(ioc).
Lazy gateway login — by design
DiscordServicedoes NOT callclient.login()in its constructor (constructors run at boot). Instead,ensureReady()callsclient.login(process.env.DISCORD_BOT_TOKEN)on the firstfetchProfilecall and caches the resulting promise.Why: avoid crashing app boot when
DISCORD_BOT_TOKENis 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 sameensureReady()+ cached-promise shape rather than logging in eagerly in the constructor.
Env + intents
- Requires env var
DISCORD_BOT_TOKEN. - Client is created with
GatewayIntentBits.Guildsonly.client.users.fetch(id)resolves arbitrary user IDs over REST without privileged intents (noGuildMembers/MessageContentneeded for profile fetches). Add intents only when a feature actually requires gateway events for them.