HTTP API#
In this stage, you’ll build an in-memory key-value store and expose it over a REST API.
Endpoints#
You’ll implement the following endpoints:
- PUT /kv/{key}
Add or update a key-value pair in the store.
PUT /kv/{key}
Parameters:
- key (path, required): The key to store (cannot be empty)
Body: Value to store as plain text (cannot be empty)
Response:
- 200 OK: Key-value pair added or updated successfully
- 400 Bad Request: Return "key cannot be empty\n" or "value cannot be empty\n"- GET /kv/{key}
Retrieve the value associated with the given key.
GET /kv/{key}
Parameters:
- key (path, required): The key to retrieve
Response:
- 200 OK: Return the stored value
- 404 Not Found: Return "key not found\n"- DELETE /kv/{key}
Remove a key-value pair from the store.
DELETE /kv/{key}
Parameters:
- key (path, required): The key to delete
Response:
- 200 OK: Key deleted successfully (or key didn't exist)- DELETE /clear
Remove all key-value pairs from the store.
DELETE /clear
Response:
- 200 OK: All keys cleared successfully- Error Handling
Unsupported HTTP methods on any endpoint should return:
- 405 Method Not Allowed: Return “method not allowed\n”
Your API should handle concurrent requests safely. Consider thread safety when implementing your in-memory store.
Storage#
A simple in-memory map/dictionary is sufficient for storage in this stage. You’ll add persistence in the next stage.
This challenge focuses on distributed systems concerns (consensus, replication, fault tolerance), not database storage engines. The entire dataset should fit in memory. Managing larger-than-memory datasets is out of scope.
Data Model#
Keys and values are stored as simple strings. This keeps the data model straightforward so you can focus on building intuition in distributed systems, not implementing complex data types.
Keys#
Keys must contain only alphanumeric plus :, _, ., and - characters. Examples of valid keys:
user:123cache_entrymetric.latency.p99
Values#
Values are stored as UTF-8 encoded text and can contain:
- Unicode characters like 😊
- Spaces and special symbols
- Long strings (up to reasonable memory limits)
Testing#
Your server must accept --port and --working-dir flags:
$ ./run.sh --port 8001 --working-dir .lsfr/run-20251226-210357
For this stage, you only need --port to start your HTTP server. The --working-dir parameter will become important in the next stage when you add persistence. For now, it’s just where your server’s logs end up (node.log).
You can test your implementation using the lsfr command:
$ lsfr test http-api
Testing http-api: Store and Retrieve Data
✓ PUT Basic Operations
✓ PUT Edge and Error Cases
✓ GET Basic Operations
✓ GET Edge and Error Cases
✓ DELETE Basic Operations
✓ DELETE Edge and Error Cases
✓ CLEAR Operations
✓ Concurrent Operations - Different Keys
✓ Concurrent Operations - Same Key
✓ Check Allowed HTTP Methods
PASSED ✓
Run 'lsfr next' to advance to the next stage.
Debugging#
Your server’s output (stdout/stderr) is captured in node.log inside the working directory. Add your own logging to help debug issues.
When tests fail, lsfr will show you exactly what went wrong:
$ lsfr test
Testing http-api: Store and Retrieve Data
✓ PUT Basic Operations
✓ PUT Edge and Error Cases
✓ GET Basic Operations
✗ GET Edge and Error Cases
GET http://127.0.0.1:42409/kv/nonexistent:key
Expected response: "key not found\n"
Actual response: "\n"
Your server should return 404 Not Found when a key doesn't exist.
Check your key lookup logic and error handling.
FAILED ✗
Read the guide: lsfr.io/kv-store/http-api