GIF Deck is a top.gg-style GIF site built with Next.js,
Tailwind CSS, and
MagicUI — with a companion
Discord bot bolted on. You search GIFs (your own library plus
Giphy), log in with Discord, click any GIF, and the bot sends it either to your DMs or straight into a server channel.
It started as a tiny slash command MVP and grew into a full site. The bot still handles instant /gif picking without opening the browser.
Architecture
Two processes run during development:
- web/ — the Next.js 16 frontend (App Router, TypeScript, Tailwind v4, shadcn/ui + MagicUI). Dev server on
:3000, proxying API and auth to the backend throughnext.config.tsrewrites. - Root — an Express backend that also runs the bot: OAuth2 login,
/search,/gifs,/senddispatch, the auth API, and/providers.
The bot client (bot.js, built on discord.js) caches guilds and channels and exposes
sendToChannel() / sendDm(). A small db.js reads and writes your own GIF library stored in data/gifs.json.
How the flow works
The round trip from search to sent GIF looks like this:
- Open the site and Log in with Discord.
- Search — your own
data/gifs.jsonlibrary ranks first, then Giphy results. - Click a GIF, pick DM me or a server + channel, then Send to Discord.
- The bot fires the GIF and the site confirms.
Search: your library first
search.js merges your local library with Giphy. Local hits always win, so the GIFs you care about surface before the firehose.
A look at the app


Running it locally
You need a Giphy key and a Discord app. Copy .env.example to .env and fill in GIPHY_API_KEY, DISCORD_TOKEN, CLIENT_ID, CLIENT_SECRET, and a REDIRECT_URI of http://localhost:3000/auth/callback. Enable the Server Members intent and use the OAuth2 scopes identify and guilds.
# backend + bot on :3001 npm install npm start # frontend on :3000 (new terminal) cd web npm install npm run dev
Open http://localhost:3000. Invite the bot to a server with the applications.commands + bot scopes and the Send Messages permission.
Adding your own GIFs
POST to the backend with your admin key — title, tags, and a URL:
curl -X POST http://localhost:3001/gifs \
-H "Content-Type: application/json" \
-H "x-admin-key: $ADMIN_KEY" \
-d '{"title":"thumbs up","tags":["approve","yes"],"url":"https://example.com/thumbsup.gif"}'Tech stack
What is next
Post-MVP work: swap the in-memory sessions and data/gifs.json for a real database, add a multipart upload endpoint instead of URL-only GIFs, rate-limit /search and /send, and add per-user recently sent history and collections.
