Build a Smart Task-to-Ticket Converter
Use Claude AI to turn messy feature ideas into structured technical tickets.
Introduction
30 Second Summary
Every engineering team has the same problem. Someone says "we need a login page with a blue button that saves to Firebase" and then the developer has to figure out what that actually means in technical terms.
In this project, you will build a web app that uses AI to convert messy feature ideas into structured technical tickets. You will learn how senior engineers document requirements and how to integrate an AI API into a web application.
What You'll Build
A locally-running web application with a Node.js backend that securely calls the Claude API to transform casual feature descriptions into professional technical tickets.
By the end of this project, you'll have:
- A styled HTML form where you type messy feature ideas and get back structured tickets.
- A Node.js/Express server that securely proxies requests to the Claude API without exposing your key to the browser.
- A crafted system prompt engineered to output professional technical tickets with sections like Summary, Requirements, and Acceptance Criteria.
- Secret Mission: Add a ticket type selector that dynamically changes the AI output format for Bug Reports, Feature Requests, and Infrastructure Tasks.
Are there any prerequisites?
None. This guide includes full setup instructions for all required tools. You will install everything from scratch in Step 1.
Set Up Your Development Environment
Before you can build the ticket converter, you need a few tools installed on your machine. Node.js runs JavaScript outside the browser, which is what powers your backend server.
In this step, you'll install Node.js, set up VS Code as your code editor, create your project folder, and pull in the libraries your app needs.
In this step, get ready to:
- Install and verify Node.js.
- Set up VS Code and create your project folder.
- Initialize the project and install dependencies.
Install and verify Node.js
Node.js is the JavaScript runtime that powers your backend server. The Anthropic SDK requires Node.js version 18 or higher.
- Open a terminal on your computer.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal (macOS) or PowerShell (Windows) and press Enter to open it.
- Check if Node.js is already installed by running this command:
node --version
✔️ I see version 18 or higher
Nice, Node.js is ready. Also confirm that npm (the package manager that comes with Node.js) is available by running:
npm --version
You should see a version number printed. If so, you're good to move on.
ⓧ I see a version lower than 18
You have Node.js installed, but the Anthropic SDK needs version 18 or higher. You'll need to upgrade.
- Go to https://nodejs.org/en/download/ in your browser.
- Download the LTS version for your operating system.
- Run the installer and accept the default options.
- Close and reopen your terminal, then run node --version again to confirm version 18 or higher.
ⓧ Command not found
Node.js is not installed on your machine yet.
- Go to https://nodejs.org/en/download/ in your browser.
- Download the LTS version for your operating system.
- Run the installer and accept the default options.
- Close and reopen your terminal, then run node --version to confirm it installed successfully.
- Run npm --version to confirm the package manager is also available.
Still seeing an old version after reinstalling?
Make sure you fully closed and reopened your terminal after the install. Some systems cache the old path until the terminal session resets.
help me troubleshoot my Node.js installation
Set up VS Code and create your project folder
VS Code is a free code editor with a built-in terminal. You'll use it to write all of your project code.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Visual Studio Code and press Enter to open it.
✔️ VS Code opened
Great, VS Code is ready. Continue to the next instructions below.
ⓧ I don't have VS Code
You'll need to install VS Code first.
- Go to https://code.visualstudio.com/Download in your browser.
- Download the installer for your operating system (macOS, Windows, or Linux).
- Run the installer and accept the default options.
- Once installed, open VS Code using the search bar instructions above.
Now create your project folder and open it in VS Code.
- In VS Code, click Terminal in the top menu bar.
- Click New Terminal.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir ticket-converter
cd ticket-converter
- Open this folder in VS Code by running:
code .
What does code . do?
The code . command opens the current folder in a new VS Code window. The dot (.) means "this directory."
If this command doesn't work, open VS Code manually, click File in the top menu, then Open Folder, and navigate to the ticket-converter folder on your Desktop.
You should see ticket-converter in the Explorer sidebar on the left. The folder is empty for now.
Initialize the project and install dependencies
Every Node.js project starts with a package.json file that tracks your project's name, version, and dependencies. You'll generate one now and then install the libraries your app needs.
- Open a terminal inside VS Code if you don't already have one open. Click Terminal in the menu bar, then New Terminal.
- Initialize your project by running this command:
npm init -y
What does npm init -y do?
This command creates a package.json file with default values. The -y flag means "yes to all defaults" so it skips the interactive prompts.
- Install the three libraries your project needs by running:
npm install express @anthropic-ai/sdk dotenv
What are these packages?
- express is a web framework for Node.js that handles HTTP requests and serves your frontend files.
- @anthropic-ai/sdk is the official library for calling the Claude API from your server.
- dotenv loads environment variables from a .env file into your code, keeping secrets out of your source files.
- Confirm the install worked by running:
ls
You should see node_modules, package.json, and package-lock.json in the output.
Don't see node_modules?
Make sure your terminal is inside the ticket-converter folder. Run pwd to check your current directory. If you're somewhere else, run cd ~/Desktop/ticket-converter and try the install command again.
help me fix my npm install
Now update package.json so it has the correct project description and start script.
- In the VS Code Explorer sidebar, click on package.json to open it.
- Select all the text in the file and replace it with the following:
{
"name": "ticket-converter",
"version": "1.0.0",
"description": "AI-powered task to ticket converter",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.96.0",
"dotenv": "^16.4.7",
"express": "^4.21.2"
}
}
What changed in package.json?
- description gives your project a human-readable summary.
- main is set to server.js, the file you'll create in a later step to run your server.
- scripts.start lets you run npm start to launch your server instead of typing the full command.
- Save the file with Cmd+S (macOS) or Ctrl+S (Windows).
✔️ Awesome, I've got everything!
Great. Double check you've saved your file.
ⓧ I'd like to double check the full code
Here is what your package.json should look like:
{
"name": "ticket-converter",
"version": "1.0.0",
"description": "AI-powered task to ticket converter",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.96.0",
"dotenv": "^16.4.7",
"express": "^4.21.2"
}
}
Your development environment is ready. Next up, you'll get an API key from Anthropic so your app can talk to Claude.
Get Your Claude API Key
Your app needs a way to communicate with Claude AI. To make that happen, you need an API key that authenticates your requests to the Claude API.
In this step, you will create an Anthropic account, generate your API key, and store it securely in your project so it never gets accidentally exposed.
In this step, get ready to:
- Create an Anthropic account and generate a Claude API key.
- Store the key in a secure .env file.
- Add a .gitignore file to prevent secrets from being shared.
Create your Anthropic account and API key
Anthropic is the company behind Claude. Their developer console is where you create and manage API keys for your projects.
- Open your browser and go to platform.claude.com.
- Create a new account or sign in if you already have one.
- Once signed in, click Settings in the left sidebar.
- Click API Keys in the settings menu.
- Click Create Key to generate a new API key.
- Give your key a name like ticket-converter and click Create Key.
- Copy the key immediately. You will not be able to see it again after closing the dialog.
What is an API key?
An API key is a unique string that identifies your application when it makes requests to a service. It proves to Claude's servers that you have permission to use the API.
Think of it like a password for your app. You will store it in a file that only your server can read, keeping it hidden from anyone who visits your website.
Store your API key in a .env file
A .env file stores environment variables that your app loads at runtime. This keeps sensitive values out of your source code.
- In VS Code, right-click in the Explorer panel and select New File.
- Name the file .env and press Enter.
- Paste the following into .env, replacing your-api-key-here with the actual key you just copied:
ANTHROPIC_API_KEY=your-api-key-here
Why not paste the key directly in your code?
Storing secrets in a .env file separates configuration from code. Your server loads the key at startup using the dotenv package you installed earlier. This means your key never appears in the files you might share or upload.
- Save .env by pressing Cmd+S (macOS) or Ctrl+S (Windows).
Protect your secrets with a .gitignore file
A .gitignore file tells Git which files to never track or upload. This keeps your API key and bulky dependency folders safe if you ever push this project to GitHub.
- In VS Code, right-click in the Explorer panel and select New File.
- Name the file .gitignore and press Enter.
- Add the following contents to .gitignore:
.env
node_modules
What does this file do?
The first line keeps your .env file (with your secret API key) from being uploaded to any repository. The second line excludes node_modules, the folder containing your installed packages. That folder can be thousands of files and anyone cloning your project can regenerate it with npm install.
- Save .gitignore by pressing Cmd+S (macOS) or Ctrl+S (Windows).
- Confirm both files exist by running this in your VS Code terminal:
ls -a
- You should see .env and .gitignore listed alongside package.json and node_modules.
Don't see .env or .gitignore?
Files starting with a dot are hidden by default. The -a flag in ls -a reveals them. If they still do not appear, make sure you saved both files in the ticket-converter folder (not a subfolder or parent folder).
Help me find my .env and .gitignore files in my project
✔️ Awesome, I've got everything!
Great. Double check you have saved both files in the ticket-converter folder.
ⓧ I'd like to double check the full code
Here is what each file should look like at this point.
ANTHROPIC_API_KEY=your-api-key-here
.env
node_modules
{
"name": "ticket-converter",
"version": "1.0.0",
"description": "AI-powered task to ticket converter",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.96.0",
"dotenv": "^16.4.7",
"express": "^4.21.2"
}
}
Your project is now securely configured with a Claude API key. Next up, you will build the Express backend that uses this key to talk to Claude and generate structured tickets.
Build the Express Backend
Your API key is securely stored in .env and your project dependencies are installed. Now it's time to build the server that sits between your browser and the Claude API.
The backend's job is to receive a messy feature description from the frontend, send it to Claude with specific formatting instructions, and return the structured ticket. This keeps your API key hidden from anyone using the app.
In this step, get ready to:
- Set up an Express server with middleware for JSON parsing and static file serving.
- Write a system prompt that defines the technical ticket format.
- Create an API route that sends user text to Claude and returns the result.
Create the server file and configure Express
Every Node.js web server needs an entry point. Yours will be a single file called server.js that sets up Express and connects to the Claude API.
- Create a new file called server.js in the root of your ticket-converter folder.
- Add the following code to set up Express, middleware, and the Anthropic client:
// Import required packages
const express = require('express');
const Anthropic = require('@anthropic-ai/sdk').default;
require('dotenv').config();
// Create Express app and set port
const app = express();
const port = 3000;
// Parse incoming JSON and serve files from the 'public' folder
app.use(express.json());
app.use(express.static('public'));
// Initialize the Anthropic client with your API key from .env
const client = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
What does this code do?
- require('express') loads the Express framework that handles HTTP requests for you.
- require('@anthropic-ai/sdk').default imports the Anthropic SDK so you can talk to the Claude API.
- require('dotenv').config() loads your .env file so process.env.ANTHROPIC_API_KEY has your key.
- express.json() lets the server understand JSON data sent from the browser.
- express.static('public') serves your frontend HTML, CSS, and JS files from a public folder.
- Save server.js.
- You won't see a visible change yet because the server has no routes or listener. The next chunks add those.
Write the system prompt
A system prompt is a set of instructions that tells the AI how to behave before it sees any user input. This is where all the "intelligence" of your ticket converter lives.
- In server.js, add the following SYSTEM_PROMPT constant below the client initialization:
// System prompt that tells Claude how to format technical tickets
const SYSTEM_PROMPT = `You are a senior technical project manager. Your job is to take messy, informal feature descriptions and convert them into structured technical tickets.
For every input, output a well-formatted markdown technical ticket with these sections:
## Summary
A clear, one-sentence description of the feature or task.
## Technical Requirements
A numbered list of specific, actionable technical requirements.
## Acceptance Criteria
A checklist (using - [ ] format) of conditions that must be true for this ticket to be considered complete.
## Dependencies
List any external services, libraries, APIs, or other tickets this depends on. If none are obvious, write "None identified - verify with team."
## Estimated Complexity
Rate as Low, Medium, or High with a brief justification.
Rules:
- If the input is vague, make reasonable assumptions and note them
- Break large ideas into specific, testable requirements
- Use technical language appropriate for a development team
- Keep each requirement focused on a single concern
- If the input mentions specific technologies, include them in the requirements`;
Why does this prompt matter?
This prompt is doing the same job a senior engineer does when they translate a product request into a development ticket. It enforces a consistent structure (Summary, Requirements, Acceptance Criteria, Dependencies, Complexity) that a development team can immediately act on.
By specifying the exact sections and formatting rules, Claude produces predictable, professional output every time. This is the core skill of prompt engineering: shaping AI output by being explicit about format and expectations.
- Save server.js.
- You won't see a change yet. The next chunk adds the route that uses this prompt.
Add the API route and start the server
The final piece is a REST API route that receives user text, sends it to Claude along with your system prompt, and returns the generated ticket.
- In server.js, add the POST route and server listener below the SYSTEM_PROMPT constant:
// POST route that receives text and returns a structured ticket
app.post('/api/convert', async (req, res) => {
const { text } = req.body;
// Validate that the user sent some text
if (!text || text.trim().length === 0) {
return res.status(400).json({ error: 'Please provide a feature description.' });
}
try {
// Send the user's text to Claude with our system prompt
const message = await client.messages.create({
model: 'claude-haiku-4-5',
max_tokens: 1024,
system: SYSTEM_PROMPT,
messages: [
{ role: 'user', content: text }
],
});
// Extract the generated ticket text and send it back
const ticketContent = message.content[0].text;
res.json({ ticket: ticketContent });
} catch (error) {
console.error('Error calling Claude API:', error.message);
res.status(500).json({ error: 'Failed to generate ticket. Check your API key and try again.' });
}
});
// Start the server
app.listen(port, () => {
console.log(`Ticket Converter running at http://localhost:${port}`);
});
What does this route do?
- app.post('/api/convert', ...) creates an endpoint the frontend will call when the user submits the form.
- The validation check rejects empty submissions with a 400 error.
- client.messages.create() sends the user's messy text to Claude along with your system prompt.
- The model claude-haiku-4-5 is the fastest and most cost-efficient Claude model. Perfect for real-time form submissions.
- The try/catch block handles errors gracefully so the app doesn't crash if something goes wrong.
- Save server.js.
- Test that your server starts by running this command in your VS Code terminal:
node server.js
- You should see Ticket Converter running at http://localhost:3000 in the terminal.
- Press Ctrl+C in the terminal to stop the server for now.
Server not starting?
- If you see Cannot find module, make sure you ran npm install in Step 1. Your node_modules folder should exist in ticket-converter.
- If you see a syntax error, double-check that all backticks and brackets in your SYSTEM_PROMPT are correct. The template literal uses backtick characters, not regular quotes.
- If you see EADDRINUSE, another process is using port 3000. Close other terminal windows or restart VS Code.
Help me debug my server.js file that won't start
✔️ Awesome, I've got everything!
Great. Double check you've saved server.js and confirmed the server starts without errors.
ⓧ I'd like to double check the full code
Here is the complete server.js file. Compare yours against this:
const express = require('express');
const Anthropic = require('@anthropic-ai/sdk').default;
require('dotenv').config();
const app = express();
const port = 3000;
app.use(express.json());
app.use(express.static('public'));
const client = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
const SYSTEM_PROMPT = `You are a senior technical project manager. Your job is to take messy, informal feature descriptions and convert them into structured technical tickets.
For every input, output a well-formatted markdown technical ticket with these sections:
## Summary
A clear, one-sentence description of the feature or task.
## Technical Requirements
A numbered list of specific, actionable technical requirements.
## Acceptance Criteria
A checklist (using - [ ] format) of conditions that must be true for this ticket to be considered complete.
## Dependencies
List any external services, libraries, APIs, or other tickets this depends on. If none are obvious, write "None identified - verify with team."
## Estimated Complexity
Rate as Low, Medium, or High with a brief justification.
Rules:
- If the input is vague, make reasonable assumptions and note them
- Break large ideas into specific, testable requirements
- Use technical language appropriate for a development team
- Keep each requirement focused on a single concern
- If the input mentions specific technologies, include them in the requirements`;
app.post('/api/convert', async (req, res) => {
const { text } = req.body;
if (!text || text.trim().length === 0) {
return res.status(400).json({ error: 'Please provide a feature description.' });
}
try {
const message = await client.messages.create({
model: 'claude-haiku-4-5',
max_tokens: 1024,
system: SYSTEM_PROMPT,
messages: [
{ role: 'user', content: text }
],
});
const ticketContent = message.content[0].text;
res.json({ ticket: ticketContent });
} catch (error) {
console.error('Error calling Claude API:', error.message);
res.status(500).json({ error: 'Failed to generate ticket. Check your API key and try again.' });
}
});
app.listen(port, () => {
console.log(`Ticket Converter running at http://localhost:${port}`);
});
Why keep the API key on the server?
Your server.js acts as a proxy between the browser and the Claude API. The browser never sees your API key because it only talks to your local server at /api/convert.
This client-server architecture pattern is how production apps protect credentials. If you put the key in frontend JavaScript, anyone could inspect your page and steal it.
Your backend is ready to receive requests and generate structured tickets. Next up, you'll build the frontend form that sends feature ideas to this server.
Build the Frontend Form
Your backend is ready to receive feature descriptions and return structured tickets. Now you need a way for users to actually interact with it.
In this step, you will build the frontend that your Express server already knows how to serve. Remember the express.static('public') line in server.js? That tells Express to look for a public folder and serve any files inside it to the browser.
In this step, get ready to:
- Create an HTML form where users type their messy feature ideas.
- Style the page to look clean and professional.
- Add JavaScript that sends the form input to your backend and displays the result.
Create the HTML page
The public folder does not exist yet. You need to create it first, then add your HTML file inside.
- Create the public folder inside your ticket-converter project by running this command in your VS Code terminal:
mkdir public
- Confirm the folder exists by running:
ls
You should see public listed alongside server.js, package.json, and node_modules.
- Create public/index.html in VS Code by clicking File in the top menu bar.
- Click New File.
- Type public/index.html as the file name and press Enter.
- Paste the following HTML into the new file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Task-to-Ticket Converter</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<h1>Task-to-Ticket Converter</h1>
<p class="subtitle">Turn messy feature ideas into structured technical tickets</p>
<form id="converter-form">
<textarea
id="feature-input"
placeholder="Describe your feature idea... e.g., 'I need a login page with a blue button and it should save to Firebase'"
rows="4"
></textarea>
<button type="submit" id="submit-btn">Convert to Ticket</button>
</form>
<div id="output-area" class="hidden">
<h2>Generated Ticket</h2>
<div id="ticket-output"></div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
What does this HTML do?
The <form> contains a <textarea> where users type their messy feature idea, and a submit button that triggers the conversion.
The output-area div starts hidden (via the hidden class) and only appears after Claude returns a generated ticket. The script.js file is loaded at the bottom so the HTML elements exist before the script tries to find them.
- Save public/index.html.
File not appearing in the public folder?
Make sure you typed public/index.html (with the folder prefix) as the filename. If you only typed index.html, the file ends up in your project root instead of inside public.
help me figure out why my index.html file is in the wrong location
Style the page with CSS
The HTML structure is in place, but it needs styling to look like a real application. You will create a stylesheet that gives the form a card-style layout and makes the output area easy to read.
- Create a new file called public/style.css (click File > New File, then type public/style.css).
- Paste the following layout and form styles into the file:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background-color: #f5f5f5;
color: #333;
line-height: 1.6;
}
.container {
max-width: 700px;
margin: 40px auto;
padding: 20px;
}
h1 {
font-size: 1.8rem;
margin-bottom: 4px;
}
.subtitle {
color: #666;
margin-bottom: 24px;
}
form {
background: white;
padding: 24px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
textarea {
width: 100%;
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 6px;
font-size: 1rem;
font-family: inherit;
resize: vertical;
transition: border-color 0.2s;
}
textarea:focus {
outline: none;
border-color: #4a90d9;
}
button {
margin-top: 12px;
padding: 12px 24px;
background-color: #4a90d9;
color: white;
border: none;
border-radius: 6px;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.2s;
}
button:hover {
background-color: #357abd;
}
button:disabled {
background-color: #a0a0a0;
cursor: not-allowed;
}
What do these styles do?
The * reset removes default browser spacing so everything starts from a clean slate. The .container centers the page content at a max width of 700px.
The form selector creates a white card with a subtle shadow. The button uses a blue color (#4a90d9) that darkens on hover and grays out when disabled.
- Now add the output area styles below what you just pasted:
#output-area {
margin-top: 24px;
background: white;
padding: 24px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
#output-area h2 {
font-size: 1.2rem;
margin-bottom: 16px;
color: #4a90d9;
}
#ticket-output {
white-space: pre-wrap;
font-family: 'Courier New', monospace;
font-size: 0.9rem;
line-height: 1.8;
background: #f9f9f9;
padding: 16px;
border-radius: 6px;
border: 1px solid #e0e0e0;
}
.hidden {
display: none;
}
What do the output styles do?
The #ticket-output uses a monospace font and pre-wrap whitespace so the markdown ticket preserves its formatting and line breaks. The .hidden class hides the output area until a ticket is actually generated.
- Save public/style.css.
Styles not looking right later?
Make sure the output area styles come after the button styles in the same file. CSS reads top to bottom, so order matters.
Check that your file is saved as public/style.css (inside the public folder), not style.css in the project root.
help me debug why my CSS styles are not applying
✔️ Awesome, I've got everything!
Great. Make sure you have saved public/style.css before moving on.
ⓧ I'd like to double check the full code
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background-color: #f5f5f5;
color: #333;
line-height: 1.6;
}
.container {
max-width: 700px;
margin: 40px auto;
padding: 20px;
}
h1 {
font-size: 1.8rem;
margin-bottom: 4px;
}
.subtitle {
color: #666;
margin-bottom: 24px;
}
form {
background: white;
padding: 24px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
textarea {
width: 100%;
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 6px;
font-size: 1rem;
font-family: inherit;
resize: vertical;
transition: border-color 0.2s;
}
textarea:focus {
outline: none;
border-color: #4a90d9;
}
button {
margin-top: 12px;
padding: 12px 24px;
background-color: #4a90d9;
color: white;
border: none;
border-radius: 6px;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.2s;
}
button:hover {
background-color: #357abd;
}
button:disabled {
background-color: #a0a0a0;
cursor: not-allowed;
}
#output-area {
margin-top: 24px;
background: white;
padding: 24px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
#output-area h2 {
font-size: 1.2rem;
margin-bottom: 16px;
color: #4a90d9;
}
#ticket-output {
white-space: pre-wrap;
font-family: 'Courier New', monospace;
font-size: 0.9rem;
line-height: 1.8;
background: #f9f9f9;
padding: 16px;
border-radius: 6px;
border: 1px solid #e0e0e0;
}
.hidden {
display: none;
}
Add the form submission logic
The HTML and CSS handle how the page looks. Now you need JavaScript to handle what happens when a user submits the form. This script will send the user's text to your backend and display the AI-generated ticket.
- Create a new file called public/script.js (click File > New File, then type public/script.js).
- Paste the following JavaScript into the file:
// Select the form and output elements from the page
const form = document.getElementById('converter-form');
const input = document.getElementById('feature-input');
const submitBtn = document.getElementById('submit-btn');
const outputArea = document.getElementById('output-area');
const ticketOutput = document.getElementById('ticket-output');
// Listen for form submissions
form.addEventListener('submit', async (e) => {
e.preventDefault();
const text = input.value.trim();
if (!text) return;
// Disable the button and show a loading state
submitBtn.disabled = true;
submitBtn.textContent = 'Converting...';
try {
// Send the user's text to the backend
const response = await fetch('/api/convert', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text }),
});
const data = await response.json();
if (response.ok) {
// Display the generated ticket
ticketOutput.textContent = data.ticket;
outputArea.classList.remove('hidden');
} else {
// Display the error message from the server
ticketOutput.textContent = 'Error: ' + data.error;
outputArea.classList.remove('hidden');
}
} catch (error) {
ticketOutput.textContent = 'Error: Could not connect to server. Make sure it is running.';
outputArea.classList.remove('hidden');
} finally {
// Re-enable the button regardless of success or failure
submitBtn.disabled = false;
submitBtn.textContent = 'Convert to Ticket';
}
});
What does this script do?
- It grabs references to the form, textarea, button, and output elements using their IDs.
- When the form is submitted, it prevents the default page reload with e.preventDefault().
- It disables the button and changes the text to Converting... so the user knows something is happening.
- The fetch call sends a POST request to your /api/convert endpoint with the user's text as JSON.
- On success, it shows the ticket in the output area. On failure, it displays an error message instead.
- Save public/script.js.
Worried about typos in element IDs?
The IDs in script.js (like converter-form and feature-input) must match exactly what you have in index.html. If they don't match, the script will silently fail.
help me check if my HTML IDs match my JavaScript selectors
✔️ Awesome, I've got everything!
Great. Double check you have saved all three files: public/index.html, public/style.css, and public/script.js.
ⓧ I'd like to double check the full code
Here is the complete public/script.js:
const form = document.getElementById('converter-form');
const input = document.getElementById('feature-input');
const submitBtn = document.getElementById('submit-btn');
const outputArea = document.getElementById('output-area');
const ticketOutput = document.getElementById('ticket-output');
form.addEventListener('submit', async (e) => {
e.preventDefault();
const text = input.value.trim();
if (!text) return;
submitBtn.disabled = true;
submitBtn.textContent = 'Converting...';
try {
const response = await fetch('/api/convert', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text }),
});
const data = await response.json();
if (response.ok) {
ticketOutput.textContent = data.ticket;
outputArea.classList.remove('hidden');
} else {
ticketOutput.textContent = 'Error: ' + data.error;
outputArea.classList.remove('hidden');
}
} catch (error) {
ticketOutput.textContent = 'Error: Could not connect to server. Make sure it is running.';
outputArea.classList.remove('hidden');
} finally {
submitBtn.disabled = false;
submitBtn.textContent = 'Convert to Ticket';
}
});
Your frontend is complete. All three pieces of the puzzle are in place: the HTML structure, the CSS styling, and the JavaScript logic. Next up, you will start the server and see everything working together.
Test Your Ticket Converter
Your Express server, HTML form, CSS styling, and JavaScript logic are all written and ready. Now comes the moment of truth: testing whether your system prompt actually transforms messy ideas into professional tickets.
The quality of the output depends entirely on how well you engineered your system prompt. You'll test with both specific and vague inputs to see how Claude responds to each.
In this step, get ready to:
- Start your Express server and load the app in your browser.
- Convert a specific feature idea into a structured technical ticket.
- Test how the AI handles vague, ambiguous input.
Start the Express server
Your server code is ready to go, but it needs to be started before the browser can reach it. The node command runs your server.js file and keeps it alive to listen for incoming requests.
- Make sure you have saved all your project files.
- In your VS Code terminal, start the server by running this command:
node server.js
- You should see this message in your terminal:
Ticket Converter running at http://localhost:3000
What does this mean?
Your Express server is now running and listening on port 3000. It will serve your HTML form and handle API requests until you stop it with Ctrl+C.
- Open your browser and navigate to http://localhost:3000.
- You should see the Task-to-Ticket Converter form with a heading, subtitle, textarea, and a blue Convert to Ticket button.
Don't see the page?
- Make sure your terminal still shows the running message. If you see an error about the port being in use, another process is already on port 3000. Close other running servers and try again.
- Check that you have a public folder in your project with index.html inside it. Express serves static files from this exact folder name.
- If you see a "Cannot find module" error, make sure you ran npm install in your ticket-converter folder first.
My server won't start or I can't see the page
Convert a specific feature description
A specific feature description gives Claude clear details to work with. This is the ideal test case because named technologies and concrete requirements let you see the full structure of a generated ticket.
- Click inside the textarea on your page.
- Type the following messy feature idea:
I need a login page with a blue button and it should save to Firebase
- Click the Convert to Ticket button.
- Wait a few seconds. The button text changes to Converting... while Claude processes your request.
A structured ticket appears below the form under the Generated Ticket heading.
What should I see in the output?
Your generated ticket should contain these five sections, each produced by the system prompt you wrote in server.js:
- Summary - a one-sentence description of the feature.
- Technical Requirements - a numbered list of specific, actionable items. Because you mentioned Firebase, you should see Firebase Authentication or Firestore in the requirements.
- Acceptance Criteria - a checklist of conditions that must be true for the ticket to be complete.
- Dependencies - external services or libraries needed. Firebase should appear here.
- Estimated Complexity - a Low, Medium, or High rating with a brief justification.
Seeing an error instead of a ticket?
- If you see "Failed to generate ticket. Check your API key and try again," open your .env file and verify that your API key is pasted correctly with no extra spaces.
- If you see "Could not connect to server," your server may have crashed. Check your terminal for error messages and restart with node server.js.
- If your API key is brand new, it may take a minute to activate. Wait 30 seconds and try again.
I'm getting an error when I try to convert my feature idea
Test with a vague description
Real feature requests are often frustratingly vague. Testing with ambiguous input shows you how the system prompt handles uncertainty. Your prompt instructs Claude to make reasonable assumptions and note them.
- Clear the textarea and type this vague request:
make the app faster
- Click Convert to Ticket and wait for the result.
- Read through the generated ticket. Notice how Claude interprets the vague request with assumptions about caching, database optimization, or frontend performance.
Why does vague input still produce a good ticket?
Your system prompt includes the rule: "If the input is vague, make reasonable assumptions and note them." This is the power of prompt engineering.
Claude follows these instructions to fill in gaps with sensible defaults. In a real team, this output becomes a starting point for discussion rather than a final specification.
Try a few more inputs on your own. Experiment with different levels of detail and see how the ticket structure adapts. The system prompt keeps the output format consistent regardless of input quality.
Secret mission
Add Ticket Type Selection
Your converter handles feature requests well. But real engineering teams write different kinds of tickets. Can you add a ticket type selector that changes the AI output format for Bug Reports and Infrastructure Tasks?
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely on your local machine. The only ongoing cost is the Claude API, which charges per use. If you stop using the app, no charges accumulate.
Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- The ticket-converter project folder on your Desktop.
- An Anthropic API key stored in your .env file.
- A running Node.js server on port 3000.
Keep everything running
No action needed. Choose this if you are still actively building or experimenting with different prompts.
- Your server continues running in the terminal where you started it.
- Your API key remains active and ready for use.
- The Claude API only charges when you submit a conversion, so leaving the app idle costs nothing.
Pause - I'll come back to this later
Shut down the running server to free up your terminal. Your project files and API key stay intact for next time.
- In the terminal where your server is running, press Ctrl+C to stop the server.
- Your ticket-converter folder and all its files remain on your Desktop.
- Your API key stays saved in the .env file, ready for when you return.
- To restart later, open a terminal in the project folder and run node server.js.
Delete - I don't want to use this again
Remove all project files and revoke your API key so nothing is left behind.
- In the terminal where your server is running, press Ctrl+C to stop the server.
- Delete the project folder by running the following commands for your platform.
macOS
rm -rf ~/Desktop/ticket-converter
Windows
rmdir /s /q %USERPROFILE%\Desktop\ticket-converter
- Revoke your Anthropic API key so it cannot be used again.
- Go to platform.claude.com and sign in.
- Navigate to the API Keys section in your account settings.
- Find the key you created for this project and revoke or disable it.
Nice Work!
Nice Work!
You just built an AI-powered ticket converter that turns messy ideas into professional technical documentation.
Here's what you accomplished:
- Built a Node.js/Express backend that securely calls the Claude API using the client-server architecture pattern.
- Engineered a system prompt that transforms casual descriptions into structured technical tickets with Summary, Requirements, Acceptance Criteria, Dependencies, and Complexity sections.
- Created a web frontend that sends user input to your backend and displays formatted markdown results in the browser.
- Secret Mission: Added dynamic ticket type selection that changes the AI output format based on whether you're writing a Feature Request, Bug Report, or Infrastructure Task.
Ready to quiz yourself?