SolidJS Guide
This guide walks you through building a reactive app with SolidJS and Spooky.
1. Installation
Install the required dependencies:
Bash npm install @spooky-sync/client-solid surrealdb
2. Generate Schema Types
First, create your schema file and generate TypeScript types:
Bash # Generate TypeScript types from your schema
spooky --input src/schema.surql --output src/schema.gen.ts --format typescript
This generates a schema.gen.ts file with your schema definition and types.
3. Initialize Database
Create a db.ts file to configure and export your database instance:
TypeScript import { SyncedDb, type SyncedDbConfig } from '@spooky-sync/client-solid';
import { schema, SURQL_SCHEMA } from './schema.gen';
export const dbConfig: SyncedDbConfig<typeof schema> = {
logLevel: 'info',
schema: schema,
schemaSurql: SURQL_SCHEMA,
database: {
namespace: 'main',
database: 'main',
endpoint: 'ws://localhost:8000/rpc',
store: 'indexeddb',
},
};
export const db = new SyncedDb<typeof schema>(dbConfig);
// Initialize the database
let initPromise: Promise<void> | null = null;
export function initDatabase(): Promise<void> {
if (initPromise) return initPromise;
initPromise = (async () => {
try {
await db.init();
console.log('Database initialized');
} catch (error) {
initPromise = null;
throw error;
}
})();
return initPromise;
}
4. App Entry Point
Initialize the database in your root component before rendering the app:
tsx import { createSignal, onMount, Show } from 'solid-js';
import { initDatabase } from './db';
export default function App() {
const [isReady, setIsReady] = createSignal(false);
onMount(async () => {
try {
await initDatabase();
setIsReady(true);
} catch (error) {
console.error('Failed to initialize database:', error);
}
});
return (
<Show
when={isReady()}
fallback={<div>Initializing database...</div>}
>
<MainContent />
</Show>
);
}
5. Querying Data with useQuery
Use the useQuery hook to create reactive queries that automatically update:
src/components/ThreadList.tsx tsx import { For } from 'solid-js';
import { useQuery } from '@spooky-sync/client-solid';
import { db } from '../db';
export function ThreadList() {
// Build the query
const threadsQuery = useQuery(db, () => {
return db.query('thread')
.related('author')
.orderBy('created_at', 'desc')
.limit(20)
.build();
});
return (
<div>
<Show when={threadsQuery.isLoading()}>
<div>Loading...</div>
</Show>
<Show when={threadsQuery.error()}>
<div>Error: {threadsQuery.error()?.message}</div>
</Show>
<ul>
<For each={threadsQuery.data() || []}>
{(thread) => (
<li>
<h3>{thread.title}</h3>
<p>{thread.content}</p>
<small>By {thread.author?.username}</small>
</li>
)}
</For>
</ul>
</div>
);
}
6. Authentication with Auth Context
Create an auth context to manage user authentication state:
tsx import { createContext, useContext, createSignal, onCleanup, type JSX } from 'solid-js';
import { useQuery } from '@spooky-sync/client-solid';
import { db } from '../db';
interface AuthContextType {
userId: () => string | null;
isLoading: () => boolean;
signIn: (username: string, password: string) => Promise<void>;
signUp: (username: string, password: string) => Promise<void>;
signOut: () => Promise<void>;
}
const AuthContext = createContext<AuthContextType>();
export function AuthProvider(props: { children: JSX.Element }) {
const [userId, setUserId] = createSignal<string | null>(null);
const [isInitializing, setIsInitializing] = createSignal(true);
// Subscribe to auth state changes
const unsubscribe = db.auth.subscribe((uid) => {
setUserId(uid);
setIsInitializing(false);
});
onCleanup(() => unsubscribe());
const signIn = async (username: string, password: string) => {
await db.auth.signIn('account', { username, password });
};
const signUp = async (username: string, password: string) => {
await db.auth.signUp('account', { username, password });
};
const signOut = async () => {
await db.auth.signOut();
};
const value: AuthContextType = {
userId,
isLoading: () => isInitializing(),
signIn,
signUp,
signOut,
};
return (
<AuthContext.Provider value={value}>
{props.children}
</AuthContext.Provider>
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within AuthProvider');
}
return context;
}
7. Creating and Updating Data
Use the database methods to create and update records:
TypeScript import { Uuid } from 'surrealdb';
import { db } from './db';
// Create a new thread
async function createThread(title: string, content: string, authorId: string) {
const id = `thread:${Uuid.v4().toString().replace(/-/g, '')}`;
await db.create(id, {
title,
content,
author: authorId,
active: true,
});
}
// Update a thread
async function updateThread(threadId: string, updates: { title?: string; content?: string }) {
await db.update('thread', threadId, updates);
}
// Delete a thread
async function deleteThread(threadId: string) {
await db.delete('thread', threadId);
}