A production-ready Express.js backend API that provides chat functionality using Google's Gemini AI. Built with TypeScript, following Express.js best practices with proper separation of concerns, authentication, rate limiting, and comprehensive error handling.
- ✅ Clean Architecture: Controllers, Services, Routes separation
- ✅ API Versioning:
/api/v1prefix for future compatibility - ✅ Authentication: API key-based authentication
- ✅ Rate Limiting: Protection against abuse
- ✅ Error Handling: Centralized error handling with custom error types
- ✅ TypeScript: Full type safety
- ✅ Streaming Support: Server-Sent Events for real-time responses
- ✅ CORS Configuration: Configurable origin restrictions
- ✅ Request Logging: Built-in request/response logging
- ✅ Configuration Management: Centralized config with validation
npm installCopy the example environment file:
cp .env.example .envEdit .env and set required variables:
# Required
GOOGLE_API_KEY=your_google_api_key_here
API_KEY=your_secure_api_key_here
# Optional
PORT=3001
FRONTEND_URL=http://localhost:5173Get your Google API key: https://aistudio.google.com/app/apikey
Development:
npm run devProduction:
npm run build
npm startThe server will start on http://localhost:3001 (or your configured PORT).
http://localhost:3001/api/v1
All endpoints except /health require authentication. Include your API key in the request header:
Option 1: Bearer Token
Authorization: Bearer your_api_key_here
Option 2: Custom Header
x-api-key: your_api_key_here
Health check endpoint (no authentication required).
Response:
{
"status": "ok",
"message": "Server is running",
"timestamp": "2024-01-15T10:30:00.000Z"
}Send a message and receive a complete AI response.
Headers:
Authorization: Bearer your_api_key_here
Content-Type: application/json
Request Body:
{
"message": "What is TypeScript?",
"history": [
{
"role": "user",
"content": "Hello!"
},
{
"role": "assistant",
"content": "Hi! How can I help you today?"
}
]
}Response (Success):
{
"success": true,
"response": "TypeScript is a strongly typed programming language that builds on JavaScript..."
}Response (Error):
{
"success": false,
"error": "ValidationError",
"message": "Message is required",
"statusCode": 400,
"timestamp": "2024-01-15T10:30:00.000Z",
"path": "/api/v1/chat"
}Stream AI responses in real-time using Server-Sent Events.
Headers:
Authorization: Bearer your_api_key_here
Content-Type: application/json
Request Body: Same as /api/v1/chat
Response (SSE Stream):
data: {"success":true,"response":"Type","done":false}
data: {"success":true,"response":"Script","done":false}
data: {"success":true,"response":" is","done":false}
data: {"success":true,"response":"","done":true}
| Variable | Description | Example |
|---|---|---|
GOOGLE_API_KEY |
Google Gemini API key | AIza... |
API_KEY |
API key for endpoint authentication | my-secure-key-123 |
| Variable | Description | Default |
|---|---|---|
PORT |
Server port | 3001 |
NODE_ENV |
Environment mode | development |
FRONTEND_URL |
Allowed CORS origins (comma-separated) | http://localhost:5173 |
AI_MODEL |
Gemini model to use | gemini-2.0-flash-exp |
SYSTEM_INSTRUCTION |
AI system prompt | You are a helpful assistant... |
MAX_OUTPUT_TOKENS |
Maximum AI response length | 500 |
TEMPERATURE |
AI creativity (0.0-1.0) | 0.7 |
RATE_LIMIT_WINDOW_MS |
Rate limit window in milliseconds | 60000 (1 min) |
RATE_LIMIT_MAX_REQUESTS |
Max requests per window | 100 |
chatbot-widget-be/
├── index.ts # Main Express app
├── src/
│ ├── config/
│ │ └── index.ts # Centralized configuration
│ ├── controllers/
│ │ ├── chatController.ts # Chat request handlers
│ │ └── healthController.ts # Health check handler
│ ├── services/
│ │ └── aiService.ts # AI service layer
│ ├── middleware/
│ │ ├── auth.ts # API key authentication
│ │ ├── errorHandler.ts # Global error handler
│ │ ├── rateLimiter.ts # Rate limiting
│ │ └── requestLogger.ts # Request logging
│ ├── routes/
│ │ └── v1/
│ │ ├── index.ts # v1 route aggregator
│ │ ├── chat.routes.ts # Chat routes
│ │ └── health.routes.ts # Health routes
│ ├── errors/
│ │ └── index.ts # Custom error classes
│ └── types.ts # TypeScript type definitions
├── utils/
│ └── validation.ts # Request validation
├── .env.example # Environment template
├── package.json
└── tsconfig.json
- Controllers: Handle HTTP requests/responses
- Services: Business logic and external API calls
- Routes: Route definitions and middleware binding
- Middleware: Cross-cutting concerns (auth, logging, errors)
- Config: Centralized configuration management
Custom error classes for better error categorization:
ValidationError(400): Invalid request dataAuthenticationError(401): Invalid/missing API keyNotFoundError(404): Resource not foundRateLimitError(429): Rate limit exceededAIServiceError(503): AI service failure
- General API: 100 requests/minute per IP
- Chat endpoints: 20 requests/minute per IP (stricter)
- API key authentication on chat endpoints
- CORS with configurable origins
- Rate limiting to prevent abuse
- Request validation
- Error messages don't leak sensitive info
npm testnpm run buildnpm run lint- Set
NODE_ENV=production - Use strong
API_KEY - Configure
FRONTEND_URLto your production domain - Consider using a process manager (PM2, systemd)
- Set up monitoring and logging
- Use HTTPS in production
npm install -g pm2
npm run build
pm2 start dist/index.js --name chatbot-api
pm2 save
pm2 startupconst response = await fetch("http://localhost:3001/api/v1/chat", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer your_api_key_here",
},
body: JSON.stringify({
message: "Hello!",
history: [],
}),
});
const data = await response.json();
console.log(data.response);const eventSource = new EventSource(
"http://localhost:3001/api/v1/chat/stream",
{
headers: {
Authorization: "Bearer your_api_key_here",
},
}
);
eventSource.onmessage = (event) => {
const chunk = JSON.parse(event.data);
if (chunk.done) {
eventSource.close();
} else {
console.log(chunk.response);
}
};- Ensure
.envfile exists withGOOGLE_API_KEYandAPI_KEY
- Check that you're sending the correct API key in headers
- Verify header format:
Authorization: Bearer <key>orx-api-key: <key>
- You've hit the rate limit
- Wait a minute or adjust
RATE_LIMIT_MAX_REQUESTSin.env
- Add your frontend URL to
FRONTEND_URLin.env - Multiple origins:
FRONTEND_URL=http://localhost:3000,http://localhost:5173
- Fork the repository
- Create a feature branch
- Make your changes
- Test thoroughly
- Submit a pull request
MIT
For issues and questions, please open an issue on GitHub.