Skip to content

Commit a9a4fcd

Browse files
committed
svelte and convex rules
1 parent 49658d8 commit a9a4fcd

File tree

1 file changed

+143
-2
lines changed

1 file changed

+143
-2
lines changed

instructions/svelte_and_convex.md

Lines changed: 143 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,146 @@
1-
The @convex_rules.md file doesn't contain examples or use cases for the client-side sdks.
1+
# SvelteKit and Convex Integration Pattern
2+
3+
This document outlines the specific architectural pattern used in this project to integrate the SvelteKit frontend with the Convex backend. The pattern ensures that data access is secure and efficient.
24

35
In this project we use Svelte and the convex-svelte package together.
46

5-
Here's the documentation/github repo for this package: https://github.com/get-convex/convex-svelte
7+
Here's the documentation/github repo for this package: https://github.com/get-convex/convex-svelte
8+
9+
## Overview
10+
11+
The core of the pattern is a **client-driven authorization model**. The SvelteKit client is responsible for obtaining the current user's identity (whether that be through a +page.server.ts/+layout.server.ts loader or a front-end auth client) and passing the `user_id` to Convex queries and mutations. The Convex backend then uses this `user_id` to both filter data and, crucially, to authorize access to specific documents.
12+
13+
This creates a clear separation of concerns:
14+
- **SvelteKit (Client):** Manages UI, user authentication state, and calls Convex functions with the necessary user context.
15+
- **Convex (Backend):** Defines data schema, business logic, and enforces strict data access rules based on the user identity provided by the client.
16+
17+
## Key Components of the Pattern
18+
19+
### 1. Client-Side User Identity
20+
21+
- The user's ID is obtained on the client through PageData or a client-auth library (in the examples below, it happens to be Clerk).
22+
- This `user_id` is then passed as a prop to any components that need to interact with user-specific data.
23+
24+
*Example (`TaskCard.svelte`):*
25+
```svelte
26+
<script lang="ts">
27+
import { useClerkContext } from 'svelte-clerk/client';
28+
// ...
29+
const ctx = useClerkContext();
30+
const user_id = $derived(ctx.auth.userId as string);
31+
</script>
32+
33+
<!-- user_id is passed down to child components -->
34+
<TaskInput {user_id} />
35+
<TaskList {user_id} />
36+
```
37+
38+
### 2. Fetching Data (Queries)
39+
40+
- The `convex-svelte` package's `useQuery` hook is used to subscribe to Convex queries.
41+
- The `user_id` is passed as an argument to the query, which the backend uses to fetch only the relevant data.
42+
43+
*Example (`TaskList.svelte`):*
44+
```svelte
45+
<script lang="ts">
46+
import { useQuery } from 'convex-svelte';
47+
import { api } from '@/convex/_generated/api';
48+
// ...
49+
const { user_id } = $props<{ user_id: string }>();
50+
const query = $derived(useQuery(api.tasks.get, { user_id }));
51+
</script>
52+
53+
<!-- UI reacts to query state (isLoading, error, data) -->
54+
```
55+
56+
### 3. Modifying Data (Mutations)
57+
58+
- The `useConvexClient` hook provides a raw client instance.
59+
- Mutations are called using `client.mutation()`, passing the `user_id` along with other arguments.
60+
61+
*Example (`TaskInput.svelte`):*
62+
```svelte
63+
<script lang="ts">
64+
import { useConvexClient } from 'convex-svelte';
65+
import { api } from '@/convex/_generated/api';
66+
// ...
67+
const client = useConvexClient();
68+
const { user_id } = $props<{ user_id: string }>();
69+
70+
const createTask = async () => {
71+
// ...
72+
await client.mutation(api.tasks.create, { body: taskBody, user_id });
73+
// ...
74+
};
75+
</script>
76+
```
77+
78+
### 4. Backend Authorization
79+
80+
This is the most critical piece of the pattern for ensuring security.
81+
82+
- **For Queries:** The backend uses the `user_id` to filter results using a database index. This is efficient and ensures users only see their own data.
83+
84+
*Example (`tasks.ts` - `get` query):*
85+
```typescript
86+
export const get = query({
87+
args: { user_id: v.string() },
88+
handler: async (ctx, args) => {
89+
return await ctx.db
90+
.query('tasks')
91+
.withIndex('by_user_id', (q) => q.eq('user_id', args.user_id))
92+
.collect();
93+
}
94+
});
95+
```
96+
97+
- **For Mutations/Actions:** For any operation that modifies or deletes a *specific* document, a dedicated authorization helper (`authorizeTaskAccess`) is used. This function retrieves the document and verifies that the `user_id` from the client matches the `user_id` stored on the document. If they don't match, it throws an error, preventing the operation from proceeding.
98+
99+
*Example (`tasks.ts` - `authorizeTaskAccess` and `update` mutation):*
100+
```typescript
101+
async function authorizeTaskAccess(
102+
ctx: { db: DatabaseReader },
103+
taskId: Id<'tasks'>,
104+
userId: string
105+
) {
106+
const task = await ctx.db.get(taskId);
107+
if (!task) throw new Error('Task not found');
108+
if (task.user_id !== userId) throw new Error('Not authorized to access this task');
109+
return task;
110+
}
111+
112+
export const update = mutation({
113+
args: { id: v.id('tasks'), isCompleted: v.boolean(), user_id: v.string() },
114+
handler: async (ctx, args) => {
115+
const { id, isCompleted, user_id } = args;
116+
await authorizeTaskAccess(ctx, id, user_id); // Authorization check
117+
await ctx.db.patch(id, { isCompleted });
118+
}
119+
});
120+
```
121+
122+
## Summary
123+
124+
This pattern provides a robust and scalable way to build secure applications with SvelteKit and Convex. By passing the user identity from the client and verifying it on the backend for every relevant operation, we ensure that users can only access and modify their own data.
125+
126+
## Alternative Authentication Patterns
127+
128+
While these examples use Clerk for authentication, it's worth noting that Convex provides its own built-in authentication solutions that may be more streamlined for certain use cases.
129+
130+
### Convex Auth (Beta)
131+
132+
Convex offers its own native solution, [Convex Auth](https://docs.convex.dev/auth/convex-auth), which is currently in beta. It simplifies adding authentication and offers a significant advantage over the client-driven pattern described in this document.
133+
134+
- **Primary Benefit:** With Convex Auth, the user's authentication information is automatically available in the backend context (`ctx`) of any query or mutation. This eliminates the need to manually pass the `user_id` from the client to every backend function, leading to cleaner and less error-prone code.
135+
- **Pro:** Tightly integrated with the Convex environment and simplifies backend authorization logic.
136+
- **Con:** As it is a managed service (similar to Clerk), you don't fully own your user data. It is also a beta feature and may evolve.
137+
138+
### Database Auth Pattern
139+
140+
Convex also supports a pattern for integrating with external auth providers by storing their user information directly in your Convex database.
141+
142+
- **Documentation:** [https://docs.convex.dev/auth/database-auth](https://docs.convex.dev/auth/database-auth)
143+
- **Pro:** Gives you more control over user data within your own database schema.
144+
- **Con:** This approach can require splitting user information between two systems (the auth provider and your Convex database), which can add complexity to data management.
145+
146+
The choice of authentication provider depends on the specific needs of the project, including requirements for data ownership, development speed, and integration complexity.

0 commit comments

Comments
 (0)