Create a basic dashboard for your Discord bot using Next.js πŸ’― (2024)

With a dashboard, it makes it easier for your bot's users to configure your bot from a web interface, instead of typing commands to configure everything, so in this tutorial I'm gonna show how to make a basic dashboard using Next.js

Things to take in consideration before starting this tutorial

  • I will use MongoDB as database, if you use a different database, you must change the code accordingly.
  • You must already have a discord app registered, if you don't know how to register one, you can learn here on how to create one.
  • You must have an intermediate knowledge of Next.js and Node.js.
  • The dashboard will run on a separated server by default, but you can add your bot's code to the dashboard's code and make them both run in the same server.
  • If you use a file-based database, such as quick.db, you must host the dashboard and bot in the same server, else it won't work.

For this project I'm gonna use the following technologies:

  • Next.js
  • MongoDB
  • Passport.js
  • Express.js

Setting up our Discord application

  • First we need to go to our app in Discord Developer Portal.
  • We need to get our client secret, and client id, save them somewhere, we'll need them later.
  • Head to OAuth2, go to general, and add your domain with the /api/auth/callback, an example:https://example.com/api/auth/callbackThis is where the users will be redirected when they press the authorize button, the route that will process the code given by Discord.

It looks like this now
Create a basic dashboard for your Discord bot using Next.js πŸ’― (1)

As it says there, ** it must exactly match one of the URIs you enter here**, so please use your domain name.

Creating the Next.js app

For this example, we're gonna use SSR (Server side rendering), so I'd recommend using the Next.js template on SSR, to do this, we're gonna run the next command:

npx create-next-app --example custom-server-express discord-bot-dashboard

And we need to install our dependencies, we can install them app by using the following command:

npm install --save mongoose passport passport-discord express-session connect-mongo cookie-parser body-parser compression helmet axios discord.js 

After that is done, we open our folder in our IDE of preference and start working.

Currently our project looks like this:

Create a basic dashboard for your Discord bot using Next.js πŸ’― (2)

Now, follow the next steps to set up our file structure:

  • Create a folder called src
  • Move the pages folder into the src folder (routing will still work this way, learn more)
  • Inside the src folder, create a folder called styles

  • In the root directory of the project, create a folder called server.

  • Move the file named server.js into the server folder, then rename it to index.js, and since we changed the main file, we should also change it on our package.json, change the dev script to point to the index.js file inside the server folder.

  • Inside the server folder, create 3 folders: api,models and config.

  • Inside the newly created api folder, create a file called servers.js, this will be used for our server related info requests, and a file named auth.js, which will handle our auth api routes such as login, logout, etc.

  • Inside the config folder, create a file called auth.js, one called routes.js, one called middleware.js and another one called databases.js.

New folder structure

After that, our folder structure now looks like this

Create a basic dashboard for your Discord bot using Next.js πŸ’― (3)

Environment variables

For this, we'll use a .env file at the root of our project, this one haves to have the following entries:

PORT, the port our Express.js app will listen toHOST, the url of our server, for example, http://localhost:8080 (It shouldn't have the / at the end)MONGODB_URI, our MongoDB database connection string (including authentication)SESSION_SECRET, The secret in which our sessions will be encryptedCOOKIE_SECURE, wether our site uses SSLCOOKIE_MAX_AGE, How long should the sessions last (in miliseconds)CLIENT_ID, Your bot's IDCLIENT_SECRET, Your bot's secret

So an example for this .env file would be this:

.env

PORT=8080HOST="http://localhost:8080"MONGODB_URI="mongodb://localhost:27017/test-bot"SESSION_SECRET="Keyboard cat"COOKIE_SECURE="false"COOKIE_MAX_AGE=604800000CLIENT_ID=516688206020739243CLIENT_SECRET=your client secret

Basic server configuration

We're now gonna open the index.js file inside the server folder, it already haves some content, generated by the npx command.

Now, we're gonna write the following code into our files

server/index.js

const express = require("express");const next = require("next");const port = parseInt(process.env.PORT, 10) || 8080;const dev = process.env.NODE_ENV !== "production";const app = next({ dev });const handle = app.getRequestHandler();const server = express();app.prepare().then(() => { // Load the middleware require("./config/middleware"); // Load the auth system require("./config/auth"); // Connect to the database require("./config/databases"); // Set the app's routes require("./config/routes"); // Next.js routing server.all("*", (req, res) => { return handle(req, res); }); // Start the server server.listen(port, () => { console.log(`> Ready on http://localhost:${port}`); });});// Export the Express.js app, since we'll use it in other filesmodule.exports = { server }

server/config/routes.js

// Import our appconst { server } = require("../");try { // Add our api endpoints to our app server.use("/api/auth", require("../api/auth")); server.use("/api/servers", require("../api/servers"));} catch (err) { // Handle errors console.log("There was an error trying to load the routes"); console.error(err);}

server/config/databases.js

const mongoose = require("mongoose");// Connect to our MongoDB database, using the connection stringmongoose.connect(process.env.MONGODB_URI);const mongooseClient = mongoose.connection;// When the app connects to our MongoDB database mongooseClient.once('open', () => { console.log("MongoDB database successfully connected")});// If there's an error trying to connect with the databasemongooseClient.once('error', (error) => { console.log("There was an error trying to connect to MongoDB") console.log(error);});module.exports = { mongooseClient }

server/config/auth.js

const DiscordStrategy = require("passport-discord").Strategy;const passport = require("passport");const expressSession = require("express-session");const { server } = require("../");// The scopes we'll needconst scopes = ["identify", "guilds"];// Serialize userspassport.serializeUser((user, done) => { done(null, user);});passport.deserializeUser((user, done) => { done(null, user);});// Add the middleware create sessionsserver.use( expressSession({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: true, // Store the sessions to our MongoDB database, so users don't have to re login each time the server restarts store: mongoStore.create({ mongoUrl: process.env.MONGODB_URI, }), // Cookie configuration name: "session", cookie: { secure: process.env.COOKIE_SECURE == "true", maxAge: parseInt(process.env.COOKIE_MAX_AGE) || 604800000, sameSite: true, }, }));// Add passport.js middleware to our serverserver.use(passport.initialize());server.use(passport.session());// Use passport.js's Discord strategypassport.use( new DiscordStrategy( { // Set passport.js's configuration for the strategy clientID: process.env.CLIENT_ID, clientSecret: process.env.CLIENT_SECRET, callbackURL: `${process.env.HOST}/api/auth/callback`, scope: scopes, }, (accessToken, refreshToken, profile, cb) => { console.log(profile) // <== To log the authorized user // Login the user return cb(null, profile); } ));

server/config/middleware.js

// Dependenciesconst cookieParser = require("cookie-parser");const bodyParser = require("body-parser");const compression = require("compression");const helmet = require("helmet");const { server } = require("../");try { // Requests server.use( bodyParser.urlencoded({ extended: true, }) ); server.use(bodyParser.json()); server.use(cookieParser()); server.use(compression()); // Basic security, which is disabled in development mode if (process.env.NODE_ENV == "production") { server.use(helmet.contentSecurityPolicy()); server.use( helmet.crossOriginEmbedderPolicy({ policy: "require-corp", }) ); server.use( helmet.crossOriginOpenerPolicy({ policy: "same-origin", }) ); server.use( helmet.crossOriginResourcePolicy({ policy: "same-origin", }) ); server.use( helmet.dnsPrefetchControl({ allow: false, }) ); server.use( helmet.expectCt({ maxAge: 0, }) ); server.use( helmet.frameguard({ action: "sameorigin", }) ); server.use( helmet.hsts({ maxAge: 15552000, includeSubDomains: true, }) ); server.use( helmet.permittedCrossDomainPolicies({ permittedPolicies: "none", }) ); server.use( helmet.referrerPolicy({ policy: "no-referrer", }) ); server.use(helmet.ieNoOpen()); server.use(helmet.hidePoweredBy()); server.use(helmet.noSniff()); server.use(helmet.originAgentCluster()); server.use(helmet.xssFilter()); } console.log(`Middleware loaded`);} catch (err) { console.log(`There was an error loading the middleware`); console.log(err);}

Setting up MongoDB's models

This is why we created the models folder, if you use mongoose you will be familiar with this type of folder, but if you don't, basically is a way to easily create and get entries in our database.

So, we're gonna get inside that folder and create a file named server-config.js, if you use MongoDB in your bot, this file must be the same as the one you use in your bot.

This is how we're gonna "connect" the bot and the dashboard, by using the database

If you don't have a server.js file, we're gonna create one and give it a basic example configuration:

server/models/server-config.js

const mongoose = require("mongoose");const Schema = mongoose.Schema;// Create the schemaconst ServerConfig = new Schema({ serverID: { type: String, required: true, unique: true, }, prefix: { type: String, unique: false, default: "!", },});// Create the model using the schema, in this order: Model name, Schema, and collection we're gonna useconst model = mongoose.model("ServerConfig", ServerConfig, "servers");// Export our modelmodule.exports = model;

Setting up the auth system

Now that we've configured our server, we need to add the API routes for our app, for this, we're gonna start on the server/api/auth.js file

server/api/auth.js

const express = require("express");const passport = require("passport");// Create our routerconst router = express.Router();// Redirect to the discord's auth pagerouter.get("/redirect", passport.authenticate("discord"));// When returning from that page, authenticate and generate a sessionrouter.get( "/callback", // Specify the auth strategy that we declared in the server/config/auth passport.authenticate("discord", { failureRedirect: "/", // If the auth failed }), function (req, res) { res.redirect("/dashboard"); // Successful auth });module.exports = router;

src/pages/index.js

const Home = () => { return ( <div> <h1>Redirect to discord's login page</h1> <a href="/api/auth/redirect">Login</a> </div> );};export default Home;

Run the server

So, basically what we've setted up, is that when we go to http://example.com we're gonna see the src/pages/index.js file, it haves a header and a link that redirects to Discord's auth page.

After the user authorizes access to their account, it will log to console their information, so, lets test it out.

Run the server on development mode

npm run dev

And now we go to http://example.com or your domain
Create a basic dashboard for your Discord bot using Next.js πŸ’― (4)

We press the login button and we get redirected to this page
Create a basic dashboard for your Discord bot using Next.js πŸ’― (5)

We press authorize, and then we check our console.

Create a basic dashboard for your Discord bot using Next.js πŸ’― (6)

We get their information, and the servers they're in, now we can delete the console.log in the server/config/auth.js file.

If we see our browser, we were redirected to http://example.com/dashboard, but that page doesn't exist.

Create a basic dashboard for your Discord bot using Next.js πŸ’― (7)

So lets create it.

Creating the server selection page

Lets get inside src/pages and get use of Next.js's routing system, learn more, and we'll create a folder called dashboard.

Inside the dashboard folder, we'll create 2 files: index.js and [server].js.

Our src file now looks like this
Create a basic dashboard for your Discord bot using Next.js πŸ’― (8)

The [server].js file is where our users will configure our bot, and the index.js file is where our users will select the server they want to configure.

And now we follow this code

src/pages/dashboard/index.js

export const getServerSideProps = async (context) => { // Check if the user is logged in, if not, redirect to the login page if (!context.req.user) return { redirect: { destination: "/api/auth/redirect", permanent: false, }, }; return { // Pass the props to the page props: { user: context.req.user, userServers: context.req.user.guilds, }, };};const Dashboard = (props) => { // Generate a map of the user's servers const servers = props.userServers.map((server) => { return ( <li key={server.id}> <a href={`/dashboard/${server.id}`}>{server.name}</a> </li> ); }); return ( <div> <h1>Please select a server</h1> <ul>{servers}</ul> </div> );};export default Dashboard;

If we login, and go to http://example.com/dashboard we will see all our servers
Create a basic dashboard for your Discord bot using Next.js πŸ’― (9)

But, we dont want to show the servers the user is not an admin in, so we can sort them our by using discord.js permissions:

src/pages/dashboard/index.js

import discord from "discord.js"export const getServerSideProps = async (context) => { // Check if the user is logged in, if not, redirect to the login page if (!context.req.user) return { redirect: { destination: "/api/auth/redirect", permanent: false, }, }; return { // Pass the props to the page props: { user: context.req.user, userServers: context.req.user.guilds, }, };};const Dashboard = (props) => { // Generate a map of the user's servers const servers = props.userServers.map((server) => { // Sort out the servers in which the user doesn't have permissions let permissions = new discord.Permissions(server.permissions).toArray(); if (!permissions.has("MANAGE_GUILD ")) return ( <li key={server.id}> <a href={`/dashboard/${server.id}`}>{server.name}</a> </li> ); }); return ( <div> <h1> Hello {props.user.username}#{props.user.discriminator} Please select a server </h1> <ul>{servers}</ul> </div> );};export default Dashboard;

And now if we go to the same url, we'll see that the number has decreased to show only the ones we have the MANAGE_GUILD, after that, we will see that the number of servers has decreased to the ones we moderate.

So, lets click on one.

Making the server dashboard page

We will be redirected to http://example/dashboard/server_id, like this:
Create a basic dashboard for your Discord bot using Next.js πŸ’― (10)

But the page doesn't have any content yet, so we'll add it

src/pages/dashboard/[server].js

export const getServerSideProps = async (context) => { // Check if the user is logged in, if not, redirect to the login page if (!context.req.user) return { redirect: { destination: "/api/auth/redirect", permanent: false, }, }; // Select the server given the id in the url const selectedServer = context.req.user.guilds.find((server) => { return server.id == context.params.server; }); const serverPermissions = new discord.Permissions(server.permissions); // Check if the server is not in the user's session and if the user doesn't have permission to edit that server, if so, redirect out of the page if (!selectedServer || !serverPermissions.has("MANAGE_GUILD")) return { redirect: { destination: "/dashboard", permanent: false, }, }; return { // Pass the props to the page props: { user: context.req.user, selectedServer: selectedServer, }, };};const Dashboard = (props) => { return ( <div> <h1> Hello {props.user.username}#{props.user.discriminator}, you can configure {props.selectedServer.name} here </h1> <input type="text" placeholder="Prefix" /> <button>Save</button> </div> );};export default Dashboard;

If we check the page, we can now see the name of the server, an input and a button:
Create a basic dashboard for your Discord bot using Next.js πŸ’― (11)

But we need to make those elements work, but first, we need to add a backend to them.

Saving and getting configurations

Remember the server/api/servers.js we created at the start of the tutorial? Now we will use it

server/api/servers.js

const express = require("express");const ServerConfigs = require("../models/server-config");const router = express.Router();router.post("/get-server-config", async (req, res) => { // If the user is not authenticated if (!req.isAuthenticated()) return res.status(403).send("unauthorized"); // Check parameters if (!req.body.serverID || typeof req.body.serverID !== "string") return res.status(400).send("invalid-parameters"); // Find the server in the bot's database using the provided id const serverConfig = await ServerConfigs.findOne({ serverID: req.body.serverID }); // If there isn't a server, return if (!serverConfig) return res.status(400).send("server-not-found"); // Send the configuration return res.send(serverConfig);});router.post("/save-server-config", async (req, res) => { // If the user is not authenticated if (!req.isAuthenticated()) return res.status(403).send("unauthorized"); // Check parameters if (!req.body.newPrefix || !req.body.serverID) return res.status(400).send("missing-parameters"); if (typeof req.body.newPrefix !== "string" || typeof req.body.serverID !== "string") return res.status(400).send("invalid-parameters"); // Save the configuration await ServerConfigs.updateOne({ serverID: req.body.serverID }, { prefix: req.body.newPrefix }); // Send the configuration return res.send("success");});module.exports = router;

Now we need to enable this api route, to do this, just put this into your server/config/routes.js file
server/config/routes.js

// Import our appconst { server } = require("../");try { // Add our api endpoints to our app server.use("/api/auth", require("../api/auth")); server.use("/api/servers", require("../api/servers"));} catch (err) { // Handle errors console.log("There was an error trying to load the routes"); console.error(err);}

I created a test configuration document on MongoDB for this tutorial
Create a basic dashboard for your Discord bot using Next.js πŸ’― (12)

And now we make an interface for it, to connect both the server and the front-end I'm gonna use axios

src/pages/dashboard/[server].js

import axios from "axios";export const getServerSideProps = async (context) => { // Check if the user is logged in, if not, redirect to the login page if (!context.req.user) return { redirect: { destination: "/api/auth/redirect", permanent: false, }, }; // Select the server given the id in the url const selectedServer = context.req.user.guilds.find((server) => { return server.id == context.params.server; }); const serverPermissions = new discord.Permissions(server.permissions); // Check if the server is not in the user's session and if the user doesn't have permission to edit that server, if so, redirect out of the page if (!selectedServer || !serverPermissions.has("MANAGE_GUILD")) return { redirect: { destination: "/dashboard", permanent: false, }, }; // Get the server configuration const response = await axios({ method: "POST", url: `${process.env.HOST}/api/servers/get-server-config`, headers: context.req.headers, // <== This part is important, if we don't add it, we won't pass our credentials, and so we wont the able to be authorized to get that information data: { serverID: selectedServer.id, }, }); return { // Pass the props to the page props: { user: context.req.user, selectedServer: selectedServer, selectedServerConfig: response.data, // This assuming there is a server with that host: process.env.HOST, // <== Important to save changes }, };};const Dashboard = (props) => { const saveChanges = async (event) => { event.preventDefault(); // Get the new prefix let newPrefix = document.querySelector("#prefix-input").value; // Post changes const response = await axios({ method: "POST", url: `${props.host}/api/servers/get-server-config`, data: { serverID: props.selectedServer.id, newPrefix: newPrefix, }, }); // show the results console.log(response); }; return ( <div> <h1> Hello {props.user.username}#{props.user.discriminator}, you can configure {props.selectedServer.name} here </h1> <input defaultValue={props.selectedServerConfig.prefix} type="text" id="prefix-input" placeholder="Prefix" /> <button onClick={saveChanges}>Save</button> </div> );};export default Dashboard;

Now if we go to the site, we can see that it sets the default values to the actual configuration.

Create a basic dashboard for your Discord bot using Next.js πŸ’― (13)

If we changed the prefix, pressed save, and then check the console, we'll see that it was saved successfully:

Create a basic dashboard for your Discord bot using Next.js πŸ’― (14)

And to test if it was changed in the database, we can go, in this case, to MongoDB Compass and check it:

Create a basic dashboard for your Discord bot using Next.js πŸ’― (15)

And as we can see, the value changed

How to implement it with your bot

After all of this is done, you might want to change the things in the models, add more configurations, and styles, this is a very very basic dashboard only made to update one value, but it can be scaled to be whatever you want, since we're connecting the bot and the dashboard using the database, the only limitant on how much you want to control your bot from the dashboard is your imagination.

Conclussion

Thanks for reading, it's been a while since I did anything related to Discord and I remember struggling to add a dashboard, the purpose of this tutorial is to make that process simple, and learn more in the way.

All the source code for this project is in my repository at Github if you want to check it out, or clone it so you don't have to copy paste all the code in this project:
https://github.com/Asterki/basic-discord-bot-dashboard

And, I got another thing for you, if you want to support multiple languages, I already did a post on that here on dev.to, you can read it here.

Create a basic dashboard for your Discord bot using Next.js πŸ’― (2024)

References

Top Articles
Latest Posts
Article information

Author: Fredrick Kertzmann

Last Updated:

Views: 6387

Rating: 4.6 / 5 (66 voted)

Reviews: 81% of readers found this page helpful

Author information

Name: Fredrick Kertzmann

Birthday: 2000-04-29

Address: Apt. 203 613 Huels Gateway, Ralphtown, LA 40204

Phone: +2135150832870

Job: Regional Design Producer

Hobby: Nordic skating, Lacemaking, Mountain biking, Rowing, Gardening, Water sports, role-playing games

Introduction: My name is Fredrick Kertzmann, I am a gleaming, encouraging, inexpensive, thankful, tender, quaint, precious person who loves writing and wants to share my knowledge and understanding with you.