Experiments >

Supacart: admin

Experiment #1118th April, 2021by Joshua Nussbaum

Today I started building the admin portion for the “supacart” project.

I decided to go with a monorepo approach with 2 svelte-kit installations, one for the public store and the other for the admin:

$> tree
- supacart
  - admin
  - store


To perform authentication, I used the supabase-ui-svelte project.

<!-- admin/src/routes/index.svelte -->
  import { Auth } from 'supabase-ui-svelte'
  import db from '$lib/db'

<Auth supabaseClient={db.supabase}/>

That worked really well.


Added policies to protect the products database table. Everything is private with the exception of selecting data:

create policy "Admins can create products." on products for
    insert with check (auth.role() = 'authenticated');

create policy "Anyone can view the products." on products for
    select using (true);

create policy "Admins can update products." on products for
    update using (auth.role() = 'authenticated');

create policy "Admins can delete products." on products for
    delete using (auth.role() = 'authenticated');


The admin is basically a bunch of CRUD screens.


The index screen shows a list of products and allows the user to remove products or click to view them.

<!-- admin/src/routes/products/index.svelte -->
<script context="module">
  import db from '$lib/db'

  export async function load() {
    const products = await db.products.all()

    return {
      props: { products }

  export let products

  async function del(product) {
    if (!confirm("are you sure?")) return

    // delete a product
    await db.products.del(product)

    // remove it from the list
    products = products.filter(p => p.id !== product.id)


<a href="/products/new">Add a product</a>


    {#if products}
    {#each products as product}
          <a href="/products/{product.permalink}">view</a>
          <button on:click={() => del(product)}>delete</button>

Shared form

Adding and updating share the same form logic, so I extracted a Form component:

<!-- admin/src/routes/products/_Form.svelte -->
  export let product
  export let action = 'Save'

<form on:submit|preventDefault>
  <input bind:value={product.name}/>
  <input bind:value={product.permalink}/>
  <input bind:value={product.sku}/>
  <textarea bind:value={product.details}/>
  <input bind:value={product.price}/>


The Create Page makes use of the Form component and redirects to the Edit Page after saving.

<!-- admin/src/routes/products/new.svelte -->
  import db from '$lib/db'
  import Form from './_Form.svelte'
  import { goto } from '$app/navigation'

  let product = {}

  async function submit() {
    // create the product
    await db.products.create(product)

    // redirect to edit page

<h1>New product</h1>

<Form bind:product on:submit={submit} action="Create"/>


Updating is very similar to the Create Page, except it loads the existing record first:

<!-- admin/src/routes/products/[permalink].svelte -->
<script context="module">
  import db from '$lib/db'

  export async function load({page}) {
    // load the product
    const product = await db.products.find(page.params.permalink)

    // return 404 if not found
    if (!product) {
      return {
        status: 404,
        error: new Error('product not found')

    // returns props if found
    return {
      props: { product }

  import { goto } from '$app/navigation'
  import Form from './_Form.svelte'

  export let product = {}

  async function submit() {
    // update the product
    await db.products.update(product)

    // redirect to products index page

<h1>Updating: {product.name}</h1>

<Form bind:product on:submit={submit} action="Save"/>
view all experiments

Stay tuned in

Learn how to add more experimentation to your workflow