Click Below to Get the Code

Browse, clone, and build from real-world templates powered by Harper.
Tutorial
GitHub Logo

Getting Started with Harper’s Resource API

Learn how to build a full-stack Todo app using Harper’s Resource API, including schema-first data modeling, automatic REST API generation, custom backend logic, and seamless frontend integration for high-performance application development.
Tutorial

Getting Started with Harper’s Resource API

Austin Akers
Head of Developer Relations
at Harper
March 4, 2026
Austin Akers
Head of Developer Relations
at Harper
March 4, 2026
Austin Akers
Head of Developer Relations
at Harper
March 4, 2026
March 4, 2026
Learn how to build a full-stack Todo app using Harper’s Resource API, including schema-first data modeling, automatic REST API generation, custom backend logic, and seamless frontend integration for high-performance application development.
Austin Akers
Head of Developer Relations

Harper simplifies full-stack development by combining data modeling, API generation, and runtime execution into a single platform.

In this tutorial, we’ll build a simple Todo application while exploring how Harper’s Resource API allows you to model data and extend REST endpoints with custom logic.

By the end of this guide, you’ll understand how to:

  • Install and configure Harper locally
  • Run a Vite application inside Harper
  • Define a schema using a schema-first approach
  • Deploy a table
  • Extend that table using a custom Resource Class
  • Connect your frontend to Resource-backed REST endpoints

Overview

In this article, we will:

  1. Install and set up Harper locally
  2. Configure a Vite application to run inside Harper
  3. Define and deploy a GraphQL schema
  4. Create a custom Resource Class
  5. Connect our frontend to Harper’s REST endpoints
  6. Add backend validation logic

Install and Set Up Harper

Harper is installed via the harperdb CLI package.

Open your terminal and run:

npm install -g harperdb

Once installation completes, start Harper:

harper

The first time you run harper, you’ll be prompted to configure:

  • Destination – Where Harper will be installed on your machine
  • Username – Used to log into Harper Studio
  • Password – Used to log into Harper Studio
  • Configuration options

After completing setup, Harper will start locally.

Access Harper Studio

While Harper is running, navigate to:

http://localhost:9925/

Log in using the credentials you created during setup.

In Harper Studio, you’ll see:

  • Applications – Manage applications
  • Databases – Manage databases, tables, and records
  • APIs – View and test generated REST endpoints
  • Status – Monitor system metrics
  • Logs – View instance logs
  • Config – Manage roles and users

Application Setup

Before integrating Harper, clone the Todo example app:

git clone https://github.com/HarperFast/harper-todo-example

Then:

  1. Open the harper-todo-example/start/ folder in your editor
  2. Run:

npm install
npm run dev

  1. Navigate to:

http://localhost:5173/

The application runs — but:

  • It is not running inside Harper
  • It does not yet use Harper for CRUD operations

Let’s fix that.

Configuring the Application to Run Inside Harper

1. Install the Harper Vite Plugin

npm install -D @harperfast/vite-plugin

2. Update config.yaml

Add the plugin:

'@harperfast/vite-plugin':
 package: '@harperfast/vite-plugin'

3. Update package.json

Replace the dev script with:

"dev": "harper run ."

Restart the app:

npm run dev

Now navigate to:

http://localhost:9926/

Your application is now running inside Harper.

What Did We Just Do?

By running inside Harper, your application now benefits from:

  • Integrated database access
  • Automatic REST API generation
  • In-memory caching
  • Low-latency backend execution

Defining Our Table (Schema-First)

Harper uses a schema-first approach with GraphQL.

Open schema.graphql and add:

type TodoList @table {
 id: ID @primaryKey
 status: String @index
 description: String
}

This defines:

  • A table named TodoList
  • A primary key id
  • Indexed status
  • A description field

Deploy the Schema

Run:

harper deploy

In Harper Studio:

  • Navigate to Databases
  • Open the TodoList table

If no database exists, Harper automatically creates a default database named data.

Adding a Record (Testing)

In Harper Studio:

  1. Click + Add New Record(s)
  2. Replace the object with:

{
 "status": "active",
 "description": "finish todo app"
}

  1. Click Save Changes

Your first record is now stored.

Understanding Auto-Generated REST Endpoints

When you define a table, Harper automatically generates REST endpoints for it.

You can view and test them in:

Harper Studio → APIs

These endpoints allow standard CRUD operations.

However, what if we want to:

  • Validate input
  • Modify data before saving
  • Add business logic

That’s where the Resource Class comes in.

What Is the Resource Class?

The Resource Class extends a table and allows you to:

  • Intercept REST operations
  • Modify or validate data
  • Add custom logic
  • Restrict scope to a specific table

Instead of using the base Resource class (which can access all tables), we extend a specific table for better scope control.

Creating Our First Resource Class

Open resources.js.

By default, it looks like:

import { tables } from 'harperdb';

export class MyCustomResource extends tables.TableName {
 static loadAsInstance = false;
}

Update for TodoList

First, reference the table:

import { tables } from 'harperdb';

const TodoListTable = tables.TodoList;

Now create the Resource:

export class TodoListResource extends TodoListTable {
 static loadAsInstance = false;
}

This automatically creates a REST endpoint at:

/TodoListResource/

Fetching Todos

Open src/main.ts and locate fetchTodos():

todos = await fetch(`/TodoListResource/`)
 .then(res => res.json());

This fetches all records from the TodoList table via our Resource endpoint.

Updating Todo Status

To update a record, we use a PUT request:

await fetch(`/TodoListResource/${id}`, {
 method: 'PUT',
 headers: {
   'Content-Type': 'application/json',
 },
 body: JSON.stringify({
   description: todos.find(todo => todo.id === id)?.description,
   status: todos.find(todo => todo.id === id)?.status === "active"
     ? "completed"
     : "active",
 }),
});

Updating Todo Description

await fetch(`/TodoListResource/${id}`, {
 method: 'PUT',
 headers: {
   'Content-Type': 'application/json',
 },
 body: JSON.stringify({
   description: newText,
   status: todos.find(todo => todo.id === id)?.status,
 }),
});

Adding Backend Validation with the Resource Class

Now let’s trim the description before saving it.

Update resources.js:

export class TodoListResource extends TodoListTable {
 static loadAsInstance = false;

 put(target, taskItemData) {
   return super.put(target, {
     id: taskItemData.id,
     description: taskItemData.description.trim(),
     status: taskItemData.status
   });
 }

 post(target, taskItemData) {
   return super.post(target, {
     description: taskItemData.description.trim(),
     status: taskItemData.status
   });
 }
}

Now:

  • Descriptions are trimmed automatically
  • Validation logic lives on the backend
  • Frontend remains clean

Adding a New Todo

await fetch(`/TodoListResource/`, {
 method: 'POST',
 headers: {
   'Content-Type': 'application/json',
 },
 body: JSON.stringify({
   description: todoText,
   status: "active",
 }),
});

We do not provide an id — Harper automatically generates one.

Deleting a Todo

await fetch(`/TodoListResource/${id}`, {
 method: 'DELETE',
});

Conclusion

In this tutorial, we built a full-stack Todo application using Harper’s Resource API.

We demonstrated how to:

  • Run a frontend application inside Harper
  • Define a schema using a schema-first approach
  • Automatically generate REST endpoints
  • Extend those endpoints using a custom Resource Class
  • Add backend validation logic
  • Perform full CRUD operations

Harper enables a streamlined development experience by combining:

  • Data modeling
  • API generation
  • Runtime execution
  • Backend extensibility

—all within a single platform.

To explore more, visit:

https://www.harper.fast/

Harper simplifies full-stack development by combining data modeling, API generation, and runtime execution into a single platform.

In this tutorial, we’ll build a simple Todo application while exploring how Harper’s Resource API allows you to model data and extend REST endpoints with custom logic.

By the end of this guide, you’ll understand how to:

  • Install and configure Harper locally
  • Run a Vite application inside Harper
  • Define a schema using a schema-first approach
  • Deploy a table
  • Extend that table using a custom Resource Class
  • Connect your frontend to Resource-backed REST endpoints

Overview

In this article, we will:

  1. Install and set up Harper locally
  2. Configure a Vite application to run inside Harper
  3. Define and deploy a GraphQL schema
  4. Create a custom Resource Class
  5. Connect our frontend to Harper’s REST endpoints
  6. Add backend validation logic

Install and Set Up Harper

Harper is installed via the harperdb CLI package.

Open your terminal and run:

npm install -g harperdb

Once installation completes, start Harper:

harper

The first time you run harper, you’ll be prompted to configure:

  • Destination – Where Harper will be installed on your machine
  • Username – Used to log into Harper Studio
  • Password – Used to log into Harper Studio
  • Configuration options

After completing setup, Harper will start locally.

Access Harper Studio

While Harper is running, navigate to:

http://localhost:9925/

Log in using the credentials you created during setup.

In Harper Studio, you’ll see:

  • Applications – Manage applications
  • Databases – Manage databases, tables, and records
  • APIs – View and test generated REST endpoints
  • Status – Monitor system metrics
  • Logs – View instance logs
  • Config – Manage roles and users

Application Setup

Before integrating Harper, clone the Todo example app:

git clone https://github.com/HarperFast/harper-todo-example

Then:

  1. Open the harper-todo-example/start/ folder in your editor
  2. Run:

npm install
npm run dev

  1. Navigate to:

http://localhost:5173/

The application runs — but:

  • It is not running inside Harper
  • It does not yet use Harper for CRUD operations

Let’s fix that.

Configuring the Application to Run Inside Harper

1. Install the Harper Vite Plugin

npm install -D @harperfast/vite-plugin

2. Update config.yaml

Add the plugin:

'@harperfast/vite-plugin':
 package: '@harperfast/vite-plugin'

3. Update package.json

Replace the dev script with:

"dev": "harper run ."

Restart the app:

npm run dev

Now navigate to:

http://localhost:9926/

Your application is now running inside Harper.

What Did We Just Do?

By running inside Harper, your application now benefits from:

  • Integrated database access
  • Automatic REST API generation
  • In-memory caching
  • Low-latency backend execution

Defining Our Table (Schema-First)

Harper uses a schema-first approach with GraphQL.

Open schema.graphql and add:

type TodoList @table {
 id: ID @primaryKey
 status: String @index
 description: String
}

This defines:

  • A table named TodoList
  • A primary key id
  • Indexed status
  • A description field

Deploy the Schema

Run:

harper deploy

In Harper Studio:

  • Navigate to Databases
  • Open the TodoList table

If no database exists, Harper automatically creates a default database named data.

Adding a Record (Testing)

In Harper Studio:

  1. Click + Add New Record(s)
  2. Replace the object with:

{
 "status": "active",
 "description": "finish todo app"
}

  1. Click Save Changes

Your first record is now stored.

Understanding Auto-Generated REST Endpoints

When you define a table, Harper automatically generates REST endpoints for it.

You can view and test them in:

Harper Studio → APIs

These endpoints allow standard CRUD operations.

However, what if we want to:

  • Validate input
  • Modify data before saving
  • Add business logic

That’s where the Resource Class comes in.

What Is the Resource Class?

The Resource Class extends a table and allows you to:

  • Intercept REST operations
  • Modify or validate data
  • Add custom logic
  • Restrict scope to a specific table

Instead of using the base Resource class (which can access all tables), we extend a specific table for better scope control.

Creating Our First Resource Class

Open resources.js.

By default, it looks like:

import { tables } from 'harperdb';

export class MyCustomResource extends tables.TableName {
 static loadAsInstance = false;
}

Update for TodoList

First, reference the table:

import { tables } from 'harperdb';

const TodoListTable = tables.TodoList;

Now create the Resource:

export class TodoListResource extends TodoListTable {
 static loadAsInstance = false;
}

This automatically creates a REST endpoint at:

/TodoListResource/

Fetching Todos

Open src/main.ts and locate fetchTodos():

todos = await fetch(`/TodoListResource/`)
 .then(res => res.json());

This fetches all records from the TodoList table via our Resource endpoint.

Updating Todo Status

To update a record, we use a PUT request:

await fetch(`/TodoListResource/${id}`, {
 method: 'PUT',
 headers: {
   'Content-Type': 'application/json',
 },
 body: JSON.stringify({
   description: todos.find(todo => todo.id === id)?.description,
   status: todos.find(todo => todo.id === id)?.status === "active"
     ? "completed"
     : "active",
 }),
});

Updating Todo Description

await fetch(`/TodoListResource/${id}`, {
 method: 'PUT',
 headers: {
   'Content-Type': 'application/json',
 },
 body: JSON.stringify({
   description: newText,
   status: todos.find(todo => todo.id === id)?.status,
 }),
});

Adding Backend Validation with the Resource Class

Now let’s trim the description before saving it.

Update resources.js:

export class TodoListResource extends TodoListTable {
 static loadAsInstance = false;

 put(target, taskItemData) {
   return super.put(target, {
     id: taskItemData.id,
     description: taskItemData.description.trim(),
     status: taskItemData.status
   });
 }

 post(target, taskItemData) {
   return super.post(target, {
     description: taskItemData.description.trim(),
     status: taskItemData.status
   });
 }
}

Now:

  • Descriptions are trimmed automatically
  • Validation logic lives on the backend
  • Frontend remains clean

Adding a New Todo

await fetch(`/TodoListResource/`, {
 method: 'POST',
 headers: {
   'Content-Type': 'application/json',
 },
 body: JSON.stringify({
   description: todoText,
   status: "active",
 }),
});

We do not provide an id — Harper automatically generates one.

Deleting a Todo

await fetch(`/TodoListResource/${id}`, {
 method: 'DELETE',
});

Conclusion

In this tutorial, we built a full-stack Todo application using Harper’s Resource API.

We demonstrated how to:

  • Run a frontend application inside Harper
  • Define a schema using a schema-first approach
  • Automatically generate REST endpoints
  • Extend those endpoints using a custom Resource Class
  • Add backend validation logic
  • Perform full CRUD operations

Harper enables a streamlined development experience by combining:

  • Data modeling
  • API generation
  • Runtime execution
  • Backend extensibility

—all within a single platform.

To explore more, visit:

https://www.harper.fast/

Learn how to build a full-stack Todo app using Harper’s Resource API, including schema-first data modeling, automatic REST API generation, custom backend logic, and seamless frontend integration for high-performance application development.

Download

White arrow pointing right
Learn how to build a full-stack Todo app using Harper’s Resource API, including schema-first data modeling, automatic REST API generation, custom backend logic, and seamless frontend integration for high-performance application development.

Download

White arrow pointing right
Learn how to build a full-stack Todo app using Harper’s Resource API, including schema-first data modeling, automatic REST API generation, custom backend logic, and seamless frontend integration for high-performance application development.

Download

White arrow pointing right

Explore Recent Resources

Blog
GitHub Logo

Agentic Engineering Needs an Opinion: Why Scale Starts with Architecture

AI coding works in a sandbox because the environment is trivially narrow. Real systems have history, constraints, and blast radius. Coding agents make sound decisions only when the architecture is explicit and shared. Opinion isn't a constraint on agentic engineering, it's what makes it possible at scale.
Select*
Blog
AI coding works in a sandbox because the environment is trivially narrow. Real systems have history, constraints, and blast radius. Coding agents make sound decisions only when the architecture is explicit and shared. Opinion isn't a constraint on agentic engineering, it's what makes it possible at scale.
A smiling man with a beard and salt-and-pepper hair stands outdoors with arms crossed, wearing a white button-down shirt.
Stephen Goldberg
CEO & Co-Founder
Blog

Agentic Engineering Needs an Opinion: Why Scale Starts with Architecture

AI coding works in a sandbox because the environment is trivially narrow. Real systems have history, constraints, and blast radius. Coding agents make sound decisions only when the architecture is explicit and shared. Opinion isn't a constraint on agentic engineering, it's what makes it possible at scale.
Stephen Goldberg
Jun 2026
Blog

Agentic Engineering Needs an Opinion: Why Scale Starts with Architecture

AI coding works in a sandbox because the environment is trivially narrow. Real systems have history, constraints, and blast radius. Coding agents make sound decisions only when the architecture is explicit and shared. Opinion isn't a constraint on agentic engineering, it's what makes it possible at scale.
Stephen Goldberg
Blog

Agentic Engineering Needs an Opinion: Why Scale Starts with Architecture

AI coding works in a sandbox because the environment is trivially narrow. Real systems have history, constraints, and blast radius. Coding agents make sound decisions only when the architecture is explicit and shared. Opinion isn't a constraint on agentic engineering, it's what makes it possible at scale.
Stephen Goldberg
Blog
GitHub Logo

Building a Cozy Sandbox Game on Harper

A nature-restoration game with six biomes, 150 animals, and a real food web — built with a single Harper component as the entire backend. One YAML file wires the database, API, content seeder, and static host. The same binary ships offline on itch.io.
Shell
Blog
A nature-restoration game with six biomes, 150 animals, and a real food web — built with a single Harper component as the entire backend. One YAML file wires the database, API, content seeder, and static host. The same binary ships offline on itch.io.
Person with long wavy brown hair wearing a bright pink shirt with a teal trim, smiling outdoors in soft sunlight with blurred trees in the background.
Bailey Dunning
Forward Deployed Engineer
Blog

Building a Cozy Sandbox Game on Harper

A nature-restoration game with six biomes, 150 animals, and a real food web — built with a single Harper component as the entire backend. One YAML file wires the database, API, content seeder, and static host. The same binary ships offline on itch.io.
Bailey Dunning
Jun 2026
Blog

Building a Cozy Sandbox Game on Harper

A nature-restoration game with six biomes, 150 animals, and a real food web — built with a single Harper component as the entire backend. One YAML file wires the database, API, content seeder, and static host. The same binary ships offline on itch.io.
Bailey Dunning
Blog

Building a Cozy Sandbox Game on Harper

A nature-restoration game with six biomes, 150 animals, and a real food web — built with a single Harper component as the entire backend. One YAML file wires the database, API, content seeder, and static host. The same binary ships offline on itch.io.
Bailey Dunning
Blog
GitHub Logo

Your Website was Built for Humans. AI Needs Something Cleaner.

The web spent a decade optimizing for browsers. JavaScript-heavy rendering, dynamic CMS templates, and client-side hydration made pages beautiful and machines blind. AI answer engines retrieve, parse, and cite content directly. If your best content is trapped behind a render cycle, a cleaner source wins.
A.I.
Blog
The web spent a decade optimizing for browsers. JavaScript-heavy rendering, dynamic CMS templates, and client-side hydration made pages beautiful and machines blind. AI answer engines retrieve, parse, and cite content directly. If your best content is trapped behind a render cycle, a cleaner source wins.
Person with short dark hair and moustache, wearing a colorful plaid shirt, smiling outdoors in a forested mountain landscape.
Aleks Haugom
Senior Manager of GTM
Blog

Your Website was Built for Humans. AI Needs Something Cleaner.

The web spent a decade optimizing for browsers. JavaScript-heavy rendering, dynamic CMS templates, and client-side hydration made pages beautiful and machines blind. AI answer engines retrieve, parse, and cite content directly. If your best content is trapped behind a render cycle, a cleaner source wins.
Aleks Haugom
Jun 2026
Blog

Your Website was Built for Humans. AI Needs Something Cleaner.

The web spent a decade optimizing for browsers. JavaScript-heavy rendering, dynamic CMS templates, and client-side hydration made pages beautiful and machines blind. AI answer engines retrieve, parse, and cite content directly. If your best content is trapped behind a render cycle, a cleaner source wins.
Aleks Haugom
Blog

Your Website was Built for Humans. AI Needs Something Cleaner.

The web spent a decade optimizing for browsers. JavaScript-heavy rendering, dynamic CMS templates, and client-side hydration made pages beautiful and machines blind. AI answer engines retrieve, parse, and cite content directly. If your best content is trapped behind a render cycle, a cleaner source wins.
Aleks Haugom
Tutorial
GitHub Logo

Harper's AI Stack: Models API, Agent Loop, and Built-in Agent

Harper 5.1 ships three layered AI capabilities: a provider-agnostic Models API supporting OpenAI, Anthropic, Bedrock, and Ollama; a built-in agentic loop via toolMode: 'auto' with hard budget controls; and an opt-in Harper Agent component. Switching providers is a config change, not a code change.
A.I.
Tutorial
Harper 5.1 ships three layered AI capabilities: a provider-agnostic Models API supporting OpenAI, Anthropic, Bedrock, and Ollama; a built-in agentic loop via toolMode: 'auto' with hard budget controls; and an opt-in Harper Agent component. Switching providers is a config change, not a code change.
Person with very short blonde hair wearing a light gray button‑up shirt, standing with arms crossed and smiling outdoors with foliage behind.
Kris Zyp
SVP of Engineering
Tutorial

Harper's AI Stack: Models API, Agent Loop, and Built-in Agent

Harper 5.1 ships three layered AI capabilities: a provider-agnostic Models API supporting OpenAI, Anthropic, Bedrock, and Ollama; a built-in agentic loop via toolMode: 'auto' with hard budget controls; and an opt-in Harper Agent component. Switching providers is a config change, not a code change.
Kris Zyp
Jun 2026
Tutorial

Harper's AI Stack: Models API, Agent Loop, and Built-in Agent

Harper 5.1 ships three layered AI capabilities: a provider-agnostic Models API supporting OpenAI, Anthropic, Bedrock, and Ollama; a built-in agentic loop via toolMode: 'auto' with hard budget controls; and an opt-in Harper Agent component. Switching providers is a config change, not a code change.
Kris Zyp
Tutorial

Harper's AI Stack: Models API, Agent Loop, and Built-in Agent

Harper 5.1 ships three layered AI capabilities: a provider-agnostic Models API supporting OpenAI, Anthropic, Bedrock, and Ollama; a built-in agentic loop via toolMode: 'auto' with hard budget controls; and an opt-in Harper Agent component. Switching providers is a config change, not a code change.
Kris Zyp