Build a Slack Bot Using Node.js in 7 Steps

Build a Slack Bot Using Node.js in 7 Steps

Build a Slack bot with Node.js and Bolt in 7 steps — OAuth, event handling, deployment, and real code examples for solo founders.

Got two hours? That's typically enough to create a Slack bot if you know where the friction points are. Use Node.js, Slack's Bolt framework, and OAuth to authenticate. Then, deploy to a VPS or serverless function so your bot runs 24/7 without interruptions.

a few small toys Photo: Ant Rozetsky on Unsplash

Who this is for: Solo founders managing small teams (one to five people) who want to automate Slack workflows. Think standup reminders, customer alerts, deployment notifications, all without paying for Zapier or hiring a backend developer.

Step 1: Create a Slack App and Configure Permissions

Start by visiting api.slack.com/apps and click "Create New App." Choose "From scratch," give your bot a name, and select your testing workspace.

Head over to OAuth & Permissions in the sidebar. Add these under "Bot Token Scopes":

  • chat:write — for sending messages
  • channels:history — to read public channel messages
  • app_mentions:read — triggers when @mentioned
  • commands — enables slash commands

Install the app in your workspace. Slack will give you a Bot User OAuth Token, starting with xoxb-. Copy it for Step 3.

In Event Subscriptions, toggle "Enable Events" on. Save this spot for Step 5 to add a Request URL after your server is live.

Step 2: Set Up Your Node.js Project

# slack text Photo: Scott Webb on Unsplash

Set up a new directory and initialize npm:

mkdir slack-bot
cd slack-bot
npm init -y

Install Slack's Bolt framework and dotenv for environment variables:

npm install @slack/bolt dotenv

Create a .env file in your project root:

SLACK_BOT_TOKEN=xoxb-your-token-here
SLACK_SIGNING_SECRET=your-signing-secret

Get your signing secret from Slack app's Basic Information page under "App Credentials."

Create a .gitignore:

node_modules
.env

Step 3: Write the Bot Server Code

Develop app.js:

require('dotenv').config();
const { App } = require('@slack/bolt');

const app = new App({
  token: process.env.SLACK_BOT_TOKEN,
  signingSecret: process.env.SLACK_SIGNING_SECRET,
  socketMode: false, // We'll use HTTP mode for production
  port: process.env.PORT || 3000
});

// Respond to app mentions
app.event('app_mention', async ({ event, client }) => {
  try {
    await client.chat.postMessage({
      channel: event.channel,
      text: `Hey <@${event.user}>, I'm alive and running on Node.js.`
    });
  } catch (error) {
    console.error(error);
  }
});

// Slash command example
app.command('/status', async ({ command, ack, respond }) => {
  await ack();
  await respond(`Server uptime: ${process.uptime()} seconds`);
});

// Message listener for specific keywords
app.message('deploy', async ({ message, say }) => {
  await say({
    text: `Deployment detected in <#${message.channel}>`,
    thread_ts: message.ts
  });
});

(async () => {
  await app.start();
  console.log('⚡️ Bolt app is running on port', process.env.PORT || 3000);
})();

This bot does three things:

  1. Responds to @mentions.
  2. Handles a /status slash command.
  3. Listens for "deploy" messages and replies in-thread.

Test it locally:

node app.js

Right now, your bot won't receive events. Slack can't reach localhost. You'll need a public URL.

Step 4: Expose Your Local Server with ngrok

Install ngrok or the npm version:

npm install -g ngrok
ngrok http 3000

Ngrok supplies a public URL like https://abc123.ngrok.io. Copy the HTTPS one.

Backtrack to Slack app's Event Subscriptions page. Add your ngrok URL appended with /slack/events:

https://abc123.ngrok.io/slack/events

Slack sends a challenge request. If your server is active, it auto-responds and shows "Verified."

Under "Subscribe to bot events," add:

  • app_mention
  • message.channels

Click "Save Changes."

Navigate to Slash Commands and create a new one:

  • Command: /status
  • Request URL: https://abc123.ngrok.io/slack/events
  • Short Description: "Check bot status"

Slack will prompt you to reinstall the app.

Now test in Slack. Type /status or @mention your bot. Responses should show up.

Step 5: Add Real Functionality — API Integration Example

Most bots pull data from external APIs. Here's how to fetch GitHub repo stars and send that data to Slack:

Install axios:

npm install axios

Insert this into app.js:

const axios = require('axios');

app.command('/github', async ({ command, ack, respond }) => {
  await ack();

  const repo = command.text; // e.g., "facebook/react"
  if (!repo) {
    return respond('Usage: `/github owner/repo`');
  }

  try {
    const { data } = await axios.get(`https://api.github.com/repos/${repo}`);
    await respond({
      blocks: [
        {
          type: 'section',
          text: {
            type: 'mrkdwn',
            text: `*${data.full_name}*\n⭐ ${data.stargazers_count} stars\n🍴 ${data.forks_count} forks\n${data.description || 'No description'}`
          }
        }
      ]
    });
  } catch (error) {
    await respond('Repository not found or API error.');
  }
});

Restart your bot, then run /github facebook/react in Slack. Expect a formatted card displaying repo stats.

Step 6: Deploy to Production

Ngrok suffices for testing but halts when the terminal is closed. Deploy to a VPS or serverless platform for continuous running.

Option A: Deploy to a $5 DigitalOcean Droplet

SSH into your server:

ssh root@your-server-ip

Install Node.js:

curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt-get install -y nodejs

Clone your repo (or scp your files), install dependencies, and run with PM2:

npm install pm2 -g
pm2 start app.js --name slack-bot
pm2 startup
pm2 save

Point your domain or use the server IP. Update Slack's Request URL to http://your-server-ip:3000/slack/events.

Option B: Deploy to Vercel or Railway

Vercel doesn't support persistent WebSocket connections, but HTTP mode works. Create vercel.json:

{
  "version": 2,
  "builds": [{ "src": "app.js", "use": "@vercel/node" }],
  "routes": [{ "src": "/(.*)", "dest": "/app.js" }]
}

Deploy:

npm i -g vercel
vercel --prod

Update Slack's Request URL to your Vercel domain.

Railway simplifies bot deployment — it supports long-running processes. Push your repo to GitHub, connect it to Railway, add environment variables in the dashboard, and deploy. Railway generates a public URL automatically.

Step 7: Secure Your Bot and Handle Rate Limits

Slack enforces rate limits — typically one request per second for most methods. If your bot sends bulk messages, use a queue:

const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

async function sendBulkMessages(channels, message) {
  for (const channel of channels) {
    await app.client.chat.postMessage({
      token: process.env.SLACK_BOT_TOKEN,
      channel,
      text: message
    });
    await delay(1100); // Slack allows ~1 msg/sec
  }
}

Verify requests. Bolt handles this by default, but ensure your signing secret is set.

Keep your bot token in environment variables, not in the code. If tokens end up on GitHub, immediately rotate them in your Slack app settings.

What Nobody Tells You About Slack Bots

Slack's Event API has a 3-second timeout. Calling a slow external API will cause errors. Use ack() right away, then handle responses asynchronously:

app.command('/slow-api', async ({ command, ack, respond }) => {
  await ack(); // Acknowledge within 3 seconds

  // Do slow work here
  const result = await someSlowApiCall();
  await respond(result);
});

Socket Mode exists but has caveats. Socket Mode lets you run bots without a public URL, useful for development. However, it needs a WebSocket connection, unsuitable for serverless platforms. HTTP mode is best for production unless using a VPS.

Bolt can obscure debugging. If events aren't firing, check Slack's Event Subscriptions for delivery errors. Failed requests and payloads are shown — way more helpful than console logs.

Slash commands don't natively support subcommands. Parse command.text manually. Split it into segments and route as needed:

app.command('/mybot', async ({ command, ack, respond }) => {
  await ack();
  const [action, ...args] = command.text.split(' ');

  if (action === 'deploy') {
    // handle deploy
  } else if (action === 'status') {
    // handle status
  } else {
    await respond('Unknown command. Try `/mybot deploy` or `/mybot status`.');
  }
});

Slack's Block Kit is overkill until it's not. Start with plain text. Need buttons, modals, or rich formatting? Then, use Block Kit Builder. Copy JSON blocks directly into your code when required.

Common Mistakes

Installing the app without correct scopes. If your bot can't read messages, you likely missed channels:history. Can't post? You're missing chat:write. Always check "OAuth & Permissions" and reinstall after adding scopes.

Not handling message subtypes. Slack sends events for edits, deletions, and bot messages. Filter by message.subtype to avoid infinite loops:

app.message(async ({ message, say }) => {
  if (message.subtype) return; // Ignore edits, bot messages, etc.
  await say('Got a real user message');
});

Hardcoding channel IDs. Channel IDs can change if a channel is recreated. Use channel names or let users configure channels with slash commands.

Forgetting to rotate tokens. A leaked bot token allows anyone to impersonate your bot. Rotate tokens in Slack's "OAuth & Permissions" and update your .env right away.

FAQ

Do I need a paid Slack plan to build bots?

No. Bots work on Slack's free tier. You're limited to 10 integrations total, but a single bot counts as one, no matter its features. Paid plans unlock unlimited integrations and longer message history, but neither impacts bot functionality.

Can I use TypeScript instead of JavaScript?

Yes. Install @types/node and ts-node, rename app.js to app.ts, and add types for Bolt. The Slack SDK includes TypeScript definitions. Compile using tsc or run with ts-node app.ts. TypeScript enhances autocomplete and reduces runtime errors in most production bots.

How do I test my bot without spamming my team?

Create a private channel or separate Slack workspace for development. Invite only your bot. Test all events and commands there. Slack allows you to create up to 10 workspaces on the free plan. Use one just for bot testing.

What's the difference between Event API and RTM?

Event API uses HTTP requests. Slack sends events to your server's public URL. RTM (Real Time Messaging) uses WebSockets and maintains a persistent Slack connection. RTM is deprecated. Use Event API for new bots, or Socket Mode if a public URL can't be exposed.

Conclusion

The bottom line: you now have a functional Slack bot listening to mentions, handling slash commands, and fetching API data. Deploy it to a $5 VPS or Railway, then implement the workflows your team actually needs. Stop paying for automation tools that do the same thing.

Next step: Identify one manual task you perform daily in Slack, like standup reminders, deployment notifications, or customer alerts, and automate it. Write the handler in app.js, test in a private channel, and ship it today. For more insights on automating tasks, check out our article on Step-by-Step Mailchimp Setup for Solo Founders or explore Best CRM Tools for Indie Hackers in 2026.


Editorial note: This article was produced with AI assistance and reviewed by Javier Valencia. Verified facts are distinguished from editorial opinion throughout the text. External sources linked are independent of NewsTide.

Sources

  1. a few small toys
  2. Ant Rozetsky
  3. api.slack.com/apps
  4. # slack text
  5. Scott Webb

More in Indie Hacking

🇪🇸 Also available in Spanish: Leer en español

𝕏in