Skip to content

discordkit/discordkit

Repository files navigation

Discordkit Discordkit

npm version CI status

TypeScript SDK for Discord's API


πŸ“¦ Installation

npm install --save-dev @discordkit/client valibot
# or
yarn add -D @discordkit/client valibot

Warning

🚧 Additional documentation and examples are currently under construction! 🚧

Discordkit only recently published it's first stable version. Priority is being given to stablizing the CI/CD infrastructure for this monorepo while use-cases are explored and examples are built.

πŸ”§ Usage

Discordkit ships a Fetcher (async (input) => Promise<output>) and a valibot schema for every Discord REST endpoint. Use them directly, or compose them with the helpers in @discordkit/core to layer on runtime validation, tRPC procedures, or react-query.

Each endpoint exports two symbols:

import {
  // Input validation schema
  getGuildSchema,
  // Request handler β€” calls Discord's REST API
  getGuild
} from "@discordkit/client";

In order to make requests, you must first set your access token on the Discord session provider.

import { discord } from "@discordkit/client";

discord.setToken(`Bearer <access-token>`, true);

Direct use:

import { getGuild } from "@discordkit/client";

const guild = await getGuild({ guild: `123456789012345678` });

With runtime validation:

@discordkit/core exports toValidated, a Proxy wrapper that validates the input and output of any Fetcher at runtime. It's framework-agnostic β€” useful any time you want strong guarantees when accepting external input.

import { toValidated } from "@discordkit/core";
import { getGuild, getGuildSchema } from "@discordkit/client";
import { guildSchema } from "@discordkit/client";

const getGuildSafe = toValidated(getGuild, getGuildSchema, guildSchema);

const guild = await getGuildSafe({ guild: `123456789012345678` });
// throws if input doesn't match getGuildSchema, or the response doesn't match guildSchema

For GET endpoints, use toQuery from @discordkit/core to produce a queryFn compatible with useQuery:

import { useQuery } from "@tanstack/react-query";
import { toQuery } from "@discordkit/core";
import { getUser } from "@discordkit/client";

const getUserQuery = toQuery(getUser);

export const UserProfile = ({ user }) => {
  const { isLoading, isError, data, error } = useQuery({
    queryKey: [`user`, user.id],
    queryFn: getUserQuery({ id: user.id })
  });

  // ...
};

For mutations, pass the Fetcher straight to useMutation β€” schemas can validate input in onMutate:

import { useMutation } from "@tanstack/react-query";
import { modifyGuild, modifyGuildSchema } from "@discordkit/client";

export const RenameGuild = ({ guild }) => {
  const [name, setName] = useState(guild.name);
  const mutation = useMutation({
    mutationFn: modifyGuild,
    onMutate: (variables) => {
      // Will throw if invalid input is given
      modifyGuildSchema.parse(variables);
    }
  });

  const onSubmit = (e) => {
    e.preventDefault();
    mutation.mutate({ guild: guild.id, body: { name } });
  };

  return (
    <form onSubmit={onSubmit}>
      {mutation.error && (
        <h5 onClick={() => mutation.reset()}>{mutation.error.message}</h5>
      )}
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
      <br />
      <button type="submit">Rename Guild</button>
    </form>
  );
};

With tRPC:

Use toProcedure from @discordkit/core to wrap a Fetcher + schemas into a tRPC procedure builder. You assemble each procedure where you need it β€” no per-endpoint imports of pre-wired procedures.

import { initTRPC } from "@trpc/server";
import { createHTTPServer } from "@trpc/server/adapters/standalone";
import { toProcedure } from "@discordkit/core";
import {
  discord,
  getCurrentApplication,
  applicationSchema,
  getGuild,
  getGuildSchema,
  guildSchema
} from "@discordkit/client";

const botToken = process.env.DISCORD_BOT_AUTH_TOKEN;

// Configure your client to use a Bot token by default
discord.setToken(botToken, Boolean(botToken));

const t = initTRPC.context<{ user: string | null }>().create();
const baseProcedure = t.procedure;

// Create a reusable procedure to use a User's auth token when available
const authorizedProcedure = baseProcedure.use((opts) => {
  if (opts.ctx.user) {
    discord.setToken(`Bearer ${opts.ctx.user}`);
  } else {
    discord.setToken(`Bot ${botToken}`);
  }

  return opts.next();
});

const getCurrentApplicationProcedure = toProcedure(
  `query`,
  getCurrentApplication,
  null,
  applicationSchema
);

const getGuildProcedure = toProcedure(
  `query`,
  getGuild,
  getGuildSchema,
  guildSchema
);

const router = t.router({
  getCurrentApplication: getCurrentApplicationProcedure(baseProcedure),
  getGuild: getGuildProcedure(authorizedProcedure)
});

createHTTPServer({
  router,
  createContext({ req }) {
    // Extract a user's auth token from the incoming request headers
    async function getUserTokenFromHeader() {
      if (req.headers.authorization) {
        const user = await decodeAndVerifyJwtToken(
          req.headers.authorization.split(` `)[1]
        );
        return user;
      }
      return null;
    }

    return {
      user: await getUserTokenFromHeader()
    };
  }
}).listen(1337);

🀝 Contributing

The project uses Vite+ as a unified toolchain (Oxlint + Oxfmt + tsdown + Vitest) and Bumpy for versioning and release.

vp install           # install dependencies
vp check --fix       # format + lint + typecheck (with autofixes)
vp test              # run Vitest
yarn bumpy add       # create a bump file for your PR

πŸ“£ Acknowledgements

Endpoint documentation taken from Discord's Official API docs.

πŸ₯‚ License

Released under the MIT license Β© Drake Costa.

About

πŸ’¬ TypeScript SDK for Discord's API

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project

 

Contributors