Retrieving Data

Let's implement https://example.com/product/abc123 so that we can retrieve the details of a product.

File Layout

The first step is to structure the directories and files so that we can create the /product/abc123 route.

──src/
  └─routes/
    └─product/
        └─[skuId]/
            └─index.tsx     # https://example.com/product/1234

Implement onGet

In a "page", an onGet function retrieves data (typically from a database, or other stores). The retrieved data can be returned directly as JSON (Accept: application/json) or used as an input to the component to render HTML (Accept: text/html). The onGet function receives the params to extract the parameters used to do data lookup.

In this example, the onGet function returns the product data that is used inside of our component with the useEndpoint() function.

// File: src/routes/product/[skuId]/details/index.tsx
import type { RequestHandler } from '@builder.io/qwik-city';

interface ProductData {
  skuId: string;
  price: number;
  description: string;
}

export const onGet: RequestHandler<ProductData> = async ({ params }) => {
  // put your DB access here, we are hard coding a response for simplicity.
  return {
    skuId: params.skuId,
    price: 123.45,
    description: `Description for ${params.skuId}`,
  };
};

Using onGet in a Component

An onGet function retrieves data and makes it available to the component using useEndpoint().

import { Resource, component$, useStore } from '@builder.io/qwik';
import type { RequestHandler } from '@builder.io/qwik-city';
import { useEndpoint } from "@builder.io/qwik-city";


interface ProductData { ... }
export const onGet: RequestHandler<ProductData> = async ({ params }) => { ... };

export default component$(() => {
  const productData = useEndpoint<ProductData>();
  return (
    <Resource
      value={productData}
      onPending={() => <div>Loading...</div>}
      onRejected={() => <div>Error</div>}
      onResolved={(product) => (
        <>
          <h1>Product: {product.skuId}</h1>
          <p>Price: {product.price}</p>
          <p>{product.description}</p>
        </>
      )}
    />
  );
});
  1. Notice that the data request handler and the component are defined in the same file. The data is serviced by the onGet function and the component is by the modules default export.
  2. The component uses useEndpoint() function to retrieve the data. The useEndpoint() function invokes onGet function directly on the server but using fetch() on the client. Your component does not need to think about server/client differences when using data. The useEndpoint() function returns an object of type ResourceReturn. A Resource is a promise-like object that can be serialized by Qwik.
  3. The onGet function is invoked before the component. This allows the onGet to return 404 or redirect in case the data is not available.
  4. Notice the use of <Resource> JSX element. The purpose of Resource is to allow the client to render different states of the useEndpoint() resource.
  5. On the server the <Resource> element will pause rendering until the Resource is resolved or rejected. This is because we don't want the server to render loading.... (The server needs to know when the component is ready for serialization into HTML.)
  6. You may use typeof onGet to keep your onGet function and useEndpoint() types in sync. TypeScript is smart enough to determine the types for you.

All of the above is done to abstract the data access from the component in a way that results in correct behavior on both the server and the client.

Made with ❤️ by