Building Your First AI App: A Complete Tutorial
Learn how to build and deploy your first AI application from scratch using Next.js and OpenAI's GPT API. Complete with code examples and deployment guide.
Building Your First AI App: A Complete Tutorial
Ever wanted to build your own AI application but didn't know where to start? This tutorial will walk you through creating a simple AI writing assistant from scratch.
What we'll build: A clean, modern AI writing assistant that helps users improve their writing with suggestions, tone adjustments, and grammar corrections.
Tech stack: Next.js, React, OpenAI API, Tailwind CSS
Time required: 2-3 hours
Prerequisites: Basic knowledge of JavaScript and React
What You'll Learn
By the end of this tutorial, you'll have:
- A fully functional AI writing assistant
- Understanding of OpenAI API integration
- Knowledge of AI app architecture patterns
- A deployed application you can share
Let's dive in!
Step 1: Project Setup
First, let's create a new Next.js project:
npx create-next-app@latest ai-writing-assistant
cd ai-writing-assistant
npm install openai
For styling, we'll use Tailwind CSS (it should already be included in the Next.js setup).
Step 2: Environment Configuration
Create a .env.local
file in your project root:
OPENAI_API_KEY=your_openai_api_key_here
NEXT_PUBLIC_APP_URL=http://localhost:3000
Important: Never commit your API key to version control. Get your API key from OpenAI's dashboard.
Step 3: Setting Up the OpenAI Client
Create lib/openai.js
:
import OpenAI from 'openai';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
export default openai;
Step 4: Creating the API Route
Create pages/api/improve-writing.js
:
import openai from '../../lib/openai';
export default async function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).json({ message: 'Method not allowed' });
}
const { text, mode } = req.body;
if (!text) {
return res.status(400).json({ message: 'Text is required' });
}
try {
let prompt;
switch (mode) {
case 'improve':
prompt = `Improve the following text while maintaining its original meaning and tone. Make it clearer, more engaging, and better structured:\n\n${text}`;
break;
case 'formal':
prompt = `Rewrite the following text in a more formal, professional tone:\n\n${text}`;
break;
case 'casual':
prompt = `Rewrite the following text in a more casual, friendly tone:\n\n${text}`;
break;
case 'grammar':
prompt = `Fix any grammar, spelling, and punctuation errors in the following text:\n\n${text}`;
break;
default:
prompt = `Improve the following text:\n\n${text}`;
}
const completion = await openai.chat.completions.create({
model: "gpt-3.5-turbo",
messages: [
{
role: "system",
content: "You are a helpful writing assistant. Provide clear, improved versions of text while maintaining the original intent."
},
{
role: "user",
content: prompt
}
],
max_tokens: 1000,
temperature: 0.7,
});
const improvedText = completion.choices[0].message.content;
res.status(200).json({
improvedText,
originalText: text,
mode
});
} catch (error) {
console.error('OpenAI API error:', error);
res.status(500).json({
message: 'Error processing your request',
error: error.message
});
}
}
Step 5: Building the Frontend
Replace the content of pages/index.js
:
import { useState } from 'react';
import Head from 'next/head';
export default function Home() {
const [text, setText] = useState('');
const [improvedText, setImprovedText] = useState('');
const [mode, setMode] = useState('improve');
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
if (!text.trim()) {
setError('Please enter some text to improve');
return;
}
setLoading(true);
setError('');
try {
const response = await fetch('/api/improve-writing', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ text, mode }),
});
if (!response.ok) {
throw new Error('Failed to improve text');
}
const data = await response.json();
setImprovedText(data.improvedText);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
const copyToClipboard = async () => {
try {
await navigator.clipboard.writeText(improvedText);
alert('Text copied to clipboard!');
} catch (err) {
console.error('Failed to copy text:', err);
}
};
return (
<div className="min-h-screen bg-gray-50">
<Head>
<title>AI Writing Assistant</title>
<meta name="description" content="Improve your writing with AI" />
</Head>
<div className="container mx-auto px-4 py-8">
<header className="text-center mb-8">
<h1 className="text-4xl font-bold text-gray-900 mb-2">
AI Writing Assistant
</h1>
<p className="text-gray-600">
Improve your writing with the power of AI
</p>
</header>
<div className="max-w-4xl mx-auto">
<form onSubmit={handleSubmit} className="space-y-6">
{/* Mode Selection */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Choose improvement mode:
</label>
<select
value={mode}
onChange={(e) => setMode(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="improve">General Improvement</option>
<option value="formal">Make More Formal</option>
<option value="casual">Make More Casual</option>
<option value="grammar">Fix Grammar</option>
</select>
</div>
{/* Text Input */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Enter your text:
</label>
<textarea
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Paste your text here..."
rows={8}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
{/* Submit Button */}
<button
type="submit"
disabled={loading}
className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
>
{loading ? 'Improving...' : 'Improve Text'}
</button>
</form>
{/* Error Message */}
{error && (
<div className="mt-4 p-4 bg-red-100 border border-red-400 text-red-700 rounded-md">
{error}
</div>
)}
{/* Results */}
{improvedText && (
<div className="mt-8 space-y-4">
<div className="bg-white p-6 rounded-lg shadow-md">
<div className="flex justify-between items-center mb-4">
<h3 className="text-lg font-semibold text-gray-900">
Improved Text
</h3>
<button
onClick={copyToClipboard}
className="bg-green-600 text-white px-4 py-2 rounded-md hover:bg-green-700 transition-colors"
>
Copy to Clipboard
</button>
</div>
<div className="prose max-w-none">
<p className="text-gray-800 leading-relaxed">
{improvedText}
</p>
</div>
</div>
{/* Original Text for Comparison */}
<div className="bg-gray-100 p-6 rounded-lg">
<h3 className="text-lg font-semibold text-gray-900 mb-4">
Original Text
</h3>
<p className="text-gray-700 leading-relaxed">
{text}
</p>
</div>
</div>
)}
</div>
</div>
</div>
);
}
Step 6: Testing Your Application
Run your development server:
npm run dev
Open http://localhost:3000 and test your AI writing assistant!
Step 7: Adding Error Handling and Rate Limiting
For production applications, you'll want to add proper error handling and rate limiting:
// lib/rateLimit.js
import { LRUCache } from 'lru-cache';
type Options = {
uniqueTokenPerInterval?: number;
interval?: number;
};
export default function rateLimit(options: Options) {
const tokenCache = new LRUCache({
max: options.uniqueTokenPerInterval || 500,
maxAge: options.interval || 60000,
});
return {
check: (key: string, limit: number) =>
new Promise<void>((resolve, reject) => {
const tokenCount = (tokenCache.get(key) as number[]) || [0];
if (tokenCount[0] === 0) {
tokenCache.set(key, tokenCount);
}
tokenCount[0] += 1;
const currentUsage = tokenCount[0];
const isRateLimited = currentUsage >= limit;
if (isRateLimited) {
reject(new Error('Rate limit exceeded'));
} else {
resolve();
}
}),
};
}
Step 8: Deployment
Deploy to Vercel (Recommended)
- Push your code to GitHub
- Connect your GitHub repo to Vercel
- Add your environment variables in Vercel dashboard
- Deploy!
Deploy to Netlify
- Build your app:
npm run build
- Deploy the
out
directory to Netlify - Add environment variables in Netlify dashboard
Step 9: Enhancements and Next Steps
Now that you have a basic AI writing assistant, here are some enhancements you can add:
Advanced Features
- Character count and word count display
- Writing style analysis
- Tone detection and adjustment
- Multiple language support
User Experience Improvements
- Dark mode toggle
- Writing history
- Export to different formats
- Real-time suggestions
Business Features
- User authentication
- Usage tracking and limits
- Subscription plans
- API analytics
Code Examples for Common Enhancements
Adding Word Count
const WordCount = ({ text }) => {
const words = text.trim().split(/\s+/).length;
const characters = text.length;
return (
<div className="text-sm text-gray-500">
{words} words, {characters} characters
</div>
);
};
Adding Dark Mode
const [darkMode, setDarkMode] = useState(false);
const toggleDarkMode = () => {
setDarkMode(!darkMode);
document.documentElement.classList.toggle('dark');
};
Adding User Authentication
// Using NextAuth.js
import { useSession, signIn, signOut } from 'next-auth/react';
const AuthButton = () => {
const { data: session } = useSession();
if (session) {
return (
<button onClick={() => signOut()}>
Sign out {session.user.email}
</button>
);
}
return (
<button onClick={() => signIn()}>
Sign in
</button>
);
};
Performance Optimization Tips
API Optimization
- Cache common requests
- Implement request debouncing
- Use streaming for long responses
- Optimize prompt engineering
Frontend Optimization
- Use React.memo for expensive components
- Implement lazy loading
- Optimize images and assets
- Use service workers for offline functionality
Common Pitfalls and Solutions
API Rate Limits
- Implement client-side rate limiting
- Show clear error messages
- Provide usage statistics to users
High API Costs
- Set usage limits per user
- Implement caching strategies
- Use cheaper models when possible
Poor User Experience
- Add loading indicators
- Implement error boundaries
- Provide clear feedback
Security Considerations
API Key Security
- Never expose API keys in client-side code
- Use environment variables
- Implement server-side validation
Input Validation
- Sanitize user inputs
- Implement rate limiting
- Add content filtering
Conclusion
Congratulations! You've built your first AI application. This tutorial covered:
- Setting up a Next.js project with OpenAI integration
- Creating API routes for AI functionality
- Building a responsive frontend
- Implementing error handling
- Deployment strategies
Your next steps:
- Add more features from the enhancement list
- Deploy to production and get user feedback
- Monitor usage and optimize performance
- Consider monetization strategies
Remember: The best AI apps solve real problems for real people. Focus on user value, not just cool AI features.
What will you build next?
Want to see more AI app tutorials? Check out our complete development guides for different types of AI applications.
Found this helpful?
Share it with others who might benefit.