
Data Fetching in EverShop
EverShop uses GraphQL for data fetching. GraphQL is a query language for your API and a server-side runtime for executing queries using a type system you define for your data.
Check this GraphQL document to learn more about GraphQL in EverShop.
EverShop allows you to fetch data for your React components using the GraphQL query language. This document explains how to fetch data from the server using GraphQL.
GraphQL Query in React Components
When creating a React component that requires data for SSR (Server-Side Rendering), you need to fetch the data from the server during the request time. To do this, export a GraphQL query in the React component file. The query will be executed on the server, and the result will be passed to the React component as a prop.
Let's look at the example below:
export default function GeneralInfo({ product }) {
return (
<Area
id="productViewGeneralInfo"
coreComponents={[
{
component: { default: Name },
props: {
name: product.name,
},
sortOrder: 10,
id: "productSingleName",
},
{
component: { default: Price },
props: {
regular: product.price.regular,
special: product.price.special,
},
sortOrder: 10,
id: "productSinglePrice",
},
{
component: { default: Sku },
props: {
sku: product.sku,
},
sortOrder: 20,
id: "productSingleSku",
},
]}
/>
);
}
export const query = `
query Query {
product (id: getContextValue('productId')) {
name
sku
price {
regular {
value
text
}
special {
value
text
}
}
}
}`;
In the example above, we export a GraphQL query in the GeneralInformation.js component file. During the request time, EverShop consolidates all queries from all components and executes them in a single request. The result of the GraphQL query is passed to the React component as a prop.
When is the GraphQL Query Executed?
The query is executed on the server during the request time. It will only be executed if the component is rendered on the server.
The GraphQL query is extracted from the component file and executed on the server. The result of the query is then passed to the component as a prop.
The GraphQL Query Format
Since the build process uses Regex to parse, collect, and remove queries from the component file, you must ensure the export statement follows the format below:
export const query = `<Your GraphQL query>`;
The getContextValue Function
Sometimes, you need to pass arguments to the GraphQL query. For example, to fetch product details for a specific product, you need to pass the product ID to the GraphQL query. To do this, use the getContextValue function. This function returns the value of the context key you pass to it.
export const query = `
query Query {
product (id: getContextValue('productId')) {
name
sku
price {
regular {
value
text
}
special {
value
text
}
}
}
}`;
To add a value to the context, use a middleware function. Here's an example:
import { select } from "@evershop/postgres-query-builder";
import { pool } from "@evershop/evershop/lib/postgres";
import { setContextValue } from "@evershop/evershop/graphql/services";
export default async (request, response, next) => {
try {
const query = select();
query
.from("category")
.leftJoin("category_description")
.on(
"category.category_id",
"=",
"category_description.category_description_category_id"
);
// The categoryView route is /category/:uuid — the path param is `uuid`,
// not `url_key`. Filtering on url_key would 404 every category page.
query.where("category.uuid", "=", request.params.uuid);
const category = await query.load(pool);
if (category === null) {
response.status(404);
next();
} else {
setContextValue(request, "categoryId", category.category_id);
setContextValue(request, "pageInfo", {
title: category.meta_title || category.name,
description: category.meta_description || category.short_description,
url: request.url,
});
next();
}
} catch (e) {
next(e);
}
};
In the example above, the middleware function index.js validates the category's availability. If the category is available, we add the category ID to the context using the setContextValue function. The getContextValue function then retrieves the category ID from the context.
The setContextValue Function
This function adds a value to the GraphQL execution context. It accepts three arguments:
request: The request object.key: The context key.value: The context value.
By default, EverShop adds all data in the current request object to the context. For example, you can call the getContextValue('url') function to get the current request URL.
The getWidgetSetting Function
Widget components have a second placeholder available to them: getWidgetSetting(). It reads a value out of the widget instance's own settings rather than the request context.
Widgets normally declare GraphQL variables and fill them from settings, using a companion export const variables:
export const query = `
query Query($collection: String, $count: Int, $countPerRow: Int) {
collection(code: $collection) {
name
products(filters: [{ key: "limit", operation: eq, value: $count }]) {
items {
name
url
}
}
}
}`;
export const variables = `{
collection: getWidgetSetting("collection"),
count: getWidgetSetting("count"),
countPerRow: getWidgetSetting("countPerRow", 4)
}`;
It takes the setting path (dot notation for nested values, e.g. "style.columns") and an optional second argument used as the default when the setting is unset. It is also recognized inline inside export const query.
getWidgetSetting() is not a runtime function — like getContextValue(), it is a build-time marker. The same webpack loader rewrites both into Base64-encoded string placeholders ("getWidgetSetting_<base64>"), and the buildQuery middleware decodes them at request time, substituting the widget instance's stored settings before the query is executed. That is why the call must appear literally inside the exported template — a variable holding the key will not be rewritten.
See Widget Development for how widget settings are declared and stored.
How Server-Side Data Fetching Works Internally
Understanding the internal mechanism helps you debug data-fetching issues and write more effective queries.
Build Time: Query Extraction
During the build process, EverShop scans all React components for each route and:
- Extracts the
export const queryfrom each component file using regex. - Aliases all selected fields to prevent naming collisions across components (e.g.,
product.namebecomese_uniqueId_name). - Encodes
getContextValue()andgetWidgetSetting()calls as Base64 placeholders. - Merges all component queries into a single GraphQL query per route.
- Generates a
propsMapthat maps aliased field names back to the original component prop names.
The merged query and propsMap are saved as build artifacts.
Request Time: Query Execution
When a request arrives for a page route:
- The buildQuery middleware reads the pre-built query file.
- It decodes
getContextValue()placeholders by replacing them with actual values from the request context (set by earlier middleware). - If any widgets are registered for the route, their queries are merged in as well, with
getWidgetSetting()placeholders resolved against each widget instance's stored settings. - The graphql middleware executes the merged query against the schema.
- The response middleware uses the
propsMapto distribute the query results back to each component as props. - React's
renderToString()produces the HTML with all data pre-filled.
This is why setContextValue() must be called in a middleware that runs before the GraphQL query execution — the values need to be available when placeholders are replaced.
When a Resolver Fails
The SSR path and the client-side endpoint handle GraphQL errors differently:
- During SSR, field errors no longer abort the page. The merged query is executed, any errors are logged at
debuglevel, and whatever data did resolve (data.data, or{}if everything failed) is passed on to the renderer. A single failing or non-critical resolver — "related products", a third-party widget — degrades its own area instead of turning the whole page into a 500. - The client-side
/api/graphqlendpoint still aborts on the first error. It forwardsdata.errors[0]to the error handler rather than returning partial data, so auseQueryhook sees a failed result, not a half-filled one.
A failure in the React render itself (as opposed to a resolver) is fatal: it is forwarded to the error handler and produces a proper 500 instead of leaving the request hanging until the socket times out.
Client-Side Data Fetching
GraphQL API Endpoint
EverShop provides a GraphQL API endpoint to fetch data from the server. The storefront endpoint is /api/graphql; the admin endpoint is /api/admin/graphql. The URQL client is pre-wired to the right one for each context, so components hydrated on the storefront talk to /api/graphql and admin components talk to /api/admin/graphql.
The useQuery Hook from URQL
EverShop uses URQL to fetch data from the server using the GraphQL API. URQL is a fully-featured GraphQL client that supports all GraphQL features and can be used with any GraphQL server.
Example:
import React from "react";
import { useQuery } from "urql";
const TodosQuery = `
query {
todos {
id
title
}
}
`;
const Todos = () => {
const [result, reexecuteQuery] = useQuery({
query: TodosQuery,
});
const { data, fetching, error } = result;
if (fetching) return <p>Loading...</p>;
if (error) return <p>Oh no... {error.message}</p>;
return (
<ul>
{data.todos.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
);
};
Support us
EverShop is an open-source project that relies on community support. If you find our project useful, please consider sponsoring us.