How to Use Supabase with Astro: Complete Guide
Step-by-step guide to integrating Supabase with your Astro website. Installation, configuration, and best practices.
Supabase is the open-source Firebase alternative built on PostgreSQL. It gives you a database, authentication, realtime subscriptions, and storage out of the box. When you pair it with Astro, you get a full-stack setup where static pages load instantly and server-side routes handle dynamic data. It is one of the most practical backends you can add to an Astro project.
Prerequisites
- Node.js 18+
- An Astro project (
npm create astro@latest) - A Supabase account and project (free tier at supabase.com)
Installation
Install the Supabase JavaScript client:
npm install @supabase/supabase-js
Configuration
Grab your project URL and anon key from the Supabase dashboard under Settings > API. Add them to your .env file:
SUPABASE_URL=https://your-project-id.supabase.co
SUPABASE_ANON_KEY=your_anon_key_here
Create a client helper:
// src/lib/supabase.ts
import { createClient } from "@supabase/supabase-js";
export const supabase = createClient(
import.meta.env.SUPABASE_URL,
import.meta.env.SUPABASE_ANON_KEY
);
For Astro to expose these variables, make sure your astro.config.mjs uses server or hybrid output mode if you need server-side routes:
// astro.config.mjs
import { defineConfig } from "astro/config";
export default defineConfig({
output: "hybrid",
});
Basic Usage
Let's fetch a list of blog posts from a Supabase table and render them on a static page:
---
// src/pages/blog/index.astro
import { supabase } from "../../lib/supabase";
import BaseLayout from "../../layouts/BaseLayout.astro";
const { data: posts, error } = await supabase
.from("posts")
.select("id, title, slug, excerpt, created_at")
.eq("published", true)
.order("created_at", { ascending: false });
if (error) {
console.error("Error fetching posts:", error.message);
}
---
<BaseLayout title="Blog">
<h1>Blog</h1>
<ul>
{posts?.map((post) => (
<li>
<a href={`/blog/${post.slug}`}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</a>
</li>
))}
</ul>
</BaseLayout>
Building an API Endpoint
Astro's server endpoints work perfectly with Supabase for handling form submissions, authentication, or any dynamic data:
// src/pages/api/subscribe.ts
import type { APIRoute } from "astro";
import { supabase } from "../../lib/supabase";
export const POST: APIRoute = async ({ request }) => {
const formData = await request.json();
const { email } = formData;
if (!email) {
return new Response(JSON.stringify({ error: "Email is required" }), {
status: 400,
});
}
const { error } = await supabase
.from("subscribers")
.insert({ email });
if (error) {
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
});
}
return new Response(JSON.stringify({ success: true }), { status: 200 });
};
Authentication with Supabase
Supabase Auth integrates smoothly with Astro's server-side rendering. Here is a basic login flow:
// src/pages/api/auth/login.ts
import type { APIRoute } from "astro";
import { supabase } from "../../../lib/supabase";
export const POST: APIRoute = async ({ request, cookies }) => {
const { email, password } = await request.json();
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error) {
return new Response(JSON.stringify({ error: error.message }), {
status: 401,
});
}
cookies.set("sb-token", data.session.access_token, {
httpOnly: true,
secure: true,
path: "/",
maxAge: 60 * 60 * 24 * 7,
});
return new Response(JSON.stringify({ success: true }), { status: 200 });
};
Production Tips
Use Row Level Security (RLS). Always enable RLS on your tables. The anon key is exposed to the client, so RLS policies are your real access control layer.
Use the service role key server-side only. For admin operations in API routes, create a separate Supabase client with the service role key. Never expose it to the browser.
Generate TypeScript types. Run supabase gen types typescript to generate types from your database schema. This gives you full autocomplete and catches errors at build time.
Enable realtime only where needed. Realtime subscriptions add overhead. Only enable them on tables that actually need live updates.
Use Supabase Edge Functions for webhooks. Instead of polling, set up database triggers that call Supabase Edge Functions to handle events like new signups or order completions.
Alternatives to Consider
- Firebase if you are already in the Google ecosystem and prefer a NoSQL document database with tight mobile SDK support.
- Neon if you want serverless Postgres without the extra features (auth, storage). Lighter weight, just the database.
- PlanetScale if you need a MySQL-compatible serverless database with branching for schema changes.
Wrapping Up
Supabase gives you almost everything you need for a backend in one package, and Astro's hybrid rendering model means you can use it for both build-time data fetching and runtime API routes. The free tier is generous enough to build a real product on before you ever need to pay.
Related Articles
How to Use Algolia with Astro: Complete Guide
Step-by-step guide to integrating Algolia with your Astro website.
How to Integrate Auth0 with Astro: Complete Guide
Step-by-step guide to integrating Auth0 with your Astro website. Setup, configuration, and best practices.
How to Use AWS Amplify with Astro: Complete Guide
Step-by-step guide to integrating AWS Amplify with your Astro website.