Lots has happened, much has been learned. I haven’t made much progress in the absolute sense. But I’ve configured and optimized crucial parts of my database structure and authentication mechanics in my application.
Pocketbase
itself has a lot of possibilities for backrelations, filters, hooks, extensions of it’s functionality, etc.Sveltekit
and Pocketbase have two things in common. They are versatile, and they are both backends. So it is crucial to decide what to programm Sveltekit as. ASPA
(Single Page Application), aSSR
(with all requests going through Sveltekit) or a combination ofSSR
(server-side-rendering) andCSR
(client-side-rendering)- The main Goal of my application is an efficient way of providing an interface for Players, Teams and Events. How this
data is loaded
, is crucial to it’s success.
These three items will be my goal in the next 3 Blog items.
This one is about the first Subject: Pocketbase it's Structure, relations, hooks and extensions
Pocketbase #
You must look up this project one day. Pocketbase is a solid, fast, Go and Sqlite based backend. It handles my Database, Authentication, File Storage and is Extendable. Not just simple Authentication, but has built in support for many OIDC, OAuth implementations like google, facebook, github, discord..everything you need! Even as an Amateur…or maybe especially as an Amateur…it’s a great opportunity to learn a Backend that I can use for any quick project in the future.
Schema Changes #
Im still not sure I’ve made the right decision here. You’re welcome to comment!
My original structure was minimal, and clear. The teams held the players, the clubs the teams. But a Player, would not have a team field. See the below Diagram. Originally from the previous blog item. I was still unexperienced in Pocketbase, and couldn’t get the proper requests to work to get data efficiently.
So if i’d want a player’s team, i would not be able to do so directly through the player.
await pb.collection("players").getOne(player_id, {
filter: "teams_via_players.players ?~ " + player_id,
expand: "teams_via_players.players",
})
Or see the more expansive UML Schema of my database.

First Review of Database Schema
Basic Calendar overview #
The calendar will be the backbone and most important. It should hold:
- Quick Overview
- Sign up of Players
- Visual pleasing Accordion with more info
- Filters to filter on: Team/Player, EventType
- Search
- Load More button / Progressive loading

Spatz-2 #
Spatz-2 uses Shadcn-svelte components, zod and superforms (but not everything from superforms) and gsap for animations.
Components #
A component like a back button is defined like this. I give it props like onclick, size, variant, extra classes and an Icon. The Icon is also a component which makes it easier to add SVG icons, without having my code become unreadable and get cluttered with SVG code.
<Button
onclick={() => window.history.back()}
size="sm"
variant="outline"
class="group/backButton backButton flex items-center gap-2">
<Icon
icon="mdi:arrow-left"
class="h-5 w-5 transition-all duration-300 md:group-hover/backButton:-translate-x-1"
/>
<span class="text-sm">back</span>
</Button>
Gsap #
Gsap is a complex, comprehensive library to create smooth and modern looking animations. Makes everything look all the more professional, without having to understand all the ins and outs of CSS and get lost in Frontend Development.
Gsap in animations.ts file.
import { gsap } from 'gsap';
export const animateMainStagger = () => {
gsap.from('.animate-item', {
opacity: 0,
y: 20,
duration: 1,
delay: 0.1,
stagger: 0.1,
ease: 'power4.out'
});
};
the onMount executes the exported animateMainStagger function. Gsap takes over.
onMount(() => {
animateMainStagger();
});
Typescript #
As an example for my problems with Typescript, I provide an example here.
import type {
EventsRecord,
UsersRecord,
TeamsRecord,
ClubsRecord
} from '$lib/types/pocketbase-types';
type ExpandedEvent = EventsRecord & {
expand: {
players: Array<UsersRecord>;
teams: Array<TeamsRecord>;
clubs: Array<ClubsRecord>;
};
};
interface Props {
data?: {
events?: Array<ExpandedEvent>;
};
}
let { data }: Props = $props();
// Ensure events is always an array
let events: ExpandedEvent[] = $derived(data?.events ?? []);
function getEventsByMonth(events: Array<ExpandedEvent>): Record<string, Array<ExpandedEvent>> {
const grouped: Record<string, Array<ExpandedEvent>> = {};
const sortedEvents = [...events].sort(
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()
);
sortedEvents.forEach((event) => {
const date = new Date(event.date);
const monthYear = date.toLocaleString('default', { month: 'long', year: 'numeric' });
if (!grouped[monthYear]) {
grouped[monthYear] = [];
}
grouped[monthYear].push(event);
});
return grouped;
}
This Code imports the Pocketbase Types from my pocketbase-types.ts file. Basically a Schema of an Event it’s properties (aka variables). For example for Teams:
export type TeamsRecord = {
category?: string
created?: IsoDateString
id: string
players?: RecordIdString[]
team?: string
trainers?: RecordIdString[]
updated?: IsoDateString
}
The same code in Javascript would be:
let { data } = $props();
let events = $derived(data.events ?? []);
function getEventsByMonth(events) {
const grouped = {};
const sortedEvents = [...events].sort(
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()
);
sortedEvents.forEach((event) => {
const date = new Date(event.date);
const monthYear = date.toLocaleString('default', { month: 'long', year: 'numeric' });
if (!grouped[monthYear]) {
grouped[monthYear] = [];
}
grouped[monthYear].push(event);
});
return grouped;
}
So there is a clear difference. For getting more IDE support and better maintainability, we have to sacrifice concise Syntax. The code with all it’s :: <> and {} becomes quite confusing at times.
Goals #
There is a lot to do. I want to especially focus on a deep understanding of the workings of Sveltekit, Javascript and Typescript. Especially the Types sometimes confuse me. The syntax makes the code harder to read, and my codebase is not yet that complicated, that it has really benefited me. Rather only confused me more. My general goals are:
- Improve my Typescript understanding
- Sveltekit understanding
- Write more code myself instead of Claude’s LLM
My next steps are:
- Implement a Current Player (in case of Users that have multiple Children or Trainers that should be able to change multiple players)
- Create the filters of the Calendar
- Signup button (is still only visually)
- More comprehensive Pocketbase API Authorization of individual Tables (Still open to anyone)