Resources
Harper's Resource API is the foundation for building custom data access logic and connecting data sources. Resources are JavaScript classes that define how data is accessed, modified, subscribed to, and served over HTTP, MQTT, and WebSocket protocols.
What Is a Resource?
A Resource is a class that provides a unified interface for a set of records or entities. Harper's built-in tables extend the base Resource class, and you can extend either Resource or a table class to implement custom behavior for any data source — internal or external.
The Resource API is designed to mirror REST/HTTP semantics: methods map directly to HTTP verbs (get, put, patch, post, delete), making it straightforward to build API endpoints alongside custom data logic.
Relationship to Other Features
- Database tables extend
Resourceautomatically. You can use tables through the Resource API without writing any custom code. - The REST plugin maps incoming HTTP requests to Resource methods. See REST Overview.
- The MQTT plugin routes publish/subscribe messages to
publishandsubscribeResource methods. See MQTT Overview. - Global APIs (
tables,databases,transaction) provide access to resources from JavaScript code. - The
jsResourceplugin (configured inconfig.yaml) registers a JavaScript file's exported Resource classes as endpoints.
Extending a Table
The most common use case is extending an existing table to add custom logic.
Starting with a table definition in a schema.graphql:
# Omit the `@export` directive
type MyTable @table {
id: Long @primaryKey
# ...
}
@export on the schema type registers Harper's default table resource at /MyTable. When you extend the table in JavaScript and want your subclass to serve those endpoints instead, omit @export from the schema and let the exported JavaScript class own the URL. Leaving @export on the schema while also exporting a subclass with the same name produces conflicting endpoints. When overriding handlers, call super.get/post/... to preserve Harper's default behavior unless you intend to replace it entirely.
For more info on the schema API see
Database / Schema
Then, in a resources.js extend from the tables.MyTable global:
export class MyTable extends tables.MyTable {
static async get(target) {
// get the record from the database
const record = await super.get(target);
// add a computed property before returning
return { ...record, computedField: 'value' };
}
static async post(target, data) {
// custom action on POST
this.create({ ...(await data), status: 'pending' });
}
}
Finally, ensure everything is configured appropriately:
rest: true
graphqlSchema:
files: schema.graphql
jsResource:
files: resources.js
Custom External Data Source
You can also extend the base Resource class directly to implement custom endpoints, or even wrap an external API or service as a custom caching layer:
export class CustomEndpoint extends Resource {
static get(target) {
return {
data: doSomething(),
};
}
}
export class MyExternalData extends Resource {
static async get(target) {
const response = await fetch(`https://api.example.com/${target.id}`);
return response.json();
}
static async put(target, data) {
return fetch(`https://api.example.com/${target.id}`, {
method: 'PUT',
body: JSON.stringify(await data),
});
}
}
// Use as a cache source for a local table
tables.MyCache.sourcedFrom(MyExternalData);
Resources are the true customization point for Harper. This is where the business logic of a Harper application really lives. There is a lot more to this API than these examples show. Ensure you fully review the Resource API documentation, and consider exploring the Learn guides for more information.
Exporting Resources as Endpoints
Resources become HTTP/MQTT endpoints when they are exported. As the examples demonstrated if a Resource extends an existing table, make sure to not have conflicting exports between the schema and the JavaScript implementation. Alternatively, you can register resources programmatically using server.resources.set(). See HTTP API for server API documentation.
The shape of the export controls the resulting URL:
| Export form | URL | Notes |
|---|---|---|
export class Foo extends Resource {} | /Foo/ | The class name becomes the path segment. Path segments are case-sensitive. |
export const Bar = { Foo }; | /Bar/Foo/ | Nest a class under an object to add a path prefix. |
export const bar = { 'foo-baz': Foo }; | /bar/foo-baz/ | Use object keys when you need lowercase, hyphens, or any non-identifier URL. |
server.resources.set('my-path', Foo); | /my-path/ | Programmatic registration; useful when the path is dynamic. |
URL path matching is case-sensitive — /Foo/ and /foo/ are different endpoints.
Pages in This Section
| Page | Description |
|---|---|
| Resource API | Complete reference for instance methods, static methods, the Query object, RequestTarget, and response handling |
| Query Optimization | How Harper executes queries and how to write performant conditions |