Skip to main content

Data Fetching In EverShop

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.

info

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:

modules/catalog/pages/productView/GeneralInformation.jsx
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

warning

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.

modules/catalog/pages/productView/GeneralInformation.jsx
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:

modules/catalog/pages/categoryView/index.js
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.
info

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:

A widget component
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:

  1. Extracts the export const query from each component file using regex.
  2. Aliases all selected fields to prevent naming collisions across components (e.g., product.name becomes e_uniqueId_name).
  3. Encodes getContextValue() and getWidgetSetting() calls as Base64 placeholders.
  4. Merges all component queries into a single GraphQL query per route.
  5. Generates a propsMap that 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:

  1. The buildQuery middleware reads the pre-built query file.
  2. It decodes getContextValue() placeholders by replacing them with actual values from the request context (set by earlier middleware).
  3. 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.
  4. The graphql middleware executes the merged query against the schema.
  5. The response middleware uses the propsMap to distribute the query results back to each component as props.
  6. 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 debug level, 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/graphql endpoint still aborts on the first error. It forwards data.errors[0] to the error handler rather than returning partial data, so a useQuery hook 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.