Alright, so I have a confession. I'm pretty much addicted to building apps. It's just my thing. The idea for LoreStrom just came to me one day – this vision of a space for 100% human-crafted worlds and stories, made easily shareable by the creator.
This was right of the bat of building an AI-assisted writing platform (more on that later).
I envisioned a place where you and I, as creators, could really dive deep into building fictional universes – all those intricate histories, the quirky characters with their own sagas, the sprawling lore – and then offer readers a genuinely cool way to get lost in it all. Something that gives writers total creative freedom and readers a uniquely engaging portal.
Now, you might be thinking, "Why LoreStrom?" You're right to ask this question! It's true that there are many apps out there to facilitate this kind of creativity. But I'm a creative, so I'm building the tool that I want. LoreStrom is the platform I wanted to see (or will be).
Being a teacher, and also having built The Writer's Den (which, for context, is an AI-assisted tool for novelists), I've spent a lot of time pondering the tools we use to bring ideas to life. For LoreStrom, I felt a strong pull towards something different – a space that champions and amplifies pure human imagination, offering the canvas and the toolkit without an AI co-author. A place where I, and others could pour their heart and souls into.
So, this article? I just wanted to share my journey, expose LoreStrom shamelessly, and if you pick up a useful tip along the way, I'll be estactic.
Ready?
Let's go.
Picking the Right Gear: My Tech Stack – Why These Tools?
Every good project needs a solid foundation. Here’s what I chose for LoreStrom and why it felt right:
-
Frontend - Vue 3 & the Quasar Power-Up:
- For the frontend, I went with Vue 3, mostly because the Composition API is just fantastic for keeping complex components organized and readable.
- Then comes Quasar Framework. Seriously, if you're doing Vue and not using Quasar, you are working too hard! It's got everything you need including an awesome component library, and CLI. I ean seriously, Quasar deserves more attention. What truly matters is it let me build the UI fast and focus on the actual LoreStrom features.
- Pinia for state management and Vue Router for navigation are pretty standard in my Vue toolkit – they just work.
-
Backend - Supabase for the Win!
- If you haven't played with Supabase yet, let me give you the rundown: it's an open-source Firebase alternative built on PostgreSQL.
- You get Authentication out of the box, instant APIs (REST and GraphQL), Storage for files, Edge Functions, Realtime stuff... the works. For LoreStrom, this meant I could get a powerful, scalable backend up and running fast without wrestling with server configurations. The fact that it’s SQL-based is also a huge win for the kind of structured, relational data a world-building app needs.
Building the Bones: Core Features Taking Shape
With the tools in hand, I started laying down the core structure of LoreStrom:
- The Universe as a Canvas: It all starts with the "Universe." This is where creators get that complete freedom I was talking about. Define your cosmology, history, unique rules – go wild.
- Populating the Worlds: Inside each Universe, you can then build out your Characters (with all their quirks, backstories, and even visual portraits down the line), weave detailed Lore entries, and, of course, write Stories broken down into chapters.
- Two Sides to Every Portal: Creator & Reader Modes: It was important to have distinct experiences. Using Quasar's layouts and Vue's conditional rendering, it wasn't too tough to switch the UI from a feature-rich dashboard for creators to a clean, immersive interface for readers.
The Real Work: Battles Fought & Lessons Learned (My Supabase Saga)
This is where the real fun (and sometimes hair-pulling) begins. Building is one thing; making it work reliably and securely is another.
1. The 'New User' Problem: "Welcome! Uh... Where's Your Profile?"
-
The Situation: So, a user signs up with Supabase Auth. Cool, they're in
auth.users
. But LoreStrom needs its own profile table,public.users
, to store app-specific info like their chosenusername
,bio
,creator_enabled
flag, etc. Thispublic.users
record wasn't just magically appearing. -
My First Thoughts (and why they weren't the best):
-
Client-Side Creation: "Easy," I thought, "after
supabase.auth.signUp()
works, I'll just make another API call from the frontend to create their profile."- The Catch? What if their internet glitches? Or they close the tab too soon? We could end up with an authenticated user who has no actual app profile. Not good. Plus, handling RLS for that very first insert from a "new" user is a bit of a pain.
-
Client-Side Creation: "Easy," I thought, "after
-
Best Practice & My "Aha!" Moment: Supabase Database Triggers
-
This was the answer. Instead of relying on the client, I told the database to handle it. I wrote a PostgreSQL function that automatically creates an entry in
public.users
whenever a new user is added toauth.users
.
-- This function runs automatically thanks to the trigger below create or replace function public.handle_new_user() returns trigger language plpgsql security definer set search_path = public as $$ begin insert into public.users (user_id, email, username) -- user_id is the foreign key to auth.users.id values (new.id, new.email, new.raw_user_meta_data->>'username'); -- 'username' is passed from client during signUp return new; end; $$; -- This trigger calls the function whenever a new user is created in auth.users create trigger on_auth_user_created after insert on auth.users for each row execute procedure public.handle_new_user();
Why this is the way (for me, at least): It's atomic – happens server-side right when the auth user is created. It's reliable. And the
username
? I just pass that from my registration form in theoptions.data
field of thesupabase.auth.signUp()
call, and the trigger picks it up. Clean.
-
2. RLS Mysteries: The Case of the 406 Not Acceptable
Error
-
The Frustration: Users were logged in, but when the app tried to fetch their profile from
public.users
...406 Not Acceptable
. My query looked right, the JWT was being sent. What was Supabase trying to tell me? - The Culprit: Row Level Security (RLS). If you're working with Supabase and user data, RLS is your best friend for security, but you have to set it up. It lets you define exactly who can see or change which rows in a table. By default, if RLS is on, nobody can do anything until you write a policy.
-
Troubleshooting & The Fix:
-
Dev Tip: First thing I did was temporarily toggle RLS off for the
public.users
table in the Supabase dashboard. The query worked! That told me 100% it was an RLS policy issue. (Pro tip: always remember to turn RLS back on!).
-
Dev Tip: First thing I did was temporarily toggle RLS off for the
-
Best Practice & Our Solution: Granular Policies
-
We needed users to read their own profile. So, the
SELECT
policy became:
CREATE POLICY "Allow individual read access to own profile" ON public.users FOR SELECT TO authenticated -- This policy applies to any logged-in user USING ( auth.uid() = user_id ); -- They can only select rows where their auth ID matches the table's user_id column
Why this is key: It enforces the principle of least privilege. Users get access only to what they need. We set up a similar one for
UPDATE
.
-
3. State of Confusion: When Your App & Guard Disagree
-
The Head-Scratcher: My UI would show a "Login" button (because
userStore.profile
wasnull
– often due to the RLS issue above), but clicking it did nothing! Or, if I typed/auth/login
manually, I'd just get bounced back to the homepage. -
The Diagnosis: My
userStore
did have anauthUser
(Supabase knew I had a session), souserStore.isLoggedIn
was true. But becauseuserStore.profile
wasn't loading, my UI (checkinguserStore.profile
) thought I wasn't logged in and showed the "Login" button. My Vue Router navigation guard, however, correctly sawisLoggedIn
as true and blocked access to the login page, sending me home. A classic state mismatch! -
The Workaround vs. The Real Fix:
- My quick fix was
await userStore.logout()
beforerouter.push('/auth/login')
in the login button's click handler. It forced the state to "logged out" so the guard would let me through. -
The Proper Solution: The real fix was ensuring that my
userStore.initialize()
action not only setauthUser
but also reliably awaited thefetchProfile
call. Once the RLS issue was sorted andfetchProfile
worked,userStore.profile
got populated, and the UI, the store, and the navigation guard were all singing from the same hymn sheet. Consistent state is everything!
- My quick fix was
LoreStrom's Next Chapter: What I'm Building Now & Dreaming Up
The core is taking shape, but there's always more to build!
- Visual Richness with Supabase Storage: Next up is integrating Supabase Storage. Creators need to be able to upload those cool universe banners, character portraits, and other visuals to really make their worlds shine.
- Audio Scenes – Hear the Story: I'm super excited about this idea. Imagine reading a story and being able to trigger short audio clips – a character's key line of dialogue, ambient sounds of a bustling fantasy market, a dramatic musical sting. It’s about adding another layer for writers to tell their story and for readers to experience it.
- Dynamic Visual Storytelling: I've been really inspired by interactive experiences like Google Spotlight Stories. The aim isn't to turn LoreStrom into a game engine, but to explore how we can present text and art more dynamically, making the reading experience itself more of an event.
- A Marketplace for Creators (The Big Dream): Looking further out, I'd love to build a marketplace where creators could optionally offer paid access to their premium universes, exclusive story arcs, or perhaps early access to content. This could provide a way for them to earn from their incredible creativity.
-
My Stance on AI (for LoreStrom): As I mentioned, I built
thewritersden.app
, which is all about AI-assisted novel writing. I'm fascinated by AI. But for LoreStrom, the philosophy is different. This platform is deliberately a space for 100% human-crafted worlds and stories. It's about providing the best possible canvas and tools for an author's unique vision, not an AI co-writer.
Wrapping Up: The Adventure Continues
Building LoreStrom has been (and continues to be) an awesome ride – a fantastic mix of creative design and tough coding challenges. It really drives home that old saying: the best way to learn is by building, especially when it’s something you’re truly passionate about creating because you wish it existed.
I hope sharing some of these "behind-the-scenes" moments from my dev journey has been interesting or maybe even helpful for your own projects. If you've tackled similar stuff or just want to chat about world-building and code, I'm all ears!
If you're interested in checking out LoreStrom, you can do so Here
Until next time, keep coding away!
Top comments (0)