January 21, 2026
8 min read
Building a chatbot used to require months of work, training data, and machine learning expertise. Now, with OpenAI's API, you can create a sophisticated conversational AI in an afternoon. This guide walks you through the entire process, from initial setup to a working chatbot you can integrate into your applications.
By the end of this tutorial, you'll have a chatbot that:
No prior AI experience required—just basic programming knowledge.
Before we start, make sure you have:
First, you need access to OpenAI's API:
Important: Never commit your API key to version control or expose it in client-side code.
Let's create a new project. Open your terminal:
mkdir ai-chatbot
cd ai-chatbot
npm init -y
Install the OpenAI SDK:
npm install openai dotenv
Create a .env file for your API key:
OPENAI_API_KEY=your-api-key-here
Add .env to your .gitignore to keep it safe:
echo ".env" >> .gitignore
Create a file called chatbot.js:
require('dotenv').config();
const OpenAI = require('openai');
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
async function chat(userMessage) {
const response = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [
{
role: 'user',
content: userMessage,
},
],
});
return response.choices[0].message.content;
}
// Test it
chat('What is the capital of France?').then(console.log);
Run it:
node chatbot.js
You should see something like: "The capital of France is Paris."
Congratulations—you just made your first API call to OpenAI!
The real power of chat completions comes from the messages array. Each message has a role:
Here's how they work together:
const messages = [
{
role: 'system',
content: 'You are a helpful coding assistant. You explain concepts clearly and provide code examples when relevant.',
},
{
role: 'user',
content: 'How do I reverse a string in JavaScript?',
},
];
const response = await openai.chat.completions.create({
model: 'gpt-4o',
messages: messages,
});
The system message shapes how the AI responds. It's like giving your chatbot a personality and job description.
A real chatbot needs to remember previous messages. Here's how to build that:
require('dotenv').config();
const OpenAI = require('openai');
const readline = require('readline');
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
// Store conversation history
const conversationHistory = [
{
role: 'system',
content: 'You are a friendly and helpful assistant. Keep responses concise but informative.',
},
];
async function chat(userMessage) {
// Add user message to history
conversationHistory.push({
role: 'user',
content: userMessage,
});
// Get AI response
const response = await openai.chat.completions.create({
model: 'gpt-4o',
messages: conversationHistory,
});
const assistantMessage = response.choices[0].message.content;
// Add assistant response to history
conversationHistory.push({
role: 'assistant',
content: assistantMessage,
});
return assistantMessage;
}
// Interactive CLI interface
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
function askQuestion() {
rl.question('You: ', async (input) => {
if (input.toLowerCase() === 'quit') {
console.log('Goodbye!');
rl.close();
return;
}
const response = await chat(input);
console.log('Bot:', response);
console.log('');
askQuestion();
});
}
console.log('Chatbot started! Type "quit" to exit.');
console.log('');
askQuestion();
Now you have a chatbot that remembers your entire conversation. Ask follow-up questions and it will understand the context.
The system message is where you define your chatbot's character. Here are some examples:
Customer Support Bot:
{
role: 'system',
content: `You are a customer support agent for TechCorp.
Be professional, empathetic, and solution-focused.
If you don't know something, say so and offer to connect them with a human agent.
Our products include: CloudSync, DataVault, and SecureChat.`
}
Coding Tutor:
{
role: 'system',
content: `You are a patient coding tutor for beginners.
Explain concepts step by step. Use analogies to make things clear.
Always provide runnable code examples.
When correcting mistakes, be encouraging rather than critical.`
}
Creative Writing Assistant:
{
role: 'system',
content: `You are a creative writing coach.
Help users develop their stories with thoughtful questions.
Offer suggestions but let them maintain creative control.
Be enthusiastic about their ideas.`
}
Production chatbots need proper error handling:
async function chat(userMessage) {
try {
conversationHistory.push({
role: 'user',
content: userMessage,
});
const response = await openai.chat.completions.create({
model: 'gpt-4o',
messages: conversationHistory,
max_tokens: 1000,
});
const assistantMessage = response.choices[0].message.content;
conversationHistory.push({
role: 'assistant',
content: assistantMessage,
});
return assistantMessage;
} catch (error) {
// Remove the failed user message from history
conversationHistory.pop();
if (error.status === 429) {
return 'I\'m receiving too many requests right now. Please try again in a moment.';
}
if (error.status === 401) {
console.error('API key invalid');
return 'There\'s a configuration issue. Please contact support.';
}
console.error('OpenAI API error:', error.message);
return 'Sorry, I encountered an error. Please try again.';
}
}
Conversations can get long, and APIs have token limits. Here's how to manage them:
function trimConversationHistory(maxMessages = 20) {
// Always keep the system message
const systemMessage = conversationHistory[0];
if (conversationHistory.length > maxMessages) {
// Keep system message + last N messages
const recentMessages = conversationHistory.slice(-(maxMessages - 1));
conversationHistory.length = 0;
conversationHistory.push(systemMessage, ...recentMessages);
}
}
async function chat(userMessage) {
conversationHistory.push({
role: 'user',
content: userMessage,
});
// Trim if needed before API call
trimConversationHistory(20);
const response = await openai.chat.completions.create({
model: 'gpt-4o',
messages: conversationHistory,
});
// ... rest of the function
}
For a better user experience, stream responses as they're generated:
async function chatWithStreaming(userMessage) {
conversationHistory.push({
role: 'user',
content: userMessage,
});
const stream = await openai.chat.completions.create({
model: 'gpt-4o',
messages: conversationHistory,
stream: true,
});
let fullResponse = '';
process.stdout.write('Bot: ');
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content || '';
process.stdout.write(content);
fullResponse += content;
}
console.log('\n');
conversationHistory.push({
role: 'assistant',
content: fullResponse,
});
return fullResponse;
}
This creates a typing effect where text appears as it's generated, making the chatbot feel more responsive.
Let's wrap our chatbot in an Express server so it can be used by web applications:
require('dotenv').config();
const express = require('express');
const OpenAI = require('openai');
const app = express();
app.use(express.json());
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
// Store conversations by session ID
const conversations = new Map();
app.post('/chat', async (req, res) => {
const { sessionId, message } = req.body;
if (!sessionId || !message) {
return res.status(400).json({ error: 'sessionId and message are required' });
}
// Get or create conversation history
if (!conversations.has(sessionId)) {
conversations.set(sessionId, [
{
role: 'system',
content: 'You are a helpful assistant.',
},
]);
}
const history = conversations.get(sessionId);
// Add user message
history.push({ role: 'user', content: message });
try {
const response = await openai.chat.completions.create({
model: 'gpt-4o',
messages: history,
});
const assistantMessage = response.choices[0].message.content;
// Add assistant response to history
history.push({ role: 'assistant', content: assistantMessage });
res.json({ response: assistantMessage });
} catch (error) {
console.error('Error:', error.message);
history.pop(); // Remove failed user message
res.status(500).json({ error: 'Failed to generate response' });
}
});
app.listen(3000, () => {
console.log('Chatbot API running on http://localhost:3000');
});
Install Express first:
npm install express
Now any frontend can send POST requests to /chat with a session ID and message.
Here are key lessons from building production chatbots:
1. Be specific in system prompts
Vague: "Be helpful" Better: "You are a customer service agent for an e-commerce site. Help users with order tracking, returns, and product questions. Be friendly but professional."
2. Set appropriate temperature
await openai.chat.completions.create({
model: 'gpt-4o',
messages: messages,
temperature: 0.7, // 0 = deterministic, 1 = creative
});
Lower temperature for factual responses, higher for creative tasks.
3. Implement rate limiting
Protect your API costs by limiting requests per user:
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 20, // 20 requests per minute
});
app.use('/chat', limiter);
4. Log conversations for debugging
Store conversations (with user consent) to identify issues and improve your system prompt.
5. Add input validation
Limit message length and sanitize input to prevent abuse:
if (message.length > 2000) {
return res.status(400).json({ error: 'Message too long' });
}
You now have a working chatbot. Here are ways to extend it:
The foundation you've built here scales to sophisticated AI applications. The same patterns power customer support bots, coding assistants, and enterprise tools.
Start simple, test thoroughly, and iterate based on real user feedback. Happy building!
Spread the word about this post