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:
- Decoupling: The consumer doesn't need to know how the data is fetched (SQL, JSON, API...).
- Centralization: Data access logic is kept in one place, making it easier to maintain and refactor.
- 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.
- A Database (mocked with a simple JSON file).
- A Data Provider to fetch this data efficiently.
- A UI to display these products.
Prerequisites:
- Project initialized with
minimaltemplate (see here for instructions). - Ensure your server is running (
npm run start:dev_server).
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.
- Go to
src/. - 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:
{
"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.
- Inside
src/mod_shop/, create a folder named@alias. - Inside
@alias, create a folder namedobjectProviders. - 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.
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.
- In
src/, create a new foldermod_website. - Inside, create
@routes. - Inside
@routes, create a folder namedproducts.
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.
// 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.
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
- Start your development server (
npm run dev). - 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.