Create Your First Bot
Let’s build a fully functional WhatsApp bot with basic commands. This guide will take you from zero to a working bot.
Set up your project
Create a new directory and initialize your project: mkdir my-whatsapp-bot
cd my-whatsapp-bot
npm init -y
Update your package.json to use ESM modules: {
"name" : "my-whatsapp-bot" ,
"type" : "module" ,
"version" : "1.0.0" ,
"description" : "My WhatsApp bot built with WAPI" ,
"main" : "index.js" ,
"scripts" : {
"start" : "node index.js"
}
}
Install dependencies
Install WAPI and the QR code library: npm install @imjxsx/wapi qrcode @imjxsx/logger
Create your bot file
Create an index.js file with the following code: import QRCode from "qrcode" ;
import { Bot , LocalAuth } from "@imjxsx/wapi" ;
import Logger from "@imjxsx/logger" ;
// Create a logger instance
const logger = new Logger ({
level: "INFO" ,
});
// Generate a unique UUID for your bot
const uuid = "1f1332f4-7c2a-4b88-b4ca-bd56d07ed713" ;
// Set up local authentication (sessions stored in ./sessions directory)
const auth = new LocalAuth ( uuid , "sessions" );
// Configure account (empty values for QR code login)
const account = {
jid: "" ,
pn: "" ,
name: "" ,
};
// Create the bot instance
const bot = new Bot ( uuid , auth , account , logger );
// Handle QR code event
bot . on ( "qr" , async ( qr ) => {
qr = await QRCode . toString ( qr , { type: "terminal" , small: true });
console . log ( qr );
});
// Handle successful connection
bot . on ( "open" , ( account ) => {
bot . logger . info ( `Successful login to @ ${ account . name } ( ${ account . pn } )` );
});
// Handle disconnection
bot . on ( "close" , ( reason ) => {
bot . logger . warn ( reason );
});
// Handle errors
bot . on ( "error" , ( err ) => {
bot . logger . error ( err );
});
// Add a middleware to log all messages
bot . use ( async ( ctx , next ) => {
bot . logger . info ( `New message from ' ${ ctx . from . name } ' in ' ${ ctx . chat . name } '` );
await next ();
});
// Add a ping command
bot . command ( "ping" , async ( ctx ) => {
await ctx . reply ( `> ¡Pong! \`\`\` ${ bot . ping . toFixed ( 2 ) } ms \`\`\` ` );
});
// Login using QR code
await bot . login ( "qr" );
Replace the UUID with your own unique identifier. You can generate one at uuidgenerator.net .
Run your bot
Start your bot: You should see a QR code appear in your terminal. Scan it with WhatsApp:
Open WhatsApp on your phone
Go to Settings → Linked Devices
Tap Link a Device
Scan the QR code
Once authenticated, session data is saved locally. You won’t need to scan the QR code again unless you delete the sessions folder.
Test your bot
Send a message to your bot: Or with the alternative prefix: Your bot should reply with the latency:
Login Methods
WAPI supports two authentication methods:
The simplest method - just scan a QR code with WhatsApp: const account = {
jid: "" ,
pn: "" ,
name: "" ,
};
const bot = new Bot ( uuid , auth , account );
bot . on ( "qr" , async ( qr ) => {
qr = await QRCode . toString ( qr , { type: "terminal" , small: true });
console . log ( qr );
});
await bot . login ( "qr" );
QR code login is perfect for development and testing. The session is saved and reused automatically.
Use a one-time password sent to your phone number: const account = {
jid: "12345@lid" ,
pn: "1234567890@s.whatsapp.net" ,
name: "My Bot" ,
};
const bot = new Bot ( uuid , auth , account );
bot . on ( "otp" , ( otp ) => {
bot . logger . info ( `Your OTP code: ${ otp } ` );
});
await bot . login ( "otp" );
For OTP login, you must provide a valid phone number in the pn field using the format number@s.whatsapp.net.
Adding More Commands
Let’s add more functionality to your bot:
// Echo command - repeats what you say
bot . command ( "say" , async ( ctx ) => {
await ctx . reply ( ctx . args . join ( " " ));
});
// Info command - shows message details
bot . command ( "info" , async ( ctx ) => {
const info = `
📱 *Chat Info*
• Type: ${ ctx . chat . type }
• Name: ${ ctx . chat . name }
👤 *Sender Info*
• Name: ${ ctx . from . name }
• Phone: ${ ctx . from . pn }
📨 *Message Info*
• Has media: ${ ctx . hasMedia ? 'Yes' : 'No' }
• Has quoted: ${ ctx . hasQuoted ? 'Yes' : 'No' }
` . trim ();
await ctx . reply ( info );
});
// Help command - lists all commands
bot . command ( "help" , async ( ctx ) => {
const help = `
🤖 *Available Commands*
/ping - Check bot latency
/say <text> - Echo your message
/info - Show message details
/help - Show this help message
` . trim ();
await ctx . reply ( help );
});
WAPI provides convenient methods for sending images, videos, and audio:
// Send an image with caption
bot . command ( "image" , async ( ctx ) => {
await ctx . replyWithImage ( "https://example.com/image.jpg" , {
caption: "Here's your image!"
});
});
// Send a video
bot . command ( "video" , async ( ctx ) => {
await ctx . replyWithVideo ( "https://example.com/video.mp4" , {
caption: "Check out this video"
});
});
// Send an audio file
bot . command ( "audio" , async ( ctx ) => {
await ctx . replyWithAudio ( "https://example.com/audio.mp3" );
});
Advanced Example: TikTok Downloader
Here’s a real-world example from the WAPI source code that downloads TikTok videos:
bot . command ( "tiktok" , async ( ctx ) => {
const links = ctx . links . filter (( v ) =>
/https ? : \/\/ ( www | vt | vm | t ) ? \. ? tiktok \. com \/ \S + / . test ( v )
);
if ( ! links . length ) {
await ctx . reply ( "> No valid TikTok link was detected in the message." );
return ;
}
await ctx . reply ( `> Processing ${ links . length } TikTok links.` );
for ( const link of links ) {
const response = await fetch ( `https://www.tikwm.com/api?url= ${ link } ` );
const { data } = await response . json ();
if ( ! data . play && ! data . images ?. length ) {
await ctx . reply ( `> Could not retrieve link information ' ${ link } '` );
continue ;
}
if ( data . images ?. length ) {
for ( let index = 0 ; index < data . images . length ; index ++ ) {
const imageUrl = data . images [ index ];
if ( ! index ) {
await ctx . replyWithImage ( imageUrl , {
caption: `> *Title:* ${ data . title } `
});
} else {
await ctx . replyWithImage ( imageUrl );
}
}
} else {
await ctx . replyWithVideo ( data . play , {
caption: `> *Title:* ${ data . title } `
});
}
}
});
The context object automatically parses links from messages using ctx.links, making it easy to build download bots and link processors.
Understanding Middleware
Middleware functions run before command handlers and can modify the context or stop execution:
// Log all messages
bot . use ( async ( ctx , next ) => {
bot . logger . info ( `Message from ${ ctx . from . name } ` );
await next (); // Continue to next middleware/command
});
// Only allow messages from groups
bot . use ( async ( ctx , next ) => {
if ( ctx . chat . type !== "group" ) {
await ctx . reply ( "This bot only works in groups!" );
return ; // Stop execution
}
await next ();
});
// Add a custom property to context
bot . use ( async ( ctx , next ) => {
ctx . timestamp = Date . now ();
await next ();
});
Context API Reference
The ctx object provides access to message data and utility methods:
Message Properties
ctx . text // Message text content
ctx . args // Command arguments as array
ctx . links // Extracted URLs from message
ctx . hasMedia // Boolean - message has media
ctx . hasQuoted // Boolean - message quotes another
ctx . isGroup // Boolean - message is from group
ctx . from . name // Sender's name
ctx . from . pn // Sender's phone number
ctx . from . jid // Sender's JID
ctx . chat . name // Chat name (contact or group)
ctx . chat . type // "private" or "group"
ctx . chat . jid // Chat JID
Reply Methods
await ctx . reply ( "text" ) // Send text
await ctx . replyWithImage ( url , { caption }) // Send image
await ctx . replyWithVideo ( url , { caption }) // Send video
await ctx . replyWithAudio ( url ) // Send audio
Event Handling
Listen to bot lifecycle events:
bot . on ( "qr" , async ( qr ) => {
// Handle QR code for authentication
});
bot . on ( "otp" , ( otp ) => {
// Handle OTP code for authentication
});
bot . on ( "open" , ( account ) => {
// Bot successfully connected
console . log ( `Logged in as ${ account . name } ` );
});
bot . on ( "close" , ( reason ) => {
// Bot disconnected
console . log ( `Disconnected: ${ reason } ` );
});
bot . on ( "error" , ( err ) => {
// Handle errors
console . error ( "Bot error:" , err );
});
Next Steps
Commands Guide Learn advanced command handling techniques
Middleware Master the middleware system
Authentication Explore Redis and MongoDB auth strategies
Context API Complete context API documentation
Tips for Development
Use environment variables for sensitive data like phone numbers and database credentials.
Don’t commit the sessions folder to version control. Add it to .gitignore.
The default command prefixes are / and !. You can customize this by setting bot.prefix = "!" or any other string.