Here's the exact stack, with code you can copy.
Why this combo wins
Need SolutionCost
Database + Auth Supabase Free tier covers a lot
AI inference Gemini 2.5 Flash ~$0.075/M input tokens
Server compute Edge Functions Free 500k invocations/mo
Rate limiting Postgres table Free
Sharing images Edge Function + SVG Free
No Vercel pro. No OpenAI bill. No Redis. Just Supabase and a free Google AI Studio key.
The pattern (one Edge Function, all your AI)
// supabase/functions/ai-task/index.ts
import "https://deno.land/x/xhr@0.1.0/mod.ts";
import { createClient } from 'jsr:@supabase/supabase-js@2';
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, content-type',
};
Deno.serve(async (req) => {
if (req.method === 'OPTIONS') return new Response(null, { headers: corsHeaders });
const { prompt, taskType } = await req.json();
// 1. Rate limit (atomic via Postgres)
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
);
const userId = req.headers.get('x-user-id');
const { data: rl } = await supabase.rpc('check_rate_limit', {
p_user_id: userId,
p_function: taskType,
p_max: 10,
});
if (!rl.allowed) {
return new Response(JSON.stringify({ error: 'Rate limited' }), {
status: 429, headers: corsHeaders
});
}
// 2. Call Gemini
const aiResponse = await fetch(
https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${Deno.env.get('GEMINI_API_KEY')},
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
contents: [{ parts: [{ text: prompt }] }],
generationConfig: { temperature: 0.7, maxOutputTokens: 2048 },
}),
}
);
const data = await aiResponse.json();
const text = data.candidates?.[0]?.content?.parts?.[0]?.text ?? '';
return new Response(JSON.stringify({ text }), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
});
});
The atomic rate limiter (Postgres FTW)
create table rate_limits (
id uuid primary key default gen_random_uuid(),
user_id uuid not null,
function_name text not null,
request_count integer default 1,
window_start timestamptz default now()
);
create or replace function check_rate_limit(
p_user_id uuid, p_function text, p_max int
) returns json language plpgsql security definer as $$
declare
v_count int;
begin
-- Clean old windows (>1h)
delete from rate_limits
where user_id = p_user_id and function_name = p_function
and window_start < now() - interval '1 hour';
-- Count current
select coalesce(sum(request_count), 0) into v_count
from rate_limits
where user_id = p_user_id and function_name = p_function;
if v_count >= p_max then
return json_build_object('allowed', false, 'used', v_count);
end if;
insert into rate_limits (user_id, function_name) values (p_user_id, p_function);
return json_build_object('allowed', true, 'used', v_count + 1);
end;
$$;
Why it's atomic: the delete + insert happen in a single transaction. No race conditions, no Redis needed.
The cost breakdown (real numbers)
For ~50,000 AI requests/month:
Gemini 2.5 Flash: ~$3.50 (mostly input tokens)
Supabase Free tier: $0 (covered by free quota)
Edge Function invocations: $0 (under 500k limit)
Total: ~$3.50/month for production AI.
If I needed image gen, I'd add Lovable AI's free gemini-2.5-flash-image-preview and pay even less.
Gotchas I hit
No external Deno deps in Edge Functions. Use crypto.subtle natively. External imports cause bundling failures.
Set verify_jwt = false for public-facing functions in supabase/config.toml, or your unauth users get 401s.
Use service role key on the server, never on the client. Always.
Try the stack live
All 17 tools running this exact stack: codemasterip.com
Drop a ⭐ if this helped, and let me know what you'd build with it.
Top comments (0)