Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
1---
2title: SolidStart Bindings
3order: 3
4---
5
6# SolidStart
7
8This guide covers how to use `@urql/solid-start` with SolidStart applications. The `@urql/solid-start` package integrates urql with SolidStart's native data fetching primitives like `query()`, `action()`, `createAsync()`, and `useAction()`.
9
10> **Note:** This guide is for SolidStart applications with SSR. If you're building a client-side only SolidJS app, see the [Solid guide](./solid.md) instead. See the [comparison section](#solidjs-vs-solidstart) below for key differences between the packages.
11
12## Getting started
13
14### Installation
15
16Installing `@urql/solid-start` requires both the package and its peer dependencies:
17
18```sh
19yarn add @urql/solid-start @urql/solid @urql/core graphql
20# or
21npm install --save @urql/solid-start @urql/solid @urql/core graphql
22# or
23pnpm add @urql/solid-start @urql/solid @urql/core graphql
24```
25
26The `@urql/solid-start` package depends on `@urql/solid` for shared utilities and re-exports some primitives that work identically on both client and server.
27
28### Setting up the `Client`
29
30The `@urql/solid-start` package exports a `Client` class from `@urql/core`. This central `Client` manages all of our GraphQL requests and results.
31
32```js
33import { createClient, cacheExchange, fetchExchange } from '@urql/solid-start';
34
35const client = createClient({
36 url: 'http://localhost:3000/graphql',
37 exchanges: [cacheExchange, fetchExchange],
38});
39```
40
41At the bare minimum we'll need to pass an API's `url` and `exchanges` when we create a `Client`.
42
43For server-side requests, you'll often want to customize `fetchOptions` to include headers like cookies or authorization tokens:
44
45```js
46import { getRequestEvent } from 'solid-js/web';
47
48const client = createClient({
49 url: 'http://localhost:3000/graphql',
50 exchanges: [cacheExchange, fetchExchange],
51 fetchOptions: () => {
52 const event = getRequestEvent();
53 return {
54 headers: {
55 cookie: event?.request.headers.get('cookie') || '',
56 },
57 };
58 },
59});
60```
61
62### Providing the `Client`
63
64To make use of the `Client` in SolidStart we will provide it via Solid's Context API using the `Provider` export. The Provider also needs the `query` and `action` functions from `@solidjs/router`:
65
66```jsx
67// src/root.tsx or src/app.tsx
68import { Router, action, query } from '@solidjs/router';
69import { FileRoutes } from '@solidjs/start/router';
70import { Suspense } from 'solid-js';
71import { createClient, Provider, cacheExchange, fetchExchange } from '@urql/solid-start';
72
73const client = createClient({
74 url: 'http://localhost:3000/graphql',
75 exchanges: [cacheExchange, fetchExchange],
76});
77
78export default function App() {
79 return (
80 <Router
81 root={props => (
82 <Provider value={{ client, query, action }}>
83 <Suspense>{props.children}</Suspense>
84 </Provider>
85 )}
86 >
87 <FileRoutes />
88 </Router>
89 );
90}
91```
92
93Now every route and component inside the `Provider` can use GraphQL queries and mutations that will be sent to our API. The `query` and `action` functions are provided in context so that `createQuery` and `createMutation` can access them automatically.
94
95## Queries
96
97The `@urql/solid-start` package offers a `createQuery` primitive that integrates with SolidStart's `query()` and `createAsync()` primitives for optimal server-side rendering and streaming.
98
99### Run a first query
100
101For the following examples, we'll imagine that we're querying data from a GraphQL API that contains todo items.
102
103```jsx
104// src/routes/todos.tsx
105import { Suspense, For, Show } from 'solid-js';
106import { createAsync } from '@solidjs/router';
107import { gql } from '@urql/core';
108import { createQuery } from '@urql/solid-start';
109
110const TodosQuery = gql`
111 query {
112 todos {
113 id
114 title
115 }
116 }
117`;
118
119export default function Todos() {
120 const queryTodos = createQuery(TodosQuery, 'todos-list');
121 const result = createAsync(() => queryTodos());
122
123 return (
124 <Suspense fallback={<p>Loading...</p>}>
125 <Show when={result()?.data}>
126 <ul>
127 <For each={result()!.data.todos}>
128 {(todo) => <li>{todo.title}</li>}
129 </For>
130 </ul>
131 </Show>
132 </Suspense>
133 );
134}
135```
136
137The `createQuery` primitive integrates with SolidStart's data fetching system:
138
1391. It wraps SolidStart's `query()` function to execute URQL queries with proper router context
1402. The `query` function is automatically retrieved from the URQL context (no manual injection needed)
1413. The second parameter is a cache key (string) for SolidStart's router
1424. The returned function is wrapped with `createAsync()` to get the reactive result
1435. `createQuery` must be called inside a component where it has access to the context
144
145The query automatically executes on both the server (during SSR) and the client, with SolidStart handling serialization and hydration.
146
147### Variables
148
149Typically we'll also need to pass variables to our queries. Pass variables as an option in the fourth parameter:
150
151```jsx
152// src/routes/todos/[page].tsx
153import { Suspense, For, Show } from 'solid-js';
154import { useParams, createAsync } from '@solidjs/router';
155import { gql } from '@urql/core';
156import { createQuery } from '@urql/solid-start';
157
158const TodosListQuery = gql`
159 query ($from: Int!, $limit: Int!) {
160 todos(from: $from, limit: $limit) {
161 id
162 title
163 }
164 }
165`;
166
167export default function TodosPage() {
168 const params = useParams();
169
170 const queryTodos = createQuery(TodosListQuery, 'todos-paginated', {
171 variables: {
172 from: parseInt(params.page) * 10,
173 limit: 10,
174 },
175 });
176
177 const result = createAsync(() => queryTodos());
178
179 return (
180 <Suspense fallback={<p>Loading...</p>}>
181 <Show when={result()?.data}>
182 <ul>
183 <For each={result()!.data.todos}>
184 {(todo) => <li>{todo.title}</li>}
185 </For>
186 </ul>
187 </Show>
188 </Suspense>
189 );
190}
191```
192
193For dynamic variables that change based on reactive values, you'll need to recreate the query function when dependencies change.
194
195### Request Policies
196
197The `requestPolicy` option determines how results are retrieved from the cache:
198
199```jsx
200const queryTodos = createQuery(TodosQuery, 'todos-list', {
201 requestPolicy: 'cache-and-network',
202});
203const result = createAsync(() => queryTodos());
204```
205
206Available policies:
207
208- `cache-first` (default): Prefer cached results, fall back to network
209- `cache-only`: Only use cached results, never send network requests
210- `network-only`: Always send a network request, ignore cache
211- `cache-and-network`: Return cached results immediately, then fetch from network
212
213[Learn more about request policies on the "Document Caching" page.](./document-caching.md)
214
215### Revalidation
216
217There are two approaches to revalidating data in SolidStart with urql:
218
2191. **urql's cache invalidation** - Invalidates specific queries or entities in urql's cache, causing automatic refetches
2202. **SolidStart's revalidation** - Uses SolidStart's router revalidation to reload route data
221
222Both approaches work well, and you can choose based on your needs. urql's invalidation is more granular and works at the query level, while SolidStart's revalidation works at the route level.
223
224#### Manual Revalidation with urql
225
226You can manually revalidate queries using urql's cache invalidation with the `keyFor` helper. This invalidates specific queries in urql's cache and triggers automatic refetches:
227
228```jsx
229// src/routes/todos.tsx
230import { Suspense, For, Show } from 'solid-js';
231import { createAsync } from '@solidjs/router';
232import { gql, keyFor } from '@urql/core';
233import { createQuery, useClient } from '@urql/solid-start';
234
235const TodosQuery = gql`
236 query {
237 todos {
238 id
239 title
240 }
241 }
242`;
243
244export default function Todos() {
245 const client = useClient();
246 const queryTodos = createQuery(TodosQuery, 'todos-list');
247 const result = createAsync(() => queryTodos());
248
249 const handleRefresh = () => {
250 // Invalidate the todos query using keyFor
251 const key = keyFor(TodosQuery);
252 client.reexecuteOperation(client.createRequestOperation('query', {
253 key,
254 query: TodosQuery
255 }));
256 };
257
258 return (
259 <div>
260 <button onClick={handleRefresh}>Refresh Todos</button>
261 <Suspense fallback={<p>Loading...</p>}>
262 <Show when={result()?.data}>
263 <ul>
264 <For each={result()!.data.todos}>
265 {(todo) => <li>{todo.title}</li>}
266 </For>
267 </ul>
268 </Show>
269 </Suspense>
270 </div>
271 );
272}
273```
274
275#### Manual Revalidation with SolidStart
276
277Alternatively, you can use SolidStart's built-in `revalidate` function to reload route data. This is useful when you want to refresh all queries on a specific route:
278
279```jsx
280// src/routes/todos.tsx
281import { Suspense, For, Show } from 'solid-js';
282import { createAsync, revalidate } from '@solidjs/router';
283import { gql } from '@urql/core';
284import { createQuery } from '@urql/solid-start';
285
286const TodosQuery = gql`
287 query {
288 todos {
289 id
290 title
291 }
292 }
293`;
294
295export default function Todos() {
296 const queryTodos = createQuery(TodosQuery, 'todos-list');
297 const result = createAsync(() => queryTodos());
298
299 const handleRefresh = async () => {
300 // Revalidate the current route - refetches all queries on this page
301 await revalidate();
302 };
303
304 return (
305 <div>
306 <button onClick={handleRefresh}>Refresh Todos</button>
307 <Suspense fallback={<p>Loading...</p>}>
308 <Show when={result()?.data}>
309 <ul>
310 <For each={result()!.data.todos}>
311 {(todo) => <li>{todo.title}</li>}
312 </For>
313 </ul>
314 </Show>
315 </Suspense>
316 </div>
317 );
318}
319```
320
321#### Revalidation After Mutations
322
323A common pattern is to revalidate after a mutation succeeds. You can choose either approach:
324
325**Using urql's cache invalidation:**
326
327```jsx
328// src/routes/todos/new.tsx
329import { useNavigate } from '@solidjs/router';
330import { gql, keyFor } from '@urql/core';
331import { createMutation, useClient } from '@urql/solid-start';
332
333const TodosQuery = gql`
334 query {
335 todos {
336 id
337 title
338 }
339 }
340`;
341
342const CreateTodo = gql`
343 mutation ($title: String!) {
344 createTodo(title: $title) {
345 id
346 title
347 }
348 }
349`;
350
351export default function NewTodo() {
352 const navigate = useNavigate();
353 const client = useClient();
354 const [state, createTodo] = createMutation(CreateTodo);
355
356 const handleSubmit = async (e: Event) => {
357 e.preventDefault();
358 const formData = new FormData(e.target as HTMLFormElement);
359 const title = formData.get('title') as string;
360
361 const result = await createTodo({ title });
362
363 if (!result.error) {
364 // Invalidate todos query using keyFor
365 const key = keyFor(TodosQuery);
366 client.reexecuteOperation(client.createRequestOperation('query', {
367 key,
368 query: TodosQuery
369 }));
370 navigate('/todos');
371 }
372 };
373
374 return (
375 <form onSubmit={handleSubmit}>
376 <input name="title" type="text" required />
377 <button type="submit" disabled={state.fetching}>
378 {state.fetching ? 'Creating...' : 'Create Todo'}
379 </button>
380 </form>
381 );
382}
383```
384
385**Using SolidStart's revalidation:**
386
387```jsx
388// src/routes/todos/new.tsx
389import { useNavigate } from '@solidjs/router';
390import { gql } from '@urql/core';
391import { createMutation } from '@urql/solid-start';
392import { revalidate } from '@solidjs/router';
393
394const CreateTodo = gql`
395 mutation ($title: String!) {
396 createTodo(title: $title) {
397 id
398 title
399 }
400 }
401`;
402
403export default function NewTodo() {
404 const navigate = useNavigate();
405 const [state, createTodo] = createMutation(CreateTodo);
406
407 const handleSubmit = async (e: Event) => {
408 e.preventDefault();
409 const formData = new FormData(e.target as HTMLFormElement);
410 const title = formData.get('title') as string;
411
412 const result = await createTodo({ title });
413
414 if (!result.error) {
415 // Revalidate the /todos route to refetch all its queries
416 await revalidate('/todos');
417 navigate('/todos');
418 }
419 };
420
421 return (
422 <form onSubmit={handleSubmit}>
423 <input name="title" type="text" required />
424 <button type="submit" disabled={state.fetching}>
425 {state.fetching ? 'Creating...' : 'Create Todo'}
426 </button>
427 </form>
428 );
429}
430```
431
432#### Automatic Revalidation with Actions
433
434When using SolidStart actions, you can configure automatic revalidation by returning the appropriate response:
435
436```jsx
437import { action, revalidate } from '@solidjs/router';
438import { gql } from '@urql/core';
439
440const createTodoAction = action(async (formData: FormData) => {
441 const title = formData.get('title') as string;
442
443 // Perform mutation
444 const result = await client.mutation(CreateTodo, { title }).toPromise();
445
446 if (!result.error) {
447 // Revalidate multiple routes if needed
448 await revalidate(['/todos', '/']);
449 }
450
451 return result;
452});
453```
454
455#### Choosing Between Approaches
456
457**Use urql's `keyFor` and `reexecuteOperation` when:**
458
459- You need to refetch a specific query after a mutation
460- You want fine-grained control over which queries to refresh
461- You're working with multiple queries on the same route and only want to refetch one
462
463**Use SolidStart's `revalidate` when:**
464
465- You want to refresh all data on a route
466- You're navigating to a different route and want to ensure fresh data
467- You want to leverage SolidStart's routing system for cache management
468
469Both approaches are valid and can even be used together depending on your application's needs.
470
471### Context Options
472
473Context options can be passed to customize the query behavior:
474
475```jsx
476const queryTodos = createQuery(TodosQuery, 'todos-list', {
477 context: {
478 requestPolicy: 'cache-and-network',
479 fetchOptions: {
480 headers: {
481 'X-Custom-Header': 'value',
482 },
483 },
484 },
485});
486const result = createAsync(() => queryTodos());
487```
488
489[You can find a list of all `Context` options in the API docs.](../api/core.md#operationcontext)
490
491## Mutations
492
493The `@urql/solid-start` package offers a `createMutation` primitive that integrates with SolidStart's `action()` and `useAction()` primitives.
494
495### Sending a mutation
496
497Mutations in SolidStart are executed using actions. Here's an example of updating a todo item:
498
499```jsx
500// src/routes/todos/[id]/edit.tsx
501import { gql } from '@urql/core';
502import { createMutation } from '@urql/solid-start';
503import { useParams, useNavigate } from '@solidjs/router';
504import { Show } from 'solid-js';
505
506const UpdateTodo = gql`
507 mutation ($id: ID!, $title: String!) {
508 updateTodo(id: $id, title: $title) {
509 id
510 title
511 }
512 }
513`;
514
515export default function EditTodo() {
516 const params = useParams();
517 const navigate = useNavigate();
518 const [state, updateTodo] = createMutation(UpdateTodo);
519
520 const handleSubmit = async (e: Event) => {
521 e.preventDefault();
522 const formData = new FormData(e.target as HTMLFormElement);
523 const title = formData.get('title') as string;
524
525 const result = await updateTodo({
526 id: params.id,
527 title,
528 });
529
530 if (!result.error) {
531 navigate(`/todos/${params.id}`);
532 }
533 };
534
535 return (
536 <form onSubmit={handleSubmit}>
537 <input name="title" type="text" required />
538 <button type="submit" disabled={state.fetching}>
539 {state.fetching ? 'Saving...' : 'Save'}
540 </button>
541 <Show when={state.error}>
542 <p style={{ color: 'red' }}>Error: {state.error.message}</p>
543 </Show>
544 </form>
545 );
546}
547```
548
549The `createMutation` primitive returns a tuple:
550
5511. A reactive state object containing `fetching`, `error`, and `data`
5522. An execute function that triggers the mutation
553
554You can optionally provide a custom `key` parameter to control how mutations are cached by SolidStart's router:
555
556```jsx
557const [state, updateTodo] = createMutation(UpdateTodo, 'update-todo-mutation');
558```
559
560### Progressive enhancement with actions
561
562SolidStart actions work with and without JavaScript enabled. Here's how to set up a mutation that works progressively:
563
564```jsx
565import { action, redirect } from '@solidjs/router';
566import { gql } from '@urql/core';
567import { createMutation } from '@urql/solid-start';
568
569const CreateTodo = gql`
570 mutation ($title: String!) {
571 createTodo(title: $title) {
572 id
573 title
574 }
575 }
576`;
577
578export default function NewTodo() {
579 const [state, createTodo] = createMutation(CreateTodo);
580
581 const handleSubmit = async (formData: FormData) => {
582 const title = formData.get('title') as string;
583 const result = await createTodo({ title });
584
585 if (!result.error) {
586 return redirect('/todos');
587 }
588 };
589
590 return (
591 <form action={handleSubmit} method="post">
592 <input name="title" type="text" required />
593 <button type="submit" disabled={state.fetching}>
594 {state.fetching ? 'Creating...' : 'Create Todo'}
595 </button>
596 <Show when={state.error}>
597 <p style={{ color: 'red' }}>Error: {state.error.message}</p>
598 </Show>
599 </form>
600 );
601}
602```
603
604### Using mutation results
605
606The mutation state is reactive and updates automatically as the mutation progresses:
607
608```jsx
609const [state, updateTodo] = createMutation(UpdateTodo);
610
611createEffect(() => {
612 if (state.data) {
613 console.log('Mutation succeeded:', state.data);
614 }
615 if (state.error) {
616 console.error('Mutation failed:', state.error);
617 }
618 if (state.fetching) {
619 console.log('Mutation in progress...');
620 }
621});
622```
623
624The execute function also returns a promise that resolves to the result:
625
626```jsx
627const [state, updateTodo] = createMutation(UpdateTodo);
628
629const handleUpdate = async () => {
630 const result = await updateTodo({ id: '1', title: 'Updated' });
631
632 if (result.error) {
633 console.error('Oh no!', result.error);
634 } else {
635 console.log('Success!', result.data);
636 }
637};
638```
639
640### Handling mutation errors
641
642Mutation promises never reject. Instead, check the `error` field on the result:
643
644```jsx
645const [state, updateTodo] = createMutation(UpdateTodo);
646
647const handleUpdate = async () => {
648 const result = await updateTodo({ id: '1', title: 'Updated' });
649
650 if (result.error) {
651 // CombinedError with network or GraphQL errors
652 console.error('Mutation failed:', result.error);
653
654 // Check for specific error types
655 if (result.error.networkError) {
656 console.error('Network error:', result.error.networkError);
657 }
658 if (result.error.graphQLErrors.length > 0) {
659 console.error('GraphQL errors:', result.error.graphQLErrors);
660 }
661 }
662};
663```
664
665[Read more about error handling on the "Errors" page.](./errors.md)
666
667## Subscriptions
668
669For GraphQL subscriptions, `@urql/solid-start` provides a `createSubscription` primitive that uses the same SolidStart `Provider` context as `createQuery` and `createMutation`:
670
671```jsx
672import { gql } from '@urql/core';
673import { createSubscription } from '@urql/solid-start';
674import { createSignal, For } from 'solid-js';
675
676const NewTodos = gql`
677 subscription {
678 newTodos {
679 id
680 title
681 }
682 }
683`;
684
685export default function TodoSubscription() {
686 const [todos, setTodos] = createSignal([]);
687
688 const handleSubscription = (previousData, newData) => {
689 setTodos(current => [...current, newData.newTodos]);
690 return newData;
691 };
692
693 const [result] = createSubscription(
694 {
695 query: NewTodos,
696 },
697 handleSubscription
698 );
699
700 return (
701 <div>
702 <h2>Live Updates</h2>
703 <ul>
704 <For each={todos()}>{todo => <li>{todo.title}</li>}</For>
705 </ul>
706 </div>
707 );
708}
709```
710
711Note that GraphQL subscriptions typically require WebSocket support. You'll need to configure your client with a subscription exchange like `subscriptionExchange` from `@urql/core`.
712
713## Server-Side Rendering
714
715SolidStart automatically handles server-side rendering and hydration. The `createQuery` primitive works seamlessly on both server and client:
716
7171. On the server, queries execute during SSR and their results are serialized
7182. On the client, SolidStart hydrates the data without refetching
7193. Subsequent navigations use the standard cache policies
720
721### SSR Considerations
722
723When using `createQuery` in SolidStart:
724
725- Queries execute on the server during initial page load
726- Results are automatically streamed to the client
727- The client hydrates with the server data
728- No manual script injection or data serialization needed
729- SolidStart handles all the complexity automatically
730
731### Handling cookies and authentication
732
733For authenticated requests, forward cookies and headers from the server request:
734
735```jsx
736import { getRequestEvent } from 'solid-js/web';
737import { createClient, cacheExchange, fetchExchange } from '@urql/solid-start';
738
739const client = createClient({
740 url: 'http://localhost:3000/graphql',
741 exchanges: [cacheExchange, fetchExchange],
742 fetchOptions: () => {
743 const event = getRequestEvent();
744 const headers: Record<string, string> = {};
745
746 // Forward cookies for authenticated requests
747 if (event) {
748 const cookie = event.request.headers.get('cookie');
749 if (cookie) {
750 headers.cookie = cookie;
751 }
752 }
753
754 return { headers };
755 },
756});
757```
758
759## SolidJS vs SolidStart
760
761### When to Use Each Package
762
763| Use Case | Package | Why |
764| ------------------ | ------------------- | ---------------------------------------------------------------- |
765| Client-side SPA | `@urql/solid` | Optimized for client-only apps, uses SolidJS reactivity patterns |
766| SolidStart SSR App | `@urql/solid-start` | Integrates with SolidStart's routing, SSR, and action system |
767
768### Key Differences
769
770#### Queries
771
772**@urql/solid** (Client-side):
773
774```tsx
775import { createQuery } from '@urql/solid';
776
777const [result] = createQuery({ query: TodosQuery });
778// Returns: [Accessor<OperationResult>, Accessor<ReExecute>]
779```
780
781**@urql/solid-start** (SSR):
782
783```tsx
784import { createQuery } from '@urql/solid-start';
785import { createAsync } from '@solidjs/router';
786
787const queryTodos = createQuery(TodosQuery, 'todos');
788const todos = createAsync(() => queryTodos());
789// Returns: Accessor<OperationResult | undefined>
790// Works with SSR and SolidStart's caching
791```
792
793#### Mutations
794
795**@urql/solid** (Client-side):
796
797```tsx
798import { createMutation } from '@urql/solid';
799
800const [result, executeMutation] = createMutation(AddTodoMutation);
801await executeMutation({ title: 'New Todo' });
802// Returns: [Accessor<OperationResult>, ExecuteMutation]
803```
804
805**@urql/solid-start** (SSR with Actions):
806
807```tsx
808import { createMutation } from '@urql/solid-start';
809import { useAction, useSubmission } from '@solidjs/router';
810
811const addTodoAction = createMutation(AddTodoMutation, 'add-todo');
812const addTodo = useAction(addTodoAction);
813const submission = useSubmission(addTodoAction);
814await addTodo({ title: 'New Todo' });
815// Integrates with SolidStart's action system for progressive enhancement
816```
817
818### Why Different APIs?
819
820- **SSR Support**: SolidStart queries run on the server and stream to the client
821- **Router Integration**: Automatic caching and invalidation with SolidStart's router
822- **Progressive Enhancement**: Actions work without JavaScript enabled
823- **Suspense**: Native support for SolidJS Suspense boundaries
824
825### Migration
826
827If you're moving from a SolidJS SPA to SolidStart:
828
8291. Change imports from `@urql/solid` to `@urql/solid-start`
8302. Wrap queries with `createAsync()`
8313. Update mutations to use the action pattern with `useAction()` and `useSubmission()`
832
833For more details, see the [Solid bindings documentation](./solid.md).
834
835## Reading on
836
837This concludes the introduction for using `@urql/solid-start` with SolidStart. For more information:
838
839- [Solid bindings documentation](./solid.md) - for client-only features
840- [How does the default "document cache" work?](./document-caching.md)
841- [How are errors handled and represented?](./errors.md)
842- [A quick overview of `urql`'s architecture and structure.](../architecture.md)
843- [Setting up other features, like authentication, uploads, or persisted queries.](../advanced/README.md)