📌 What Is This Project?
This backend project uses AI to generate knowledge base (KB) articles automatically by extracting and summarizing data from Jira tickets.
💡 Why I Built This
If you’ve worked in product or engineering teams, you know how painful documentation can be. Issues often come to the dev team via Customer Support or Solutions Engineers—but once resolved, the knowledge rarely makes it back into a centralized, searchable format.
We all use Jira to track issues, but let’s be honest:
Going through a stack of tickets just to understand a recurring issue is... not fun.
That’s where this project comes in.
Personal Motivation
I’d never built anything with AI before. This was my first attempt—and I wanted to create something practical, something that scratched a real itch.
This tool is a simple MVP to help automate the creation of knowledge base articles. The idea is to reduce context loss, improve handovers between teams, and keep documentation up to date without making developers do more work.
Feel free to extend or adapt it to your team’s needs!
🛠️ Project Details
⚙️ Tech Stack
- Node.js
- Express.js
- MongoDB (via Mongoose)
- OpenAI (ChatGPT API)
📋 Functional Requirements
- Follows REST API principles
- Fetches ticket data from Jira (can be extended to Intercom or Zendesk)
- Uses ChatGPT to generate KB draft content
- Allows finalizing and storing KBs in MongoDB
- Supports audit fields like createdAt, updatedAt (auto-handled by Mongoose)
🗂 Project Structure
.
├── server.js # Entry point
├── /db
│ └── mongodb_connection.js # MongoDB connection
├── /models
│ └── kb.model.js # KB Schema
├── /controllers
│ └── kbController.js
├── /routes
│ └── kbRoutes.js # KB API Routes
├── /services
│ └── jiraService.js # Jira integration logic
├── .env # Environment variables
└── chatgpt.js # chatgpt configuration
🧱 Prerequisites
Install dependencies:
npm install
Make sure to install the following:
"config": "^3.3.12",
"dot-env": "^0.0.1",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"mongodb": "^6.14.2",
"mongoose": "^8.12.1",
"node-fetch": "^2.7.0",
"nodemon": "^3.1.9",
"openai": "^4.77.4"
Create a .env
file in the root directory:
PORT=3000
MONGODB_URI=your_mongodb_connection_string
OPENAI_API_KEY=your_openai_api_key
JIRA_API_KEY=your_jira_api_key
JIRA_BASE_URL=https://your-domain.atlassian.net
🧬 MongoDB Schema Overview
KB Model
Field Type Description
ticketId String Unique ticket ID (e.g., from Jira, Intercom)
sourceType String One of: JIRA, INTERCOM, ZENDESK
kb String Generated knowledge base content
isCompleted Boolean Whether the KB has been finalized
createdAt Date Auto-added timestamp
updatedAt Date Auto-updated timestamp
🔌 API Endpoints
-
GET /kb/jira/:ticketId
Fetches a Jira ticket by ID and generates a KB draft using the OpenAI API.
Example Request
GET http://localhost:3000/kb/jira/ABC-123
Example Response
{
"kbEntry": "Generated knowledge base content for ticket ABC-123..."
}
-
POST /kb/finalize
Finalize a generated KB and save it to the database.
Example Request
POST http://localhost:3000/kb/finalize
Content-Type: application/json
Request Body
{
"ticketId": "ABC-123",
"sourceType": "JIRA",
"kb": "The finalized KB content..."
}
Example Response
{
"message": "KB finalized and saved successfully.",
"kb": {
"_id": "605d1b2f1c4ae0a1b0a2f19b",
"ticketId": "ABC-123",
"sourceType": "JIRA",
"kb": "The finalized KB content...",
"isCompleted": true,
"createdAt": "2025-03-14T08:55:31.203Z",
"updatedAt": "2025-03-14T08:55:31.203Z"
}
}
✍️ Code Files
Please note that these code files are just for reference and you might encounter certain issues by directly running these files.
server.js : entry point
const express = require("express");
const mongoose = require('mongoose');
require("dotenv").config();
const connectDB = require("./db/mongodb_connection.js");
const kbRoutes = require("./routes/kbRoutes.js")
const app = express();
const PORT = process.env.PORT || 3000;
app.use(express.json()); // Middleware to parse JSON
app.use("/kb", kbRoutes); // Use knowledge base routes
const startServer = async () => {
try{
await connectDB();
app.listen(PORT,()=>{
console.log(`✅ Server running on http://localhost:${PORT}`);
});
}catch(err){
console.log(`❌ Failed to connect to MongoDB. Server has not been started`);
console.log(err)
}
}
startServer();
db/mongodb_connection.js
require('dotenv').config(); // <-- Required here if not already run in server.js
const mongoose = require('mongoose');
const uri = process.env.MONGODB_URI;
if (!uri) {
console.error('No MONGODB_URI found in environment variables');
process.exit(1);
}
const connectDB = async () => {
try {
await mongoose.connect(uri, {
dbName: "knowledgeBaseDB",
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log('✅ MongoDB connected successfully!');
} catch (err) {
console.error('❌ MongoDB connection failed:', err.message);
process.exit(1);
}
};
module.exports = connectDB;
models/kb.model.js
const mongoose = require("mongoose");
const kbSchema = new mongoose.Schema(
{
ticketId: {
type: String,
required: true,
unique: true, // prevent duplicates for the same Jira ticket
},
sourceType: {
type: String,
enum: ["JIRA", "INTERCOM", "ZENDESK"],
required: true,
},
kb: {
type: String,
required: true,
},
isCompleted: {
type: Boolean,
default: false,
},
},
{
timestamps: true, // adds createdAt and updatedAt fields automatically
}
);
const KB = mongoose.model("KB", kbSchema);
module.exports = KB;
controller/kbController.js
const KB = require("../models/kb.model.js");
const { fetchJiraTicket } = require("../services/jiraService");
const { generateKnowledgeBase } = require("../chatgpt");
const finalizeKB = async (req, res) => {
try {
const { ticketId, sourceType } = req.body;
if (!ticketId || !sourceType) {
return res.status(400).json({ error: "ticketId and sourceType are required." });
}
// Check if KB already exists and is completed
const existingKB = await KB.findOne({ ticketId });
if (existingKB && existingKB.isCompleted) {
return res.status(409).json({ message: "KB already finalized for this ticket." });
}
// Fetch Jira ticket data
const ticketData = await fetchJiraTicket(ticketId);
// Generate KB using your chatGPT function
const kbContent = await generateKnowledgeBase(ticketData);
// Save or Update KB in MongoDB
const kbEntry = await KB.findOneAndUpdate(
{ ticketId }, // filter
{
ticketId,
sourceType,
kb: kbContent,
isCompleted: true,
},
{ new: true, upsert: true }
);
res.status(200).json({ message: "KB finalized and saved!", kbEntry });
} catch (error) {
console.error("Error in finalizeKB:", error.message);
res.status(500).json({ error: error.message });
}
};
module.exports = finalizeKB;
routes/kbRoutes.js
const express = require("express");
const KB = require("../models/kb.model.js");
const { fetchJiraTicket } = require("../services/jiraService");
// const { getZendeskTicket } = require("../services/zendeskService"); -> to be implemented
// const { getIntercomChat } = require("../services/intercomService"); -> to be implemented
const finalizeKB = require("../controllers/kbController.js");
const { generateKnowledgeBase } = require("../chatgpt.js");
const router = express.Router();
// Fetch and process Jira ticket
router.get("/jira/:ticketId", async (req, res) => {
try {
const { ticketId } = req.params;
const existingKB = await KB.findOne({ ticketId });
res.json(existingKB);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Finalize or Create KB (upsert logic)
router.post("/jira/finalize", finalizeKB);
module.exports = router;
services/jiraService.js
const fetch = require('node-fetch');
const { jira } = require("../config");
require("dotenv").config();
if (!jira.email || !jira.apiKey) {
console.error("Missing JIRA_EMAIL or JIRA_API_KEY_PERSONAL in environment variables");
process.exit(1);
}
const encodedAuth = Buffer.from(`${jira.email}:${jira.apiKey}`).toString("base64");
async function fetchJiraTicket(ticketId) {
try {
const issueDetailsResponse = await fetch(`${jira.baseUrl}${ticketId}`, {
method: "GET",
headers: {
Authorization: `Basic ${encodedAuth}`,
Accept: "application/json",
},
});
if (!issueDetailsResponse.ok) {
throw new Error(`Failed to fetch Jira ticket: ${issueDetailsResponse.statusText}`);
}
const issueCommentsResponse = await fetch(`${jira.baseUrl}${ticketId}/comment`, {
method: "GET",
headers: {
Authorization: `Basic ${encodedAuth}`,
Accept: "application/json",
},
});
const issueDescription = await issueDetailsResponse.json();
const issueComments = await issueCommentsResponse.json();
return {
source: "jira",
id: ticketId,
description: issueDescription.fields.description,
comments: issueComments.comments.map(comment => comment.body),
};
} catch (error) {
console.error("Error fetching Jira ticket:", error.message);
return null;
}
}
module.exports = { fetchJiraTicket };
chatgpt.js
const fetch = require("node-fetch");
require("dotenv").config(); // Load environment variables from a .env file
const { openai } = require("./config");
if (!openai.apiKey) {
console.error("Missing OpenAI API key in environment variables");
process.exit(1);
}
const systemPrompt = ``;
async function generateKnowledgeBase(ticketData) {
let ticketId = ticketData.id;
let ticketComments = ticketData.comments;
let ticketDescription = ticketData.description
try {
const response = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${openai.apiKey}`,
},
body: JSON.stringify({
model: "gpt-4o",
messages: [
{ role: "system", content: `${systemPrompt}` },
{ role: "user", content: `Generate a KB article from this Jira ticket "${ticketId}" description: "${ticketDescription}" and the below JIRA comments "${ticketComments}"` },
],
}),
});
if (!response.ok) {
throw new Error(`OpenAI request failed: ${response.status}`);
}
const data = await response.json();
return data.choices[0].message.content; // Return AI-generated response
} catch (error) {
console.error("Error generating KB:", error.message);
return null;
}
}
module.exports = { generateKnowledgeBase };
🚀 What’s Next?
I believe we can further improve this project by implementing the following:
- Build a web interface for reviewing/editing KBs. Give users the ability to search the KB articles by searching key-words
- Improve the logic but adding the ability to traverse linked JIRAs and generate more efficient documentation without loosing the context
- use vectorDB to increase the efficacy of this project
🙌 Final Thoughts
Documentation shouldn’t be a chore. With AI, we can shift from manual documentation to automated, just-in-time knowledge sharing.
This is my first article; if you found this project useful — or have ideas to make it better — I’d love to hear from you.
Adios!