@urql/solid-start#
@urql/solid-start provides URQL integration for SolidStart, built with SolidStart's native primitives like query, action, and createAsync.
Note: This package is specifically designed for SolidStart applications with SSR. If you're building a client-side only SolidJS app, use
@urql/solidinstead. See SolidJS vs SolidStart for key differences.
Features#
- 🎯 SolidStart Native - Built with
query,action,createAsync, anduseAction - 🚀 Automatic SSR - Works seamlessly with SolidStart's server-side rendering
- 🔄 Reactive Variables - Query variables can be signals that automatically trigger re-execution
- 📡 Real-time Subscriptions - Full GraphQL subscription support
- 🎨 Type Safe - Complete TypeScript support with GraphQL types
- 🪶 Lightweight - Minimal wrapper over URQL core
Installation#
npm install @urql/solid-start @urql/solid @urql/core graphql
# or
pnpm add @urql/solid-start @urql/solid @urql/core graphql
# or
yarn add @urql/solid-start @urql/solid @urql/core graphql
Note:
@urql/solidis a peer dependency required for subscriptions.
Quick Start#
1. Set up the Provider#
Wrap your app with the Provider to make the URQL client and router primitives available:
// src/app.tsx
import { Router, action, query } from '@solidjs/router';
import { FileRoutes } from '@solidjs/start/router';
import { Provider } from '@urql/solid-start';
import { createClient, cacheExchange, fetchExchange } from '@urql/core';
const client = createClient({
url: 'https://api.example.com/graphql',
exchanges: [cacheExchange, fetchExchange],
});
export default function App() {
return (
<Router root={props => <Provider value={{ client, query, action }}>{props.children}</Provider>}>
<FileRoutes />
</Router>
);
}
Note: The Provider accepts
client,query, andactionsocreateQueryandcreateMutationcan use SolidStart APIs via context.
2. Use Queries#
// src/routes/todos.tsx
import { createQuery } from '@urql/solid-start';
import { createAsync } from '@solidjs/router';
import { gql } from '@urql/core';
import { For, Show, Suspense } from 'solid-js';
const TodosQuery = gql`
query {
todos {
id
title
completed
}
}
`;
export default function TodosPage() {
const queryTodos = createQuery(TodosQuery, 'todos-list');
const todos = createAsync(() => queryTodos());
return (
<div>
<h1>Todos</h1>
<Suspense fallback={<div>Loading...</div>}>
<Show when={todos()?.data}>
<ul>
<For each={todos()!.data.todos}>
{todo => (
<li>
<input type="checkbox" checked={todo.completed} />
{todo.title}
</li>
)}
</For>
</ul>
</Show>
<Show when={todos()?.error}>
<p>Error: {todos()!.error.message}</p>
</Show>
</Suspense>
</div>
);
}
3. Use Mutations#
// src/components/AddTodoForm.tsx
import { createMutation } from '@urql/solid-start';
import { useAction, useSubmission } from '@solidjs/router';
import { gql } from '@urql/core';
import { Show } from 'solid-js';
const AddTodoMutation = gql`
mutation AddTodo($title: String!) {
addTodo(title: $title) {
id
title
}
}
`;
export function AddTodoForm() {
const addTodoAction = createMutation(AddTodoMutation, 'add-todo');
const addTodo = useAction(addTodoAction);
const submission = useSubmission(addTodoAction);
let inputRef: HTMLInputElement | undefined;
const handleSubmit = async (e: Event) => {
e.preventDefault();
if (!inputRef?.value) return;
const result = await addTodo({ title: inputRef.value });
if (result.data) {
inputRef.value = '';
}
};
return (
<form onSubmit={handleSubmit}>
<input ref={inputRef} type="text" placeholder="New todo" />
<button type="submit" disabled={submission.pending}>
Add Todo
</button>
<Show when={submission.result?.error}>
<p>Error: {submission.result!.error.message}</p>
</Show>
</form>
);
}
4. Use Subscriptions#
// src/components/LiveMessages.tsx
import { createSubscription } from '@urql/solid-start';
import { gql } from '@urql/core';
import { For } from 'solid-js';
const MessagesSubscription = gql`
subscription {
messageAdded {
id
text
createdAt
}
}
`;
export function LiveMessages() {
const [messages] = createSubscription(
{ query: MessagesSubscription },
// Optional handler to accumulate messages
(prev = [], data) => [...prev, data.messageAdded]
);
return (
<div>
<h2>Live Messages</h2>
<For each={messages.data}>{msg => <div>{msg.text}</div>}</For>
</div>
);
}
API Reference#
createQuery(queryDocument, key, options?)#
Creates a GraphQL query using SolidStart's query and createAsync primitives. The query function is automatically retrieved from context.
Parameters:
queryDocument: DocumentInput- GraphQL query documentkey: string- Cache key for SolidStart's routeroptions?: object- Optional configurationvariables?: Variables- Query variablesrequestPolicy?: RequestPolicy- Cache policycontext?: Partial<OperationContext>- Additional context
Returns: A query function that can be used with createAsync
Basic Example:
import { createAsync } from '@solidjs/router';
import { createQuery } from '@urql/solid-start';
export default function TodosPage() {
const queryTodos = createQuery(TodosQuery, 'todos-list');
const todos = createAsync(() => queryTodos());
return <div>{/* ... */}</div>;
}
Example with variables:
import { createAsync } from '@solidjs/router';
import { createQuery } from '@urql/solid-start';
export default function UserPage() {
const queryUser = createQuery(UserQuery, 'user-details', {
variables: { id: 1 },
});
const user = createAsync(() => queryUser());
return <div>{/* ... */}</div>;
}
Example with custom client:
import { createAsync } from '@solidjs/router';
import { createQuery } from '@urql/solid-start';
import { createClient } from '@urql/core';
const customClient = createClient({ url: 'https://api.example.com/graphql' });
export default function CustomPage() {
const queryTodos = createQuery(TodosQuery, 'todos-list');
const todos = createAsync(() => queryTodos(customClient));
return <div>{/* ... */}</div>;
}
Note:
createQuerymust be called inside a component where it has access to the URQL context. The query function from@solidjs/routeris automatically retrieved from the Provider.
createMutation(mutation, key)#
Creates a GraphQL mutation action using SolidStart's action primitive.
Args:
mutation: DocumentInput- GraphQL mutation documentkey: string- Cache key for SolidStart's router
Returns: Action - A SolidStart action that can be used with useAction() and useSubmission()
Example:
import { createMutation } from '@urql/solid-start';
import { useAction, useSubmission } from '@solidjs/router';
const updateUserAction = createMutation(UpdateUserMutation, 'update-user');
const updateUser = useAction(updateUserAction);
const submission = useSubmission(updateUserAction);
// Call the mutation
const result = await updateUser({ id: 1, name: 'Alice' });
// Access submission state
console.log(submission.pending); // boolean
console.log(submission.result); // OperationResult
createSubscription(args, handler?)#
Creates a GraphQL subscription for real-time updates.
Args:
{
query: DocumentInput;
variables?: MaybeAccessor<Variables>;
context?: MaybeAccessor<Partial<OperationContext>>;
pause?: MaybeAccessor<boolean>;
}
Handler: Optional function to accumulate/transform subscription data
(previousData: Data | undefined, newData: Data) => Data;
Returns: [State, ExecuteFunction]
Example with handler:
const [messages] = createSubscription({ query: MessagesSubscription }, (prev = [], data) => [
...prev,
data.messageAdded,
]);
Provider#
Context provider for the URQL client and SolidStart router primitives.
Props:
value: { client: Client; query: typeof query; action: typeof action }- Object containing the URQL client and SolidStart router primitives
Example:
import { action, query } from '@solidjs/router';
import { Provider } from '@urql/solid-start';
<Provider value={{ client, query, action }}>
<App />
</Provider>;
useClient()#
Hook to access the URQL client from context.
Returns: Client
Request Policies#
Control caching behavior with requestPolicy:
cache-first(default) - Use cache if available, otherwise fetchcache-only- Only use cached data, never fetchnetwork-only- Always fetch, ignore cachecache-and-network- Return cache immediately, then fetch in background
const todos = createQuery({
query: TodosQuery,
requestPolicy: 'cache-and-network',
});
Dynamic Queries#
For dynamic queries that change based on reactive values, you can pass variables to the query function:
import { createSignal } from 'solid-js';
import { createAsync } from '@solidjs/router';
import { createQuery } from '@urql/solid-start';
export default function UserPage() {
const [userId, setUserId] = createSignal(1);
// Create the query function
const queryUser = createQuery(UserQuery, 'user-details', {
variables: { id: userId() },
});
// Wrap with createAsync to get reactive data
const user = createAsync(() => queryUser());
return (
<div>
<button onClick={() => setUserId(userId() + 1)}>Next User</button>
<Show when={user()?.data}>
<h1>{user()!.data.user.name}</h1>
</Show>
</div>
);
}
Note: For fully reactive variables that trigger re-fetches, you may need to create the query function inside a createEffect or recreate it when dependencies change.
Cache Keys#
Cache keys are required for both queries and mutations to enable SolidStart's caching and revalidation features:
// Query with cache key
const queryTodos = createQuery(TodosQuery, 'todos-list', query);
// Mutation with cache key
const [state, addTodo] = createMutation(AddTodoMutation, 'add-todo');
Choose descriptive cache keys that:
- Are unique within your application
- Describe the data being cached (e.g., 'user-profile', 'todos-list')
- Make debugging easier by being human-readable
Advanced: Custom Exchanges#
Add custom exchanges for authentication, error handling, etc:
import { createClient, cacheExchange, fetchExchange } from '@urql/core';
import { authExchange } from '@urql/exchange-auth';
const client = createClient({
url: 'https://api.example.com/graphql',
exchanges: [
cacheExchange,
authExchange(async utils => {
return {
addAuthToOperation(operation) {
const token = localStorage.getItem('token');
if (!token) return operation;
return utils.appendHeaders(operation, {
Authorization: `Bearer ${token}`,
});
},
didAuthError(error) {
return error.graphQLErrors.some(e => e.extensions?.code === 'UNAUTHENTICATED');
},
async refreshAuth() {
// Refresh token logic
},
};
}),
fetchExchange,
],
});
TypeScript#
Full type safety with GraphQL types:
import { createQuery } from '@urql/solid-start';
import { gql, type TypedDocumentNode } from '@urql/core';
interface Todo {
id: string;
title: string;
completed: boolean;
}
interface TodosData {
todos: Todo[];
}
const TodosQuery: TypedDocumentNode<TodosData> = gql`
query {
todos {
id
title
completed
}
}
`;
// Fully typed!
const todos = createQuery({ query: TodosQuery });
// ^? Accessor<OperationResult<TodosData> | undefined>
How It Works#
@urql/solid-start integrates URQL with SolidStart's primitives:
createQuerywraps SolidStart'squery()function to execute URQL queries with automatic SSR and caching. Thequeryfunction is automatically retrieved from the URQL context, eliminating the need for manual injection.createMutationcreates SolidStartaction()primitives that integrate withuseAction()anduseSubmission()for form handling and progressive enhancementcreateSubscriptionuses@urql/solid-startcontext so it works with the same Provider as queries and mutations
This means you get:
- ✅ Automatic server-side rendering
- ✅ Request deduplication via SolidStart's query caching
- ✅ Streaming responses
- ✅ Progressive enhancement with actions
- ✅ Full fine-grained reactivity
SolidJS vs SolidStart#
When to Use Each Package#
| Use Case | Package | Why |
|---|---|---|
| Client-side SPA | @urql/solid |
Optimized for client-only apps, uses SolidJS reactivity patterns |
| SolidStart SSR App | @urql/solid-start |
Integrates with SolidStart's routing, SSR, and action system |
Key Differences#
Queries#
@urql/solid (Client-side):
import { createQuery } from '@urql/solid';
const [result] = createQuery({ query: TodosQuery });
// Returns: [Accessor<OperationResult>, Accessor<ReExecute>]
@urql/solid-start (SSR):
import { createQuery } from '@urql/solid-start';
import { createAsync } from '@solidjs/router';
const queryTodos = createQuery(TodosQuery, 'todos');
const todos = createAsync(() => queryTodos());
// Returns: Accessor<OperationResult | undefined>
// Works with SSR and SolidStart's caching
Mutations#
@urql/solid (Client-side):
import { createMutation } from '@urql/solid';
const [result, executeMutation] = createMutation(AddTodoMutation);
await executeMutation({ title: 'New Todo' });
// Returns: [Accessor<OperationResult>, ExecuteMutation]
@urql/solid-start (SSR with Actions):
import { createMutation } from '@urql/solid-start';
import { useAction, useSubmission } from '@solidjs/router';
const addTodoAction = createMutation(AddTodoMutation, 'add-todo');
const addTodo = useAction(addTodoAction);
const submission = useSubmission(addTodoAction);
await addTodo({ title: 'New Todo' });
// Integrates with SolidStart's action system for progressive enhancement
Why Different APIs?#
- SSR Support: SolidStart queries run on the server and stream to the client
- Router Integration: Automatic caching and invalidation with SolidStart's router
- Progressive Enhancement: Actions work without JavaScript enabled
- Suspense: Native support for SolidJS Suspense boundaries
Migration#
If you're moving from a SolidJS SPA to SolidStart:
- Change imports from
@urql/solidto@urql/solid-start - Wrap queries with
createAsync() - Update mutations to use the action pattern with
useAction()anduseSubmission()
Resources#
License#
MIT