Getting Started with Facet
Add server-rendered HTML to your REST API. No new mental model required.
You have a working REST API. Now you need web pages.
The standard options all require adding something significant to your stack: a React frontend with its own build pipeline, or a template engine like Thymeleaf that requires a controller for every new page.
Facet takes a different approach. You add a template file. The endpoint starts serving HTML. Your API keeps working exactly as before.
Quick Start
The fastest way to see Facet in action is to run the complete product catalog example.
1. Clone and Run
# Clone the repository
git clone https://github.com/SoftInstigate/facet.git
cd facet
# Start the product catalog example (published image)
cd examples/product-catalog
docker compose up
# Optional: build a local image (for plugin changes)
# mvn package -DskipTests
# docker compose up --build
Wait for services to start (few seconds), then open http://localhost:8080/shop/products
You'll see a styled product catalog with search, filtering, and pagination. All server-rendered from MongoDB data.
Default login credentials:
- Admin:
admin/secret(full access) - Viewer:
viewer/viewer(read-only)
Docker Hub image: https://hub.docker.com/r/softinstigate/facet
2. Explore the Dual Interface
The same endpoint serves both HTML and JSON:
# Browser request → HTML
curl -u admin:secret -H "Accept: text/html" http://localhost:8080/shop/products
# API request → JSON
curl -u admin:secret -H "Accept: application/json" http://localhost:8080/shop/products
Key concept: Templates are opt-in. No template = JSON API unchanged.
3. Hot Reload Templates
Templates reload automatically when file-loader is enabled and cache is disabled:
- Edit
examples/product-catalog/templates/shop/products/list.html - Refresh your browser
- See changes instantly, no restart needed!
Understanding the Example
Directory Structure
examples/product-catalog/
├── docker-compose.yml # MongoDB + Facet services
├── restheart.yml # Facet configuration
├── init-data.js # Sample product data
├── templates/
│ ├── layout.html # Base layout
│ ├── shop/
│ │ └── products/
│ │ ├── list.html # Product collection view
│ │ └── view.html # Product detail view
│ └── _fragments/
│ └── product-list.html # HTMX fragment
└── static/
└── favicon.ico # Static assets
Template Resolution
When you visit /shop/products (collection), Facet looks for templates in this order:
1. templates/shop/products/list.html ✓ (found!)
2. templates/shop/products/index.html (optional fallback)
3. templates/shop/list.html (parent fallback)
4. templates/shop/index.html (parent fallback)
5. templates/list.html (global collection template)
6. templates/index.html (global fallback)
7. No template → return JSON
Template naming convention:
list.html- Collection views (recommended - clean, no conditional logic)view.html- Document views (recommended - clean, no conditional logic)index.html- Optional fallback (when list/view share template logic)
Template Example
Here's the actual product list template (simplified):
{% extends "layout" %}
{% block main %}
<h1>Products</h1>
<!-- Search form -->
<form method="GET" hx-get="{{ path }}" hx-target="#product-list">
<input type="text" name="search" placeholder="Search products...">
<button type="submit">Search</button>
</form>
<!-- Product list (replaced by HTMX on search) -->
<div id="product-list">
{% include "_fragments/product-list" %}
</div>
{% endblock %}
Fragment Template
HTMX requests target fragments for partial updates:
{# templates/_fragments/product-list.html #}
{% if items is not empty %}
{% for item in items %}
<article>
<h3>{{ item.data.name }}</h3>
<p>{{ item.data.description }}</p>
<span>${{ item.data.price }}</span>
</article>
{% endfor %}
<!-- Pagination -->
{% if totalPages > 1 %}
<nav>
{% for p in range(1, totalPages + 1) %}
<a href="?page={{ p }}"
hx-get="{{ path }}?page={{ p }}"
hx-target="#product-list">{{ p }}</a>
{% endfor %}
</nav>
{% endif %}
{% else %}
<p>No products found.</p>
{% endif %}
Key Concepts
1. Path-Based Templates
Template location mirrors your API structure with explicit action templates:
URL: /shop/products (collection)
→ Template: templates/shop/products/list.html
URL: /shop/products/{id} (document)
→ Template: templates/shop/products/view.html
Why explicit templates? Cleaner code without conditional logic checking request type. File names clearly indicate purpose.
2. Template Context Variables
Facet provides rich context to templates:
| Variable | Description | Example |
|---|---|---|
items |
Array of MongoDB documents | {% for item in items %} |
page |
Current page number | {{ page }} of {{ totalPages }} |
pagesize |
Items per page | Default: 100 |
path |
Full request path | /shop/products |
filter |
MongoDB query filter | {"category":"Electronics"} |
username |
Authenticated user | null if not logged in |
roles |
User's roles | ['admin', 'editor'] |
3. HTMX Integration
Facet automatically detects HTMX requests and returns fragments:
<!-- Full page on direct visit -->
<a href="/shop/products">Products</a>
<!-- Partial update with HTMX -->
<a href="/shop/products"
hx-get="/shop/products"
hx-target="#main-content">Products</a>
How it works:
- HTMX sends
HX-Request: trueheader - Facet detects HTMX request
- Returns fragment template instead of full page
- No JavaScript needed!
4. MongoDB Query Parameters
RESTHeart provides powerful query parameters that work automatically:
# Filter by category
http://localhost:8080/shop/products?filter={"category":"Electronics"}
# Sort by price
http://localhost:8080/shop/products?sort={"price":1}
# Pagination
http://localhost:8080/shop/products?page=2&pagesize=10
# Combine them
http://localhost:8080/shop/products?filter={"price":{"$lt":100}}&sort={"price":1}&page=1
All query parameters are available as template variables.
Creating Your Own Application
1. Start from the Example
# Copy the product catalog example
cd examples
cp -r product-catalog my-app
cd my-app
2. Customize Your Data
Edit init-data.js:
db = db.getSiblingDB('mydb');
db.createCollection('myitems');
db.myitems.insertMany([
{ name: "Item 1", value: 100 },
{ name: "Item 2", value: 200 }
]);
3. Update Templates
Rename template directories to match your structure:
mv templates/shop templates/mydb
mv templates/mydb/products templates/mydb/myitems
Edit templates/mydb/myitems/list.html to display your collection view, and optionally create view.html for document detail views.
4. Update Configuration
In restheart.yml, change the name:
/core:
name: my-app
5. Start Your App
docker compose up
Use docker compose up --build if you are building a local image.
Visit http://localhost:8080/mydb/myitems
Next Steps
- Product Catalog Tutorial — guided walkthrough of every feature
- Developer's Guide — complete reference
- Template Context Reference — all available variables
- RESTHeart Documentation — authentication, WebSockets, GridFS, and more
- GitHub Issues — bugs, questions, feature requests