| name | nockapp-api-design |
| description | Design new API endpoints following the Hoon-Rust-HTTP pattern used in this NockApp. Use when adding endpoints, modifying SNARK structure, extending the API, or implementing new features. |
NockApp API Design Pattern
This skill guides you through adding new API endpoints to the prover NockApp following the established architectural pattern.
The 4-Step Pattern
Every new API endpoint requires changes in 4 places, in this order:
Step 1: Update Hoon Kernel Types
File: prover/hoon/prover.hoon
Add your new operation to the $cause type:
+$ cause
$% [%init ~]
[%submit-snark proof=tape verification-key=tape public-inputs=tape proof-system=tape submitter=tape]
[%get-snark id=@ud]
[%list-snarks ~]
[%delete-snark id=@ud]
[%update-status id=@ud new-status=tape]
:: Add your new operation here
[%your-new-operation field1=@ud field2=tape]
==
Type Guidelines:
@ud: Unsigned decimal (numbers, IDs)tape: String/text data~: Null/unit (no data)(list ...): List of items(map ...): Key-value pairs
Step 2: Implement Handler in ++poke Arm
Add your handler logic inside the ++poke arm:
++ poke
|= [=cause =bowl:cask]
^- [(list effect:cask) _this]
?- -.cause
%init
...
%submit-snark
...
:: Add your handler here
%your-new-operation
=/ field1 field1.cause
=/ field2 field2.cause
:: Validate inputs
?: =(field1 0)
:_ this
[%http-response (error-response "Field1 cannot be zero")]~
:: Update state
=/ new-state ...
:: Return response
:_ this(state new-state)
[%http-response (success-response "Operation completed")]~
==
Common Patterns:
- Access input:
field.cause - Update state:
this(state new-state) - Return with HTTP response:
:_ this [%http-response ...]~ - Error handling: Check conditions with
?:and return error responses
Step 3: Add Rust HTTP Handler
File: prover/src/main.rs
3a. Define Request/Response Structs
#[derive(Deserialize)]
struct YourNewOperationRequest {
field1: u64,
field2: String,
}
#[derive(Serialize)]
struct YourNewOperationResponse {
success: bool,
message: String,
data: Option<YourData>,
}
3b. Implement Handler Function
async fn handle_your_new_operation(
State(kernel): State<Arc<RwLock<Kernel>>>,
Json(payload): Json<YourNewOperationRequest>,
) -> Result<Json<YourNewOperationResponse>, StatusCode> {
// Validate input
if payload.field1 == 0 {
return Err(StatusCode::BAD_REQUEST);
}
// Build noun message for Hoon kernel
let message = format!(
"[%your-new-operation {} '{}']",
payload.field1,
payload.field2
);
// Send to kernel
let mut kernel_guard = kernel.write().await;
let response_noun = kernel_guard.poke(&message)?;
// Parse response (simplified)
let response = YourNewOperationResponse {
success: true,
message: "Operation completed".to_string(),
data: Some(parse_response(&response_noun)),
};
Ok(Json(response))
}
3c. Add Route to Router
In the main() function, add your route:
let app = Router::new()
.route("/api/v1/snark", post(submit_snark))
.route("/api/v1/snark/:id", get(get_snark))
.route("/api/v1/snarks", get(list_snarks))
.route("/api/v1/snark/:id", delete(delete_snark))
// Add your new route
.route("/api/v1/your-endpoint", post(handle_your_new_operation))
.with_state(kernel)
.layer(cors);
Step 4: Update Frontend (if needed)
File: prover/web/prover.js
Add JavaScript to call your new endpoint:
async function callYourNewOperation(field1, field2) {
try {
const response = await fetch('/api/v1/your-endpoint', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
field1: field1,
field2: field2
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('Success:', data);
return data;
} catch (error) {
console.error('Error:', error);
showError(error.message);
}
}
Detailed Examples
For complete working examples, see PATTERNS.md.
Rebuilding After Changes
After modifying the code:
# 1. Recompile Hoon kernel to Nock
hoonc prover/hoon/prover.hoon -o prover/out.jam
# 2. Rebuild Rust binary
cargo build --release
# 3. Restart server
RUST_LOG=debug cargo run
Common Pitfalls
- Type Mismatches: Ensure Hoon types match Rust deserialization
- Noun Formatting: Check noun message format carefully (brackets, spaces)
- State Updates: Remember to return updated state in Hoon with
this(state ...) - Error Handling: Always validate inputs in both Rust and Hoon
- Base64 Data: For binary data, validate base64 encoding in Rust before sending to Hoon
Testing Your New Endpoint
# Test with curl
curl -X POST http://localhost:8080/api/v1/your-endpoint \
-H "Content-Type: application/json" \
-d '{
"field1": 42,
"field2": "test data"
}'
# Check server logs
RUST_LOG=debug cargo run
State Management Best Practices
- Immutable Updates: Always create new state, don't mutate
- Validation First: Validate all inputs before updating state
- Atomic Operations: Keep state updates atomic within a single poke
- ID Generation: Use
next-idpattern for generating unique IDs - Timestamps: Use
now.bowlfrom the bowl for consistent timestamps
Response Format Guidelines
Success Response:
{
"success": true,
"data": {...}
}
Error Response:
{
"error": "Descriptive error message"
}
Performance Considerations
- Async Handlers: All Rust handlers should be async
- Lock Duration: Minimize time holding kernel write lock
- Validation Early: Validate in Rust before sending to Hoon kernel
- Serialization Cost: Be mindful of large noun serialization overhead
Security Checklist
- Input validation in Rust handler
- Input validation in Hoon handler
- No SQL injection risks (using nouns, not SQL)
- Base64 validation for binary data
- Authentication/authorization (if required)
- Rate limiting considerations
- Error messages don't leak sensitive info