A high-performance Rust backend implementation for the Hulunote outline note-taking application, replacing the original Clojure backend.
- Overview
- Features
- Tech Stack
- Project Structure
- Prerequisites
- Installation
- Configuration
- Running the Server
- API Reference
- Registration Code System
- Frontend Setup
- Deployment
- Troubleshooting
Hulunote is an outline-based note-taking application that organizes your thoughts hierarchically. This Rust backend provides:
- User authentication with JWT tokens
- Note database (notebook) management
- Note CRUD operations
- Hierarchical outline navigation nodes
- Registration code-based user management with expiration
- High Performance: Built with Rust for minimal memory footprint and maximum throughput
- Zero GC Pauses: No garbage collection delays
- Fast Startup: Quick cold start times
- API Compatible: Drop-in replacement for the original Clojure backend
- Registration System: Code-based registration with configurable account expiration
| Component | Technology |
|---|---|
| Web Framework | Axum 0.7 |
| Database | PostgreSQL + SQLx |
| Authentication | JWT (jsonwebtoken) |
| Password Hashing | bcrypt |
| Async Runtime | Tokio |
| Serialization | Serde |
hulunote-rust/
├── Cargo.toml # Rust dependencies
├── Cargo.lock # Dependency lock file
├── .env # Environment configuration (create from template)
├── env-production-template.txt # Production environment template
├── init.sql # Database initialization script
├── run.sh # Development run script
│
├── src/
│ ├── main.rs # Application entry point
│ ├── config.rs # Configuration management
│ ├── db.rs # Database connection pool
│ ├── error.rs # Error handling
│ ├── middleware.rs # JWT authentication middleware
│ ├── models.rs # Data models and structures
│ ├── routes.rs # Route configuration
│ └── handlers/ # API request handlers
│ ├── mod.rs
│ ├── auth.rs # Login/registration handlers
│ ├── database.rs # Note database operations
│ ├── note.rs # Note CRUD operations
│ └── nav.rs # Outline node operations
│
├── migrations/ # Database migrations
│ └── 001_add_registration_codes.sql
│
├── scripts/
│ ├── deploy.sh # Deployment script
│ ├── generate_registration_code.sh # Registration code generator
│ └── monitor.sh # Monitoring script
│
├── resources/
│ └── public/ # Static files (frontend assets)
│
└── nginx-deploy/ # Nginx configuration files
Before you begin, ensure you have installed:
- Rust (1.70 or later) - Install Rust
- PostgreSQL (12 or later) - Install PostgreSQL
- Git - Install Git
git clone https://github.com/your-repo/hulunote-rust.git
cd hulunote-rust# Create the database
createdb -U postgres hulunote_open
# Initialize the schema
psql -U postgres -d hulunote_open -f init.sql
# Run migrations (if any)
psql -U postgres -d hulunote_open -f migrations/001_add_registration_codes.sql# Copy the environment template
cp env-production-template.txt .env
# Edit the configuration
vim .env# Debug build
cargo build
# Release build (optimized)
cargo build --releaseCreate a .env file in the project root with the following variables:
# Database connection (required)
DATABASE_URL=postgres://postgres:your_password@localhost:5432/hulunote_open
# JWT configuration
JWT_SECRET=your-super-secret-key-change-this-in-production
JWT_EXPIRY_HOURS=720 # Token validity in hours (default: 30 days)
# Server configuration
PORT=6689 # Server port
# Logging level
RUST_LOG=hulunote_server=info,tower_http=info| Variable | Description | Default | Required |
|---|---|---|---|
DATABASE_URL |
PostgreSQL connection string | - | Yes |
JWT_SECRET |
Secret key for JWT signing | hulunote-secret-key |
Yes (in production) |
JWT_EXPIRY_HOURS |
JWT token expiration in hours | 720 |
No |
PORT |
Server listening port | 6689 |
No |
RUST_LOG |
Logging configuration | hulunote_server=debug |
No |
# Using the run script (recommended)
./run.sh
# Or directly with cargo
cargo run# Build release binary
cargo build --release
# Run the server
./target/release/hulunote-serverThe server will start at http://localhost:6689 by default.
All API endpoints return JSON responses with kebab-case field names for compatibility with the ClojureScript frontend.
POST /login/web-login
Content-Type: application/json
{
"email": "user@example.com",
"password": "password123"
}POST /login/web-signup
Content-Type: application/json
{
"email": "user@example.com",
"password": "password123",
"username": "optional_username",
"registration_code": "FA8E-AF6E-4578-9347"
}POST /login/send-ack-msg
Content-Type: application/json
{
"email": "user@example.com"
}All authenticated endpoints require the JWT token in the Authorization header:
Authorization: Bearer <jwt_token>
POST /hulunote/new-database
Content-Type: application/json
{
"name": "My Notebook",
"description": "My personal notes"
}POST /hulunote/get-database-list
Content-Type: application/jsonPOST /hulunote/update-database
Content-Type: application/json
{
"database-id": "uuid",
"name": "Updated Name"
}POST /hulunote/new-note
Content-Type: application/json
{
"database-id": "uuid",
"title": "My Note",
"note-id": "optional-client-generated-uuid",
"root-nav-id": "optional-client-generated-uuid"
}Notes:
note-idandroot-nav-idare optional. If omitted, backend generates UUIDs.- If provided, both must be valid UUIDs and must not already exist.
note-idandroot-nav-idmust be different.
POST /hulunote/get-note-list
Content-Type: application/json
{
"database-id": "uuid",
"page": 1,
"page_size": 20
}POST /hulunote/get-all-note-list
Content-Type: application/json
{
"database-id": "uuid"
}POST /hulunote/update-hulunote-note
Content-Type: application/json
{
"note_id": "uuid",
"title": "Updated Title",
"content": "Updated content"
}POST /hulunote/create-or-update-nav
Content-Type: application/json
{
"note_id": "uuid",
"nav_id": "uuid",
"content": "Node content",
"parent_id": "parent_uuid_or_null"
}POST /hulunote/get-note-navs
Content-Type: application/json
{
"note_id": "uuid"
}POST /hulunote/get-all-nav-by-page
Content-Type: application/json
{
"database-id": "uuid",
"page": 1,
"page_size": 100
}Retrieve all navigation items under a specific database.
POST /hulunote/get-all-navs
Content-Type: application/json
{
"database-id": "uuid"
}Instead of email verification, Hulunote uses registration codes that control account expiration.
# Generate a 6-month code
./scripts/generate_registration_code.sh 6months
# Generate a 1-year code
./scripts/generate_registration_code.sh 1year
# Generate a 2-year code
./scripts/generate_registration_code.sh 2years
# Generate a custom validity code
./scripts/generate_registration_code.sh custom
# Then enter the number of days when promptedRegistration codes follow the format: XXXX-XXXX-XXXX-XXXX
- 16 hexadecimal characters (uppercase)
- Separated by hyphens
- Example:
FA8E-AF6E-4578-9347
- One-time use: Each code can only be used once
- Expiration control: Determines account validity period
- Usage tracking: Records which user used the code and when
-- View all registration codes
SELECT code, validity_days, is_used, used_by_account_id, used_at, created_at
FROM registration_codes
ORDER BY created_at DESC;
-- View unused codes
SELECT code, validity_days, created_at
FROM registration_codes
WHERE is_used = false;
-- View expired accounts
SELECT id, username, mail, expires_at
FROM accounts
WHERE expires_at IS NOT NULL AND expires_at < NOW();
-- Extend an account's expiration
UPDATE accounts
SET expires_at = NOW() + INTERVAL '365 days'
WHERE id = <user_id>;The frontend is built with ClojureScript and needs to be compiled separately.
# Navigate to the original Hulunote project
cd ../hulunote
# Build the frontend
npx shadow-cljs release hulunote
# Copy to Rust project
cp -r resources/public/hulunote ../hulunote-rust/resources/public/The run.sh script will automatically attempt to copy frontend files if they don't exist.
./scripts/deploy.sh- Build the release binary:
cargo build --release- Copy files to server:
scp target/release/hulunote-server user@server:/opt/hulunote/
scp .env user@server:/opt/hulunote/
scp -r resources user@server:/opt/hulunote/- Set up systemd service (optional):
[Unit]
Description=Hulunote Server
After=network.target postgresql.service
[Service]
Type=simple
User=hulunote
WorkingDirectory=/opt/hulunote
ExecStart=/opt/hulunote/hulunote-server
Restart=always
Environment=RUST_LOG=hulunote_server=info
[Install]
WantedBy=multi-user.targetSample Nginx reverse proxy configuration is available in the nginx-deploy/ directory.
Error: error connecting to database
Solutions:
- Verify PostgreSQL is running:
pg_isready - Check
DATABASE_URLin.env - Ensure the database exists:
psql -l | grep hulunote_open
Error: Invalid registration code
Solutions:
- Verify the code format (case-sensitive)
- Check if the code exists in the database
- Confirm the code hasn't been used
Error: Account has expired
Solutions:
- Generate a new registration code
- Or extend the account manually:
UPDATE accounts SET expires_at = NOW() + INTERVAL '365 days' WHERE id = <user_id>;Error: Invalid token or Token expired
Solutions:
- Re-login to get a new token
- Check
JWT_SECRETmatches between deployments - Verify
JWT_EXPIRY_HOURSsetting
Error: 404 on static files
Solutions:
- Run
./run.shto auto-copy frontend files - Manually copy:
cp -r ../hulunote/resources/public/hulunote resources/public/ - Verify files exist in
resources/public/hulunote/
This Rust backend maintains full compatibility with:
- ✅ Same PostgreSQL database schema
- ✅ Same ClojureScript frontend
- ✅ Same JSON response format (kebab-case fields)
- ✅ Same API endpoint paths
- ✅ Same JWT authentication flow
Compared to the Clojure backend:
| Metric | Improvement |
|---|---|
| Memory Usage | 5-10x lower |
| Startup Time | 10-20x faster |
| Request Latency | 2-5x lower |
| Concurrent Connections | Higher capacity |
| GC Pauses | Zero |
This project is licensed under the MIT License - see the LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.