Build a Fullstack CRUD App with SvelteKit and Supabase: A Beginner's Guide
Introduction
This guide will walk you through building a simple to-do list application using SvelteKit for the frontend and backend API routes, and Supabase for the database and authentication. We'll cover the essential CRUD operations: creating, reading, updating, and deleting to-do items.
Prerequisites
- Node.js (latest LTS version)
- npm or pnpm
- Supabase account (free tier is sufficient)
Setup
-
Create a SvelteKit project:
npm create svelte@latest my-todo-app cd my-todo-app npm installChoose the skeleton project. We will be adding Typescript for type safety.
-
Initialize Supabase:
Create a new project on Supabase. Once created, grab your Supabase URL and anon key from the API settings.
-
Install Supabase JavaScript client:
npm install @supabase/supabase-js npm install -D @sveltejs/adapter-autoAlso, install Typescript packages that will aid with development
npm install -D typescript svelte-check svelte-preprocess
Creating the Supabase Client
Create a file src/lib/supabaseClient.js:
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
Important: Store your Supabase URL and anon key in .env files and reference them using import.meta.env. Never commit these keys directly to your repository!
Create .env and .env.development files:
VITE_SUPABASE_URL="your_supabase_url"
VITE_SUPABASE_ANON_KEY="your_supabase_anon_key"
You might need to update svelte.config.js if you face issues with environmental variables. The following is a sample configuration
import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/kit/vite';
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: vitePreprocess(),
kit: {
adapter: adapter(),
vite: {
define: {
'process.env': process.env
}
}
}
};
export default config;
Building the UI (src/routes/+page.svelte)
<script>
import { supabase } from '$lib/supabaseClient';
import { onMount } from 'svelte';
let todos = [];
let newTodo = '';
onMount(async () => {
await fetchTodos();
});
async function fetchTodos() {
const { data, error } = await supabase
.from('todos')
.select('*')
.order('created_at', { ascending: false });
if (error) {
console.error('Error fetching todos:', error);
return;
}
todos = data;
}
async function addTodo() {
if (!newTodo.trim()) return;
const { data, error } = await supabase
.from('todos')
.insert([{ task: newTodo }])
.single();
if (error) {
console.error('Error adding todo:', error);
return;
}
todos = [data, ...todos];
newTodo = '';
}
async function deleteTodo(id) {
const { error } = await supabase
.from('todos')
.delete()
.eq('id', id);
if (error) {
console.error('Error deleting todo:', error);
return;
}
todos = todos.filter(todo => todo.id !== id);
}
async function toggleComplete(todo) {
const { error } = await supabase
.from('todos')
.update({ completed: !todo.completed })
.eq('id', todo.id)
.single();
if (error) {
console.error('Error updating todo:', error);
return;
}
todo.completed = !todo.completed;
todos = [...todos]; // Trigger reactivity
}
</script>
<input type="text" bind:value={newTodo} placeholder="Add new todo" />
<button on:click={addTodo}>Add</button>
<ul>
{#each todos as todo (todo.id)}
<li>
<input type="checkbox" checked={todo.completed} on:change={() => toggleComplete(todo)} />
<span class:completed={todo.completed}>{todo.task}</span>
<button on:click={() => deleteTodo(todo.id)}>Delete</button>
</li>
{/each}
</ul>
<style>
.completed {
text-decoration: line-through;
color: gray;
}
ul {
list-style: none;
padding: 0;
}
li {
display: flex;
align-items: center;
margin-bottom: 5px;
}
li > * {
margin-right: 10px;
}
</style>
Creating the 'todos' Table in Supabase
In your Supabase dashboard, go to the SQL Editor and run the following SQL:
CREATE TABLE todos (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()),
task TEXT NOT NULL,
completed BOOLEAN NOT NULL DEFAULT FALSE
);
Enable Row Level Security (RLS) and create policies based on user ID. For this tutorial, we assume no auth and disable RLS. WARNING: this is highly insecure for production usage
Explanation
src/lib/supabaseClient.js: Initializes the Supabase client.src/routes/+page.svelte:- Fetches todos on mount.
addTodocreates a new todo in Supabase.deleteTododeletes a todo from Supabase.toggleCompleteupdates the completed status in Supabase.- The UI uses Svelte's reactivity to display the todos.
Running the Application
npm run dev -- --open
Your application will be running at http://localhost:5173/.
Next Steps
- Implement user authentication with Supabase Auth.
- Add more advanced features like editing todos, setting deadlines, etc.
- Improve the UI with CSS frameworks like Tailwind CSS.