OTOMY
DIGITAL PRODUCTSJune 6, 20266 min

Build a REST API in 48h with Supabase Edge Functions

Think building a solid REST API takes weeks? With Supabase Edge Functions, 48 hours is all you need to go from idea to deployment. Here's the complete, step-by-step playbook.

M

By

Melissa Slimani

Build a REST API in 48h with Supabase Edge Functions

Why Supabase Edge Functions Are a Game-Changer for REST APIs

At Otomy, we help SMBs across France and Algeria build high-performance digital products. One pattern we see repeatedly: founders spend weeks setting up backend infrastructure when they could already be validating their product in the market.

Supabase Edge Functions, powered by Deno Deploy, let you write serverless TypeScript functions that deploy instantly to a global CDN. Combined with Supabase's PostgreSQL database, built-in authentication, and storage, they form a complete backend in hours, not weeks.

Here's how we build a full REST API in 48 hours — not a fragile prototype, but a production-ready API.


Hours 0-4: Architecture and Data Modeling

Before writing a single line of code, lay the foundations.

Define Your API Resources

Let's take a concrete example: an order management API for an e-commerce platform.

Identified resources:

  • /orders — full CRUD
  • /products — read-only catalog
  • /customers — customer profile management
  • /webhooks — notifications to n8n or Make

Model the Database

In Supabase's SQL Editor:

CREATE TABLE products (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  name TEXT NOT NULL,
  price NUMERIC(10,2) NOT NULL,
  stock INTEGER DEFAULT 0,
  created_at TIMESTAMPTZ DEFAULT now()
);

CREATE TABLE orders (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  customer_id UUID REFERENCES auth.users(id),
  status TEXT DEFAULT 'pending' CHECK (status IN ('pending','confirmed','shipped','delivered')),
  total NUMERIC(10,2) NOT NULL,
  created_at TIMESTAMPTZ DEFAULT now()
);

CREATE TABLE order_items (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  order_id UUID REFERENCES orders(id) ON DELETE CASCADE,
  product_id UUID REFERENCES products(id),
  quantity INTEGER NOT NULL,
  unit_price NUMERIC(10,2) NOT NULL
);

Otomy tip: Enable Row Level Security (RLS) policies immediately. This is non-negotiable in production.


Hours 4-16: Developing Edge Functions

Initialize the Project

supabase init
supabase functions new orders-api

RESTful Edge Function Pattern

Here's the pattern we use consistently to handle multiple HTTP methods in a single function:

// supabase/functions/orders-api/index.ts
import { serve } from "https://deno.land/std@0.177.0/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";

const corsHeaders = {
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
};

serve(async (req) => {
  if (req.method === "OPTIONS") {
    return new Response("ok", { headers: corsHeaders });
  }

  const supabase = createClient(
    Deno.env.get("SUPABASE_URL")!,
    Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!
  );

  const url = new URL(req.url);
  const id = url.searchParams.get("id");

  try {
    switch (req.method) {
      case "GET": {
        const query = id
          ? supabase.from("orders").select("*, order_items(*)").eq("id", id).single()
          : supabase.from("orders").select("*, order_items(*)").order("created_at", { ascending: false });
        const { data, error } = await query;
        if (error) throw error;
        return new Response(JSON.stringify(data), {
          headers: { ...corsHeaders, "Content-Type": "application/json" },
        });
      }
      case "POST": {
        const body = await req.json();
        const { data, error } = await supabase.from("orders").insert(body).select().single();
        if (error) throw error;
        return new Response(JSON.stringify(data), {
          status: 201,
          headers: { ...corsHeaders, "Content-Type": "application/json" },
        });
      }
      case "PATCH": {
        if (!id) throw new Error("ID required for update");
        const body = await req.json();
        const { data, error } = await supabase.from("orders").update(body).eq("id", id).select().single();
        if (error) throw error;
        return new Response(JSON.stringify(data), {
          headers: { ...corsHeaders, "Content-Type": "application/json" },
        });
      }
      default:
        return new Response("Method not allowed", { status: 405, headers: corsHeaders });
    }
  } catch (err) {
    return new Response(JSON.stringify({ error: err.message }), {
      status: 400,
      headers: { ...corsHeaders, "Content-Type": "application/json" },
    });
  }
});

Input Validation

Use Zod to validate all incoming data:

import { z } from "https://deno.land/x/zod@v3.22.4/mod.ts";

const OrderSchema = z.object({
  customer_id: z.string().uuid(),
  total: z.number().positive(),
  items: z.array(z.object({
    product_id: z.string().uuid(),
    quantity: z.number().int().positive(),
    unit_price: z.number().positive(),
  })).min(1),
});

Hours 16-30: Authentication, Security, and Integrations

Secure the API with JWT

Supabase handles authentication natively. Extract and verify the JWT token from each request:

const authHeader = req.headers.get("Authorization");
const { data: { user }, error } = await supabase.auth.getUser(
  authHeader?.replace("Bearer ", "")
);
if (!user) return new Response("Unauthorized", { status: 401, headers: corsHeaders });

Connect n8n or Make for Automation

After order creation, trigger a workflow via webhook:

await fetch(Deno.env.get("N8N_WEBHOOK_URL")!, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ event: "order.created", data: newOrder }),
});

This webhook can trigger in n8n: confirmation emails, Slack notifications, stock updates, or ERP synchronization.

Basic Rate Limiting

Store request counters in Supabase using a dedicated table or leverage Deno's built-in capabilities to prevent abuse.


Hours 30-42: Testing and Documentation

Automated Testing

deno test --allow-net --allow-env supabase/functions/tests/

Write tests for every endpoint. Use Claude AI to rapidly generate edge cases from your Zod schemas.

OpenAPI Documentation

Generate an OpenAPI 3.0 spec and host it on your Vercel or Netlify frontend. Your API consumers will thank you.


Hours 42-48: Deployment and Monitoring

Deploy to Production

supabase functions deploy orders-api --project-ref your-project-ref

That's it. Your REST API is live on the edge, with sub-50ms latency in most regions.

Monitoring

  • Supabase Dashboard: real-time Edge Function logs
  • Logflare: natively integrated for log analysis
  • Uptime Robot or Better Stack: uptime monitoring

Pre-Production Checklist

  • ✅ RLS enabled on all tables
  • ✅ Zod validation on all inputs
  • ✅ JWT authentication verified
  • ✅ CORS configured (replace * with your domains)
  • ✅ Environment variables stored as Supabase secrets
  • ✅ Automated tests passing
  • ✅ n8n/Make webhooks operational
  • ✅ OpenAPI documentation published

What You Get in 48 Hours

Component Tool
Database Supabase PostgreSQL
Serverless API Supabase Edge Functions (Deno)
Authentication Supabase Auth (JWT)
Automation n8n / Make via webhooks
Frontend hosting Vercel / Netlify
Monitoring Logflare + Better Stack

The result: a complete, secure, scalable REST API deployed globally — without managing a single server.

At Otomy, this is exactly the approach we use for SMBs looking to launch digital products fast, whether in France or Algeria. Speed of execution isn't the enemy of quality — as long as you pick the right tools.

OTOMY

Ready to automate your business?

Book a free call — 30 minutes to identify what we can automate for you.