Using Object Providers

Learn how to create reusable data fetching logic that automatically caches results.

Using Object Providers

In this tutorial, we will learn how to create a Object Provider.

What is it? A Object Provider is a standardized way for a module (like mod_shop) to share its data with the rest of the application (like mod_website or mod_admin).

It acts as a smart bridge between your raw data (Database, API) and your code, offering major benefits:

  1. Decoupling: The consumer doesn't need to know how the data is fetched (SQL, JSON, API...).
  2. Centralization: Data access logic is kept in one place, making it easier to maintain and refactor.
  3. Consistency: Ensures all parts of the application access data in the same way.

What we will build: We are going to create a system to manage a catalog of flowers.

  1. A Database (mocked with a simple JSON file).
  2. A Data Provider to fetch this data efficiently.
  3. A UI to display these products.

Prerequisites:


1. Setup the Data Source

First, let's simulate a database using a simple JSON file.

Step 1: Create the Project Structure

If you haven't already, create a simple workspace. We will work in a module named mod_shop.

  1. Go to src/.
  2. Create a directory named mod_shop.

Step 2: Create the JSON Database

Inside src/mod_shop/, create a file named db.json. Copy this content into it:

src/mod_shop/db.json
{
  "products": [
    { "id": "p1", "name": "Red Roses", "price": 25 },
    { "id": "p2", "name": "White Tulips", "price": 15 },
    { "id": "p3", "name": "Sunflowers", "price": 20 }
  ]
}

2. Defaulting the Object Provider

Now we will create the Object Provider. It will be the "bridge" between our application and this data file.

Step 1: Create the Provider Folder

In JopiJS, shared object providers must be placed in a specific folder structure.

  1. Inside src/mod_shop/, create a folder named @alias.
  2. Inside @alias, create a folder named objectProviders.
  3. Finally, create a folder for our specific provider: shop.product.

Full path: src/mod_shop/@alias/objectProviders/shop.product/

Step 2: Implement the Logic

Inside this folder, create a file named index.ts. This file will export the function responsible for fetching the data.

src/mod_shop/@alias/objectProviders/shop.product/index.ts
import db from "../../../db.json";
import type { ObjectProvider } from "jopijs";

// Define the keys we accept
type Key = { id?: string };

export default <ObjectProvider<Key, any>>{
    // The simplified API uses a single 'get' method
    async get(keys) {
        
        // Strategy 1: If an ID is provided, we return only this product.
        if (keys.id) {
            const product = db.products.find(p => p.id === keys.id);
            return product; 
        }

        // Strategy 2: If no ID is provided, we return the full list.
        return db.products;
    }
}

3. Consuming the Data

Now that our provider is ready, we can use it on a page. We will create a page that lists all products, and highlights one "Featured" product.

Step 1: Create the Page Route

We need a place to display this. Let's create a products route in a website module.

  1. In src/, create a new folder mod_website.
  2. Inside, create @routes.
  3. Inside @routes, create a folder named products.

Step 2: Fetch Data (pageData.ts)

Create a pageData.ts file in src/mod_website/@routes/products/. In JopiJS, this file is responsible for preparing the data on the server before rendering the page. Learn more about Page Data.

We will use our provider here. Notice how we import it using the alias system.

src/mod_website/@routes/products/pageData.ts
// Import using the alias generated by JopiJS from the folder name
import shopProduct from "@/objectProviders/shop.product";
import { JopiPageDataProvider } from "jopijs";

export default {
    async getDataForCache({ req }) {
        // 1. Fetch a single product
        const featured = await shopProduct.get({ id: "p1" });

        // 2. Fetch all products
        const allItems = await shopProduct.get({});

        return {
            items: allItems,
            featured: featured
        };
    }
} as JopiPageDataProvider;

Step 3: Display Data (page.tsx)

Finally, create the page.tsx file in the same folder to render the UI.

src/mod_website/@routes/products/page.tsx
import { usePageData } from "jopijs/ui";

export default function ProductsPage() {
    // Automatically retrieve the data returned by pageData.ts
    const { items, featured } = usePageData();

    return (
        <div className="p-10">
            <h1 className="text-3xl font-bold mb-6">Our Flowers</h1>

            {/* Featured Section */}
            {featured && (
                <div className="bg-orange-100 p-6 rounded-xl mb-10 border border-orange-200">
                    <h2 className="text-xl font-bold text-orange-800">✨ Featured Pick</h2>
                    <p className="text-2xl">{featured.name} - Only ${featured.price}!</p>
                </div>
            )}

            {/* List Section */}
            <div className="grid grid-cols-3 gap-4">
                {items?.map((product: any) => (
                    <div key={product.id} className="border p-4 rounded shadow-sm">
                        <h3 className="font-bold">{product.name}</h3>
                        <span className="text-green-600">${product.price}</span>
                    </div>
                ))}
            </div>
        </div>
    );
}

4. Test It

  1. Start your development server (npm run dev).
  2. Go to http://localhost:3000/products.

You should see the "Red Roses" highlighted as the featured product, followed by the list of all flowers.

Behind the Scenes: By using shopProduct.get(), your page requested data from the module mod_shop in a clean, decoupled way. If you modify how products are stored in mod_shop (e.g. switching to a real database), the products page code won't need to change.