Skip to main content
Version: v5

REST Overview

Added in: v4.2.0

Harper provides a powerful, efficient, and standard-compliant HTTP REST interface for interacting with tables and other resources. The REST interface is the recommended interface for data access, querying, and manipulation over HTTP, providing the best performance and HTTP interoperability with different clients.

How the REST Interface Works

Harper's REST interface exposes database tables and custom resources as RESTful endpoints. Tables are not exported by default; they must be explicitly exported in a schema definition. The name of the exported resource defines the base of the endpoint path, served on the application HTTP server port (default 9926).

For more on defining schemas and exporting resources, see Database / Schema.

Configuration

Enable the REST interface by adding the rest plugin to your application's config.yaml:

rest: true

Options:

rest:
lastModified: true # enables Last-Modified response header support
webSocket: false # disables automatic WebSocket support (enabled by default)

Tables and Their Automatic Endpoints

A table is served over REST only when both of the following are true:

  1. The table is exported in a schema definition with @export.
  2. The rest plugin is enabled in the application's config.yaml (see Configuration).
# schema.graphql
type Product @table @export {
id: Long @primaryKey
name: String
price: Float
}
# config.yaml
graphqlSchema:
files: schema.graphql
rest: true

Neither half is sufficient on its own. Without @export the table has no REST route and callers get 404. Without rest: true the REST handler is never registered for the application, so even an exported table does not respond to HTTP requests.

Components with no configuration file

The one exception is a component directory that has no configuration file at all. Harper falls back to a built-in default for those, and that default enables rest and loads *.graphql from the component root — so a bare directory containing only a schema file does serve its exported tables.

The fallback is all or nothing. As soon as a configuration file exists it is used verbatim, with no merge against the built-in default, so a config.yaml that omits rest turns REST off even though the same directory would have had it with no file present. If you add a configuration file, add rest: true with it.

With both in place, the exported name becomes the base path (Product above, or the name argument of @export) and Harper serves the following endpoints on the application HTTP server port (default 9926). No route definitions, controllers, or handler code are required.

EndpointDescriptionDetails
GET /ProductDescribes the resource — table name, database, and declared attributes — plus an href to the record collection. No trailing slash.GET
GET /Product/The record collection. Append query parameters to search, filter, sort, and page.GET, Querying
GET /Product/{id}A single record by primary key; 404 when no such record exists.GET
GET /Product/{id}.propertyA single property of one record. Only properties declared in the schema.GET
POST /Product/Creates a record with a Harper-assigned primary key and responds 201. Requires the trailing slash — POST /Product returns 404.POST
PUT /Product/{id}Creates or replaces the record at {id} (upsert). The stored record matches the body exactly — properties omitted from the body are removed.PUT
PATCH /Product/{id}Merges the body into the existing record, preserving unspecified properties. The merge is shallow — a nested object in the body replaces the stored one wholesale.PATCH
DELETE /Product/{id}Deletes the record at {id}.DELETE
DELETE /Product/?queryDeletes every record matching the query. With no query parameters, this matches — and deletes — every record in the table.DELETE

Notes on this surface:

  • The trailing slash is significant throughout: /Product addresses the resource itself, /Product/ addresses its collection of records. See URL Structure.
  • HEAD is served exactly as GET with the response body omitted. QUERY is accepted on the collection path (QUERY /Product/) and runs a search taken from the request body rather than the URL.
  • On a successful POST, the new record's primary key is returned in the Location response header. The header carries the bare key value, not a URL — it is not a link to follow.
  • PUT replaces the stored record, with three exceptions that Harper always applies: a @createdTime attribute keeps the original record's value, an @updatedTime attribute is re-stamped with the time of the write, and the primary key is forced to match the {id} in the URL even if the body carries a different one.
  • Enabling rest also enables WebSocket and Server-Sent Events subscriptions on these same resource paths.
  • Every exported resource is included in the generated OpenAPI document.
  • Custom resource classes exported from an application get the same URL structure and method mapping; they implement the methods themselves rather than inheriting the table behavior above. See Resource API.

URL Structure

The REST interface follows a consistent URL structure:

PathDescription
/my-resourceRoot path — returns a description of the resource (e.g., table metadata)
/my-resource/Trailing slash indicates a collection — represents all records; append query parameters to search
/my-resource/record-idA specific record identified by its primary key
/my-resource/record-id/Trailing slash — the collection of records with the given id prefix
/my-resource/record-id/with/multiple/partsRecord id with multiple path segments

Changed in: v4.5.0 — Resources can be defined with nested paths and accessed by exact path without a trailing slash. The id.property dot syntax for accessing properties via URL is only applied to properties declared in a schema.

HTTP Methods

REST operations map to HTTP methods following uniform interface principles:

GET

Retrieve a record or perform a search. Handled by the resource's get() method.

GET /MyTable/123

Returns the record with primary key 123.

GET /MyTable/?name=Harper

Returns records matching name=Harper. See Querying for the full query syntax.

GET /MyTable/123.propertyName

Returns a single property of a record. Only works for properties declared in the schema.

Conditional Requests and Caching

GET responses include an ETag header encoding the record's version/last-modification time. Clients with a cached copy can include If-None-Match on subsequent requests. If the record hasn't changed, Harper returns 304 Not Modified with no body — avoiding serialization and network transfer overhead.

PUT

Create or replace a record with a specified primary key (upsert semantics). Handled by the resource's put(record) method. The stored record will exactly match the submitted body — any properties not included in the body are removed from the previous record.

PUT /MyTable/123
Content-Type: application/json

{ "name": "some data" }

Creates or replaces the record with primary key 123.

POST

Create a new record without specifying a primary key, or trigger a custom action. Handled by the resource's post(data) method. The auto-assigned primary key is returned in the Location response header.

POST /MyTable/
Content-Type: application/json

{ "name": "some data" }

PATCH

Partially update a record, merging only the provided properties (CRDT-style update). Unspecified properties are preserved.

Added in: v4.3.0
PATCH /MyTable/123
Content-Type: application/json

{ "status": "active" }
warning

The merge is shallow (top-level only). Preserving "unspecified properties" applies only to top-level attributes.

If the request body includes a nested object, that entire sub-object is replaced rather than deep-merged. Any omitted nested properties will be dropped.

Example:

  • Existing record: {"settings": {"theme": "light", "notifications": {"email": true}}}
  • PATCH request body: {"settings": {"theme": "dark"}}
  • Resulting record: {"settings": {"theme": "dark"}} (the notifications object is lost)

To update a single nested field, you must either:

  1. Read-modify-write the parent object.
  2. Send the full nested object with the updated values.

Note that dot-path keys (e.g., "settings.theme") are stored literally as keys and are not interpreted as paths.

DELETE

Delete a specific record or all records matching a query.

DELETE /MyTable/123

Deletes the record with primary key 123.

DELETE /MyTable/?status=archived

Deletes all records matching status=archived.

Content Types

Harper supports multiple content types for both request bodies and responses. Use the Content-Type header for request bodies and the Accept header to request a specific response format.

See Content Types for the full list of supported formats and encoding recommendations.

OpenAPI

Added in: v4.3.0

Harper automatically generates an OpenAPI specification for all resources exported via a schema. This endpoint is available at:

GET /openapi

See Also