Harper is a composable application platform that folds the essential layers of a modern backend (database, cache, pub/sub messaging, and application logic) into a single, highly performant process. By collapsing what would normally be a fleet of disparate services, Harper removes round‑trip serialization overhead, cuts latency, and lets developers ship globally distributed features without boilerplate orchestration.
‍
Why it matters: Fewer moving parts mean fewer failure modes, faster deploys, and less time re‑implementing plumbing you don’t really care about.
‍
Installing Harper
There are three ways to install Harper. Use npm for local prototyping, Docker for containerized deployments, or the offline package when the internet is off‑limits. All methods give you the exact same runtime.
Global install via npm
# Requires Node.js ≥ 22
npm install -g harperdb‍
# Start the runtime
harperdb
The first launch will ask for:
- Destination path – where Harper will store its data
- Admin credentials – username / password
- Hostname & port – defaults to localhost:9925
To verify the instance is alive after installation, visit http://localhost:9925/healthÂ
‍
Run Harper in Docker
# Grab the latest image
docker pull harperdb/harperdb‍
# Fire it up (detached, port‑mapped)
docker run -d -p 9925:9925 harperdb/harperdb
Need persistence? Mount a host volume:
docker run -d \Â Â
-v $PWD/hdb:/home/harperdb/hdb \Â Â
-p 9925:9925 \Â Â
harperdb/harperdb
Check logs with docker logs <container_id>
‍
Secure & clustered variant
docker run -d \Â Â
-v $PWD/hdb:/home/harperdb/hdb \Â Â
-e OPERATIONSAPI_NETWORK_PORT=null \Â Â
-e OPERATIONSAPI_NETWORK_SECUREPORT=9925 \Â Â
-e CLUSTERING_ENABLED=true \Â Â
-e CLUSTERING_USER=cluster_user \Â Â
-e CLUSTERING_PASSWORD=password \Â Â
-e CLUSTERING_NODENAME=hdb1 \Â Â
-p 9925:9925
-p 9926:9926
-p 9932:9932 \Â Â
harperdb/harperdb
Offline installation
If you need to install Harper on a device that doesn't have an Internet connection, you can choose your version and download the npm package and install it directly (you’ll still need Node.js and NPM). Click this link to download and install the package. Once you’ve downloaded the .tgz file, run the following command from the directory where you’ve placed it:
npm install -g harperdb-X.X.X.tgz harperdb install
‍
Building Your First Application
Now that you’ve set up Harper, get ready to build your first application. Let’s spin up a fresh workspace by cloning the official application template and stepping inside it:
git clone https://github.com/HarperDB/application-template my-app
cd my-app
‍
The template includes:
- schema.graphql – your GraphQL‑based 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.
- resources.js – This file provides a template for defining JavaScript resource classes, for customized application logic in your endpoints.
- config.yaml – Your application‑level settings, which specifies how files are handled in your application.
‍
Run harperdb dev .
at any point to boot the app locally. Once you’ve set up the app template, it's time for you to define a GraphQL schema.
Define a GraphQL schema
Harper models tables via GraphQL type definitions decorated with the @table directive. Adding @export automatically surfaces the table through REST and GraphQL endpoints, no extra wiring needed. Let’s build a Book table:
type Book @table @export {Â Â
id: ID @primaryKey Â
title: String!
}
‍
Save the file and restart the app. Harper auto‑migrates the schema and creates the backing table.
Extend the Resource class
Your resource.js file extends the Resource class. Here, you can override its lifecycle methods to add business logic, caching, validation, or transforms. In your resource.js file, drop in:
export class Books extends Resource {
...
// Create a new Book record
async post(data) {
console.log("POST request received");
// Allow both raw JSON strings and objects
if (typeof data === "string") {
try {
data = JSON.parse(data);
} catch (err) {
console.error("Failed to parse payload:", err);
return { error: "Invalid JSON" };
}
}
// Prevent clients from overriding the primary key
if (data && data.id !== undefined) {
delete data.id;
}
try {
const result = await this.table.post(data);
console.log("Record created successfully:", result);
return result;
} catch (error) {
console.error("Error creating record in Book:", error);
return { error: "Internal Server Error", details: error.message };
}
}
// Fetch a Book by ID
async get() {
console.log("GET request received");
this.id = this.getId();
if (this.id) {
const localRecord = await this.table.get(this.id);
return localRecord;
}
}
}
‍
This intercepts every GET /Book/:id
call, fetches the record directly from the in‑process table, and returns it. Because everything runs in one process there’s no network hop or (de)serialization overhead.
‍
Smoke‑test the endpoint
Insert a sample record (using SQL, GraphQL mutation, or POST) then query it:
curl http://localhost:9926/Book/<book-id>
You should see a JSON object matching the schema.
‍
Conclusion
In this article, you installed Harper via npm, Docker, or an offline package, cloned the official application template, defined a GraphQL‑backed Book table, and implemented both POST and GET handlers in resource.js to create and retrieve records, all without leaving a single, unified runtime. From here, you can flesh out the remaining CRUD methods or connect a front‑end to the auto‑generated graphql endpoint. With Harper’s composable platform, shipping globally distributed, real‑time apps is simpler, faster, and more predictable, so go build something amazing!
‍