API Endpoint
A REST API for chat completions with SSE streaming support.
Features
- SSE Streaming - Server-Sent Events for real-time responses
- Rate Limiting - In-memory request throttling
- Validation - Request body validation
- Error Handling - Proper HTTP status codes
Quick Start
bash
export OPENAI_API_KEY=your_key
npm run recipe:api-endpointHow It Works
This recipe creates a simple HTTP server that wraps Meloqui's ChatClient, exposing it as a REST API with SSE streaming support.
Endpoints:
POST /chat- Send a message and get a completion (supports streaming)GET /health- Health check endpoint
Flow:
- Client sends a POST request with a message
- Server validates the request and checks rate limits
- Meloqui's ChatClient forwards the request to OpenAI
- Response streams back through SSE or returns as JSON
Architecture
API Reference
POST /chat
Send a chat message and receive a response.
Request:
json
{
"message": "Hello!",
"stream": false
}| Field | Type | Required | Description |
|---|---|---|---|
message | string | Yes | The message to send |
stream | boolean | No | Enable SSE streaming (default: false) |
Response (non-streaming):
json
{
"content": "Hi there! How can I help you?",
"tokensUsed": 15
}Response (streaming):
Content-Type: text/event-stream
data: {"content": "Hi"}
data: {"content": " there"}
data: {"content": "!"}
data: [DONE]GET /health
Health check endpoint.
json
{"content": "ok"}Testing with curl
Non-streaming Request
bash
curl -X POST http://localhost:3000/chat \
-H "Content-Type: application/json" \
-d '{"message": "What is 2+2?"}'Streaming Request
bash
curl -X POST http://localhost:3000/chat \
-H "Content-Type: application/json" \
-d '{"message": "Tell me a short joke", "stream": true}'Health Check
bash
curl http://localhost:3000/healthCode Walkthrough
Server Setup
typescript
import * as http from 'http';
import { ChatClient } from 'meloqui';
const client = new ChatClient({
provider: 'openai',
model: 'gpt-4o-mini',
apiKey: process.env.OPENAI_API_KEY
});
const server = http.createServer(async (req, res) => {
if (req.url === '/chat' && req.method === 'POST') {
await handleChat(req, res, client);
}
});
server.listen(3000);SSE Streaming
typescript
// Set SSE headers
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
// Stream chunks
for await (const chunk of client.stream(message)) {
res.write(`data: ${JSON.stringify({ content: chunk.content })}\n\n`);
}
res.write('data: [DONE]\n\n');
res.end();Rate Limiting
typescript
const RATE_LIMIT_WINDOW_MS = 60_000; // 1 minute
const RATE_LIMIT_MAX_REQUESTS = 20;
const rateLimitMap = new Map<string, { count: number; resetTime: number }>();
function checkRateLimit(clientIP: string): boolean {
const now = Date.now();
const record = rateLimitMap.get(clientIP);
if (!record || now > record.resetTime) {
rateLimitMap.set(clientIP, { count: 1, resetTime: now + RATE_LIMIT_WINDOW_MS });
return true;
}
if (record.count >= RATE_LIMIT_MAX_REQUESTS) {
return false;
}
record.count++;
return true;
}Error Responses
| Status | Code | Description |
|---|---|---|
| 400 | INVALID_REQUEST | Invalid JSON body |
| 400 | VALIDATION_ERROR | Missing or invalid fields |
| 401 | AUTH_ERROR | API key authentication failed |
| 404 | NOT_FOUND | Endpoint not found |
| 429 | RATE_LIMITED | Too many requests |
| 429 | PROVIDER_RATE_LIMITED | OpenAI rate limit hit |
| 500 | CHAT_ERROR | Chat completion failed |
| 500 | INTERNAL_ERROR | Unexpected error |
Rate Limit Headers
All responses include rate limit information:
X-RateLimit-Limit: 20
X-RateLimit-Remaining: 19
X-RateLimit-Reset: 60Configuration
Environment variables:
| Variable | Default | Description |
|---|---|---|
OPENAI_API_KEY | - | Required. OpenAI API key |
PORT | 3000 | Server port |
