You've added an AI chat feature to your Rork app, but users drop off after waiting 4–5 seconds for a response. It's one of the most common friction points I see in AI-powered mobile apps — and Groq is one of the most effective tools to fix it.
Groq runs large language models on its proprietary LPU (Language Processing Unit) chips, which are optimized specifically for the sequential token generation that LLMs perform. The result is inference speeds several times faster than typical GPU-based providers. The first time I tested Groq's response, I genuinely thought the API had cached a previous reply — it was that fast.
What Makes Groq Different
Groq isn't another wrapper around GPT-4 or Claude. It's an inference-as-a-service platform that runs open-source models — Llama 3, Mixtral, Gemma 2 — on LPU hardware designed to maximize throughput for autoregressive text generation.
The practical implication for mobile apps: streaming responses feel almost instantaneous. Rather than a loading spinner followed by a wall of text, users see words appearing as they're generated. That shift in user experience — from waiting to reading — meaningfully improves engagement in conversational interfaces.
The free tier is also generous enough to build and test a full feature without spending anything.
Setting Up Your Groq API Key
Head to console.groq.com and sign up (Google login works). From the dashboard, navigate to API Keys and generate a new key. Copy it immediately — it won't be shown again.
In your Rork project, store the key as an environment variable rather than hardcoding it:
# .env (make sure this is listed in .gitignore)
EXPO_PUBLIC_GROQ_API_KEY=YOUR_GROQ_API_KEYIn production, consider proxying requests through a backend (Cloudflare Workers or an Edge Function) so the key never touches the client bundle. For now, the environment variable approach works well for development and testing.
Basic Chat Implementation
Groq exposes an OpenAI-compatible API, so you can use the openai npm package with a simple base URL swap. This makes migration from OpenAI straightforward — and lets you use Rork's AI to generate the integration code with a familiar prompt.
// src/lib/groq.js
import OpenAI from 'openai';
const groq = new OpenAI({
apiKey: process.env.EXPO_PUBLIC_GROQ_API_KEY,
baseURL: 'https://api.groq.com/openai/v1',
dangerouslyAllowBrowser: true, // required for Expo client-side calls
});
export async function askGroq(messages) {
try {
const response = await groq.chat.completions.create({
model: 'llama3-8b-8192',
messages,
max_tokens: 1024,
temperature: 0.7,
});
return response.choices[0]?.message?.content ?? 'No response received.';
} catch (error) {
if (error.status === 429) {
throw new Error('Rate limit reached. Please wait a moment and try again.');
}
if (error.status === 401) {
throw new Error('Invalid API key. Check your configuration.');
}
throw new Error(`Groq API error: ${error.message}`);
}
}The key detail here is baseURL — everything else follows the standard OpenAI SDK pattern. If you have existing OpenAI-powered features, you can point them at Groq with a one-line change and compare latency directly.
Streaming Responses in Real Time
Non-streaming is fine for simple lookups, but streaming is where Groq really shines. Here's a full chat screen component with streaming implemented:
// src/components/ChatScreen.jsx
import React, { useState, useRef } from 'react';
import { View, Text, TextInput, TouchableOpacity, ScrollView, StyleSheet } from 'react-native';
import OpenAI from 'openai';
const groq = new OpenAI({
apiKey: process.env.EXPO_PUBLIC_GROQ_API_KEY,
baseURL: 'https://api.groq.com/openai/v1',
dangerouslyAllowBrowser: true,
});
export default function ChatScreen() {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
const [isStreaming, setIsStreaming] = useState(false);
const scrollRef = useRef(null);
const sendMessage = async () => {
if (\!input.trim() || isStreaming) return;
const userMessage = { role: 'user', content: input };
const newMessages = [...messages, userMessage];
setMessages(newMessages);
setInput('');
setIsStreaming(true);
// Add an empty assistant message to fill in as chunks arrive
setMessages(prev => [...prev, { role: 'assistant', content: '' }]);
try {
const stream = await groq.chat.completions.create({
model: 'llama3-8b-8192',
messages: newMessages,
stream: true,
max_tokens: 1024,
});
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta?.content ?? '';
if (delta) {
setMessages(prev => {
const updated = [...prev];
updated[updated.length - 1] = {
role: 'assistant',
content: updated[updated.length - 1].content + delta,
};
return updated;
});
scrollRef.current?.scrollToEnd({ animated: false });
}
}
} catch (error) {
setMessages(prev => {
const updated = [...prev];
updated[updated.length - 1] = {
role: 'assistant',
content: `Error: ${error.message}`,
};
return updated;
});
} finally {
setIsStreaming(false);
}
};
return (
<View style={styles.container}>
<ScrollView ref={scrollRef} style={styles.chatArea}>
{messages.map((msg, i) => (
<View key={i} style={[styles.bubble, msg.role === 'user' ? styles.user : styles.assistant]}>
<Text style={styles.text}>{msg.content}</Text>
</View>
))}
</ScrollView>
<View style={styles.inputRow}>
<TextInput
style={styles.input}
value={input}
onChangeText={setInput}
placeholder="Type a message..."
editable={\!isStreaming}
multiline
/>
<TouchableOpacity
style={[styles.sendButton, isStreaming && styles.disabled]}
onPress={sendMessage}
disabled={isStreaming}
>
<Text style={styles.sendText}>{isStreaming ? '...' : 'Send'}</Text>
</TouchableOpacity>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#f5f5f5' },
chatArea: { flex: 1, padding: 12 },
bubble: { maxWidth: '80%', borderRadius: 12, padding: 10, marginBottom: 8 },
user: { backgroundColor: '#007AFF', alignSelf: 'flex-end' },
assistant: { backgroundColor: '#fff', alignSelf: 'flex-start' },
text: { color: '#333', fontSize: 15, lineHeight: 22 },
inputRow: { flexDirection: 'row', padding: 8, backgroundColor: '#fff', borderTopWidth: 1, borderTopColor: '#eee' },
input: { flex: 1, borderWidth: 1, borderColor: '#ddd', borderRadius: 8, padding: 8, marginRight: 8, maxHeight: 100 },
sendButton: { backgroundColor: '#007AFF', borderRadius: 8, paddingHorizontal: 16, justifyContent: 'center' },
disabled: { backgroundColor: '#aaa' },
sendText: { color: '#fff', fontWeight: 'bold' },
});The for await...of loop is the heart of it — each iteration receives a chunk from Groq and appends the delta to the last message. Combined with Groq's LPU speed, this creates a response that genuinely feels live rather than delayed.
Choosing the Right Model
Groq gives you several model options. Here's how I think about the tradeoffs:
- llama3-8b-8192 — fastest, widest free quota, great for conversational use cases. My starting point for almost everything.
- llama3-70b-8192 — slower but more capable. Worth the switch when responses need nuanced reasoning or longer form output.
- mixtral-8x7b-32768 — 32K context window. Use this when you need to maintain a long conversation history without summarization.
- gemma2-9b-it — Google DeepMind's model, competitive on multilingual tasks.
Swapping models is a one-line change in the model field, so it's easy to A/B test within your app and measure which performs better for your specific use case.
Handling Rate Limits Gracefully
Groq's free tier has per-minute token and request limits that vary by model. For production, you'll want a retry strategy:
// Exponential backoff retry wrapper
async function askGroqWithRetry(messages, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await askGroq(messages);
} catch (error) {
const isRateLimit = error.message.includes('Rate limit');
if (isRateLimit && attempt < maxRetries - 1) {
// Wait 1s, 2s, 4s before retrying
const waitMs = Math.pow(2, attempt) * 1000;
console.log(`Rate limited — retrying in ${waitMs / 1000}s (attempt ${attempt + 1}/${maxRetries})`);
await new Promise(resolve => setTimeout(resolve, waitMs));
continue;
}
throw error;
}
}
}For apps beyond the prototype stage, routing API calls through a backend API integration is the right move — it hides your key, lets you add rate limiting per user, and gives you a place to add logging.
When to Use Groq vs. Other Providers
Groq is an excellent choice when speed is the primary concern, but it's not a universal replacement for every AI provider:
Groq is a strong fit when:
- You need fast conversational responses with low latency
- You're building a prototype or early-stage product and want to minimize cost
- Open-source models (Llama, Mixtral, Gemma) are acceptable for your use case
Consider alternatives when:
- Your app relies heavily on function calling or tool use — Claude and OpenAI have more mature implementations
- You need native multimodal support (image or audio input)
- You require proprietary model capabilities specific to GPT-4o or Claude
A practical architecture I've found useful: use Groq as the primary inference provider for speed, and fall back to OpenAI or Claude API when Groq's rate limit is hit. This gives you the best of both worlds without hard-coupling your app to a single provider.
Start With One Screen
The fastest way to validate Groq in your Rork app is to build one chat screen today. Setup takes under 10 minutes, and the speed difference is immediately obvious. Once you've felt the difference in a real device test, you'll have a clear sense of where fast inference can improve your app's experience.
From there, the natural next step is moving API calls to a backend and adding per-user rate limiting — which opens the door to building a sustainable, scalable AI feature rather than a demo.