This repo and the resulting code is the product of a 45 minutes live-coding during my talk at CapraCon 2024. During the talk I created a Supabase project, wrote all the code in this repo with a little help of shadcn/ui Blocks and deployed the application. The final (unpolished) results can be viewed at https://capracon-nach.vercel.app/.
You can find the slide deck from the presentation here: CapraCon 2024 Presentation
- Supabase: Open Source Firebase alternative centered around PostgreSQL.
- Next.js: React Meta Framework
- shadcn/ui: Open Source component library where you copy and paste the components you need directly into your apps.
- Vercel: Deploy your frontend apps with a few keystrokes.
- TailwindCSS: CSS framework
npx create-next-app@latest -e with-supabase <your-project-name> && cd <your-project-name>
Go to database.new and follow the instruction to create your first project. The free tier is more than sufficient for an application like this.
Configure some needed boilerplate files to use the Supabase SDK.
npx supabase init
NEXT_PUBLIC_SUPABASE_URL=<SUBSTITUTE_SUPABASE_URL>
NEXT_PUBLIC_SUPABASE_ANON_KEY=<SUBSTITUTE_SUPABASE_ANON_KEY>
If you deploy your application make sure to provide these variables to your hosting service. With Vercel you can add this in the dashboard or by using vercel env add <NEXT_PUBLIC_xx>
.
Log in to have a valid login session from your terminal.
npx supabase login
npx supabase link --project-ref <PROJECT_REF>
This will create TypeScript types based on your database schema. Remember to run the command again in case you make any changes to your schema.
mkdir types/ && npx supabase gen types typescript --linked --schema public > types/database.types.ts
These type may look scary at first, but try importing Table
to a .ts/.tsx file and this snippet will give you an exact representation of your table in TypeScript. Depending on whether you wish to represent, insert og update something, you can use Tables
, TablesInsert
or TablesUpdate
.
import { Tables, TablesInsert, TablesUpdate } from '@/types/database.types'
type Event = Tables<'events'>
type EventInsert = TablesInsert<'events'> // Auto-generated values such as created_at are nullable
type EventUpdate = TablesUpdate<'events'>
Run these commands to initialize shadcn/ui in your app. You can select one or all of the components you wish to use by following the instructions. For this project I used button
, card
, input
, label
and sonner
.
npx shadcn-ui@latest init
npx shacdn-ui@latest add
Go to shadcn/ui and pick and choose whichever components you like. I also recommend checking out the Blocks.
Supabase offers plenty of cool features. One of those is Realtime, which enables you to listen to any changes in your database to your application via the Supabase SDK. You can read more about all Realtime Concepts here.
For this to work you first need to enable Realtime on each table you wish to use the Realtime API. Follow the steps in this guide to do so.
Practically I used this during my talk to instantly update the attendee list whenever a new attendee is registered at the event. The application will then instantly update the list. This requires no polling, and under the hood uses a WebSocket connection to enable two-way communication between the client and the server.
Realtime Channels Example: Event attendees
const EventAttendees = ({ id }: { id: number }) => {
const supabase = createClient();
const [attendees, setAttendees] = useState<Attendee[]>([]);
const fetchEventAttendees = async () => {
const { data, error } = await supabase
.from('attendees')
.select('*')
.eq('event_id', id);
if (error) {
toast.error(error.message);
} else {
setAttendees(data);
}
};
useEffect(() => {
fetchEventAttendees();
}, []);
useEffect(() => {
const channel = supabase
.channel('attendees')
.on('postgres_changes',
{ event: 'INSERT', schema: 'public', table: 'attendees' },
(payload) => {
setAttendees((attendees) => [...attendees, payload.new as Attendee]);
}
)
.subscribe();
return () => {
supabase.removeChannel(channel);
};
}, [supabase, attendees, setAttendees]);
return (
<div>
<h1 className="text-2xl">Geiteliste</h1>
<ul>
{attendees.map(attendee => (
<li key={attendee.id}>{attendee.name}</li>
))}
</ul>
</div>
);
}
You're all set to start building your next web application. The repo can be the basis for inspiration for how to build your own app, but be aware that this was coded live on stage in udner 45 minutes. The resulting code and organization of files is therefore not optimal.