This project is a basic ecommerce template that demonstrates a fullstack Harper powered Next.js application.
Harper provides the backend database, API, caching, and a server to run the Next.js frontend on. The same patterns in this code can be used to run any app that requires dynamic data and/or caching.
Almost 2% of global ecommerce sales flow through Harper Systems, with an average p95 latency of 1.12ms across early hints, redirects, and product page lookups for real-world e-commerce applications.
- Install Harper:
npm install -g harper - Clone this repository and change directory in your terminal to the code
- Run
npm i - Run
npm run dev - View the frontend at localhost:9926
- View the data in Harper Studio UI at localhost:9925
The app runs on Next.js 16 with the @harperfast/nextjs 2.x plugin (withHarper in next.config.js) and is deployed prebuilt (prebuilt: true in config.yaml):
- Build:
npm run build(runsnext build --webpack) - Serve the prebuilt app:
npm start(runsharper run .)
Static generation reads product data through Harper, so build on a machine whose local Harper root already has this app's schema and seed data (running npm run dev once takes care of that). The config pins the build to a single worker because each build worker loads the harper module and concurrent workers would contend for the database lock.
Notes for constrained or clustered environments:
- If a multi-worker build contends for the RocksDB lock, build with
next build --webpack --experimental-build-mode compileandNODE_OPTIONS="--max-old-space-size=4096". - If the cluster reports
exports is not defined, setdependencyContainment: falsein the node config (a node setting, not a repo change).
Next.js Incremental Cache entries are stored in Harper instead of in process memory: next.config.js points cacheHandler at cacheHandler.cjs and sets cacheMaxMemorySize: 0. The handler uses three tables in the appCache database (see schema.graphql):
Cache— one row per incremental-cache entry, with the payload v8-serialized into aBlobcolumnCacheRules— path-pattern policy (bypass patterns and group codes), seeded from resources.jsCacheInvalidation— a soft-invalidation log; cache reads, writes, and invalidation failures all degrade to a cache MISS so rendering never depends on the cache
To invalidate cached entries, insert a row into appCache.CacheInvalidation whose id is a cache tag, a CacheRules groupCode (e.g. pdp), or a URL path, with timestamp set to the current epoch milliseconds. Every entry refreshed at or before that timestamp is then served as a MISS and re-rendered on next request. For example, via the Operations API:
{
"operation": "insert",
"database": "appCache",
"table": "CacheInvalidation",
"records": [{ "id": "pdp", "timestamp": 1750000000000 }]
}- Run
cp .env.template .env - Add an
OPENAI_API_KEYto the.envfile - Restart the application
With a key set, product descriptions are personalized to user traits, and product search becomes semantic: queries are embedded and matched against an HNSW vector index built directly into Harper, with no external search service. Without a key, search falls back to a Harper keyword match. Built-in Fabric embeddings are coming, which will remove the external embedding call entirely.
For more information about getting started with Harper and building your Next.js applications, see our getting started guides and documentation.
This template includes the default configuration, which specifies how files are handled in your application.
The schema.graphql is the schema definition. This is the main starting point for defining your database schema, specifying which tables you want and what attributes/fields they should have.
The resources.js provides a template for defining JavaScript resource classes, for customized application logic in your endpoints. This repo comes with sample product data in a json file.