Simple, lightweight Python-based key-value storage server that exposes a small RESTful HTTP API for saving and retrieving project-scoped key/value pairs.
Designed for simplicity and predictable behaviour, perfect for small-to-medium projects, personal projects, internal tools, demos, or anywhere you need a tiny secure persistent or in-memory key/value store without a heavy database.
This project was developed and tested with Python 3.13.3. It may work on earlier versions, but they are not officially supported.
- Lightweight - minimal dependencies and easy to deploy.
- RESTful API - predictable HTTP endpoints for project and store management.
- Multiple project - isolated namespaces for different projects with unique API keys per project.
- Basic CRUD operations for key-value pairs.
- In-memory fast access with optional on-disk persistence per project.
- Small, explicit configuration (
config.json) with apropriate defaults. - Simple authentication via API keys (system-level and per-project).
- Key and value discovery controls for security.
- Metadata support for stored keys (timestamps, size, type).
- Thread-safe operations for concurrent API access.
- Multiple authentication header support (
Authorization,X-API-Key,Api-Key).
- Quickstart
- Configuration
- Running the server
- Terminal logging / CUI
- Ensuring security
- API overview
- Examples
- Client tips
- Troubleshooting
- Privacy, security, and transparency
- Contributing
- License
You can run Simple PyKV either directly using Python or using Docker. We recommend using a WSGI server like Gunicorn or Waitress for production use to ensure better performance and security.
Important: Running the server without proper configuration, security settings, and API key management will expose your server to potential security risks. Please read the Configuration, Running the server and Ensuring security sections carefully before deploying the server publicly.
Depending on if you want to run using Python or Docker, follow the respective instructions below. Editing the configuration file is diffrent for each method, so please read the Configuration section after setup.
Requirements: Python 3.8+.
-
Clone the repo
git clone https://github.com/majdiJ/simple-pykv.git
-
Change to project directory
cd simple-pykv -
Install dependencies
python -m pip install -r requirements.txt
(Optional: create and activate a virtual environment before installing dependencies to avoid conflicts with other Python packages.)
-
Run the server Within the
simple-pykvdirectory, run:For windows, MacOs, or Linux:
waitress-serve --listen=127.0.0.1:23849 main:app
or, for Linux/MacOS with Gunicorn:
gunicorn -w 4 -b 127.0.0.1:23849 main:app
Gunicorn provides better performance and is recommended for production use if using Linux or MacOS. However, if running the server for small projects, performance gains may be minimal.
The server will start using the default configuration.
On first server run, if config.json is missing, a default config file will be created in the current directory (simple-pykv) along with a storage folder for on-disk projects.
(Read the Configuration section for details and information about API key management.)
You can also run Simple PyKV using Docker. Make sure you have Docker installed and running.
-
Clone the repo
git clone https://github.com/majdiJ/simple-pykv.git
-
Change to project directory
cd simple-pykv -
Build the Docker image
docker build -t simple-pykv:latest . -
Run with a host bind mount (easy to inspect on host)
mkdir -p ./pykv_data
docker run -d --name simple-pykv \ -p 23849:23849 \ -v "$(pwd)/pykv_data:/data" \ simple-pykv:latestAfter the first container start you will find ./pykv_data/config.json and ./pykv_data/storage_data/ (the app creates them on first run). The app generates API keys and save_api_key_to_config is false, the plaintext keys will be printed to container logs once — check them with docker logs.
-
Using docker-compose (recommended)
# bring up in background docker-compose up -d --build # view logs docker-compose logs -f
The server is configured by a single JSON file (config.json).
To edit configuration file, edit:
config.jsonif running using Python directly, orpykv_data/config.jsonif running using Docker with the above bind mount.
On first server run, if config.json or pykv_data/config.json is missing, a default config file will be created in the current directory (simple-pykv) along with a storage folder for on-disk projects.
If authentication is enabled, API keys will be generated and shown in the console. If save_api_key_to_config is true, the plaintext API keys will also be saved in config.json. Otherwise, they will only be shown in the console once, store them safely!
Below is a sanitised example.
{
"version": 1,
"server_port": 23849,
"server_host": "127.0.0.1",
"system": {
"storage": {
"persistent_file_path": "storage_data"
},
"authentication": {
"enabled": true,
"save_api_key_to_config": true,
"api_key": "If `save_api_key_to_config` is true, the plaintext API key will be here otherwise it will be null",
"api_key_hash": "If `authentication.enabled` is true, the hashed API key will be here otherwise it will be null"
},
"security": {
"project_discoverable": true
}
},
"projects": [
{
"id": "first_project",
"storage": {
"on_disk": true
},
"authentication": {
"enabled": true,
"save_api_key_to_config": true,
"api_key": "If `save_api_key_to_config` is true, the plaintext API key will be here otherwise it will be null",
"api_key_hash": "If `authentication.enabled` is true, the hashed API key will be here otherwise it will be null"
},
"security": {
"keys_and_values_discoverable": true
},
"api_key": "test"
}
]
}DO NOT COMMIT REAL API KEYS TO PUBLIC REPOSITORIES!
Click to expand to view configuration options
Here are the main configuration options:
-
version- What version of the config schema is being used (currently1). -
server_port- Port number to bind the HTTP server to (default23849). -
server_host- Host/IP to bind the HTTP server to (default127.0.0.1). -
system.storage.persistent_file_path- Directory path for on-disk project storage files. Recommended to keep asstorage_data. -
system.authentication.enabled- Whether a system/global API key is required for administrative routes (create/delete projects, list projects, etc). Recommended to keep astrue. -
system.authentication.save_api_key_to_config- Whether to save the raw system API key in plaintext inconfig.json. By default, this isfalsefor security. Iffalse, the generated API key will be shown once in the console on first run and never shown again (store it safely!). Iftrue, the API key will be saved inconfig.jsonundersystem.authentication.api_key. -
system.authentication.api_key- The system/global API key in plaintext (ifsave_api_key_to_configistrue), otherwisenull. You can set to your own API key, but not recommended for security. Instead, let the server generate a secure random key on first run. -
system.authentication.api_key_hash- The hashed system/global API key (ifauthentication.enabledistrue), otherwisenull. Used for verifying incoming API keys. -
system.security.project_discoverable- WhetherGET /api/v1/projectscan list all projects that exist. Iffalse, that endpoint returns403 Forbidden. Useful for hiding project existence. -
projects- An array of project configurations. Each project has:-
id- Unique project identifier string (Must be alphanumeric with underscores or hyphens, and unique id). -
storage.on_disk- Whether this project's key/value store is persisted to disk (true) or kept in memory only (false) (All projects will be kept in memory while the server is running). -
authentication.enabled- Whether this project requires an API key for access. Recommended to keep astruefor security. -
authentication.save_api_key_to_config- Whether to save the raw project API key in plaintext inconfig.json. By default, this isfalsefor security. Iffalse, the generated API key will be shown once in the console on first run and never shown again (store it safely!). Iftrue, the API key will be saved inconfig.jsonunder the project'sauthentication.api_key. -
authentication.api_key- The project API key in plaintext (ifsave_api_key_to_configistrue), otherwisenull. You can set to your own API key, but not recommended for security. Instead, let the server generate a secure random key on first run. -
authentication.api_key_hash- The hashed project API key (ifauthentication.enabledistrue), otherwisenull. Used for verifying incoming API keys. -
security.keys_and_values_discoverable- WhetherGET /api/v1/projects/<project_id>/storecan list all keys in this project's store. Iffalse, that endpoint returns403 Forbidden. Useful for hiding key existence.
-
system.authentication.enabledandprojects[].authentication.enabledcontrol whether API keys are required for system-level and project-level routes respectively. If disabled, no API key is needed for those routes. Recommended to keep authentication enabled for both for security, unless you wish to have an unsecured server/project.save_api_key_to_configoptions control whether plaintext API keys are saved inconfig.json. For security, it is recommended to keep these asfalseso that keys are not stored on disk.- API keys are always hashed and stored in
api_key_hashfields for verification, regardless ofsave_api_key_to_configsettings. - To update the config, stop the server, edit
config.jsonorpykv_data/config.json, then restart the server.
-
Run the server
You can run the server using a WSGI server like Gunicorn or Waitress for production use (See the Quickstart section for detailed instructions). You can also run in development mode using Flask's built-in server, but this is NOT recommended for production due to security and performance reasons.
a. using Waitress (cross-platform):
waitress-serve --listen=127.0.0.1:23849 main:app
or in devlelopment mode (NOT recommended for production):
b. using Flask's built-in server:
python main.py
-
The server will start and use the configuration in config.json (if it exists) or create a default config if missing
-
The server listens on the configured host and port (default
127.0.0.1:23849). -
You can interact with the API using HTTP clients like curl, Postman, or custom scripts.
You can stop the server with CTRL+C in the terminal.
The server includes a simple Console User Interface (CUI) for logging important events and messages to the terminal.
Common tyoes if logging messages:
- INFO - General informational messages about server status and operations.
- ERROR - Error messages indicating problems or failures.
- SUCCESS - Messages indicating successful operations.
- VERBOSE - Detailed debug messages (shown only if verbose mode is enabled in config).
- WARNING - Warning messages indicating potential issues.
Running the server as is and without additional security measures for public deployment is NOT recommended. Here are some tips to enhance security:
- Enable authentication - Ensure
system.authentication.enabledandprojects[].authentication.enabledare set totrueinconfig.jsonto require API keys for access (By default, authentication is enabled but double-check). - Use strong API keys - Let the server generate secure random API keys on first run. Avoid setting weak or guessable API keys manually.
- Restrict network access - Use firewalls or reverse proxies to restrict access to trusted clients only.
- Use HTTPS - Deploy behind a reverse proxy (e.g., Nginx) with SSL/TLS to encrypt traffic.
- Regularly rotate API keys - Regenerate API keys periodically and update clients accordingly.
- Monitor logs - Regularly check server logs for suspicious activity.
- Keep software updated - Regularly update Python and dependencies to patch security vulnerabilities.
Root paths:
- Health/status:
/status - API prefix:
/api/v1(project management and store routes)
Authorization: Bearer <api-key>X-API-Key: <api-key>Api-Key: <api-key>
Most endpoints return an envelope like:
{
"System": <object|null>,
"data": <object|null>,
"message": "<string>",
"status_code": <int>
}Exception: GET /api/v1/projects/<project_id>/store/<key>/value returns the raw stored value with no envelope (JSON or plain text).
200, 201, 400, 401, 403, 404, 500.
See
routes.mdfor the full, detailed API reference (example requests/responses and troubleshooting). The README contains a compact summary for common usage.
System / project management (system/global API key required):
GET /status— health & system info.GET /api/v1/projects— list projects (requiresproject_discoverable: true).POST /api/v1/projects— create a new project. JSON body must include{ "id": "<project_id>" }. Do not includeapi_key/api_key_hash.POST /api/v1/projects/<project_id>/config/regenerate-api-key— regenerate project API key (plaintext may be returned once).DELETE /api/v1/projects/<project_id>— delete a project and its on-disk store file.
Project-level (project API key required when project auth enabled):
GET /api/v1/projects/<project_id>— get scrubbed project configuration.PUT /api/v1/projects/<project_id>— update project settings (rejectsidandauthentication).
Store operations (project API key required when auth enabled):
GET /api/v1/projects/<project_id>/store— list keys with metadata (requireskeys_and_values_discoverable = true).PUT /api/v1/projects/<project_id>/store/<key>— create/update a key (JSON or raw text body).GET /api/v1/projects/<project_id>/store/<key>— retrieve key with metadata.GET /api/v1/projects/<project_id>/store/<key>/value— retrieve raw value only (no envelope).DELETE /api/v1/projects/<project_id>/store/<key>— delete a key.DELETE /api/v1/projects/<project_id>/store— clear a project's store.
curl -H "Authorization: Bearer <PROJECT_API_KEY>" \
http://127.0.0.1:23849/api/v1/projects/first_project/store/example_key6/valueimport requests
project = 'first_project'
key = 'example_key6'
api = '<PROJECT_API_KEY>'
url = f'http://127.0.0.1:23849/api/v1/projects/{project}/store/{key}/value'
resp = requests.get(url, headers={'Authorization': f'Bearer {api}'}, timeout=5)
if resp.status_code == 200:
try:
value = resp.json()
except ValueError:
value = resp.text
print('value:', value)
else:
print('error', resp.status_code, resp.text)(Requires system/global API key if system authentication is enabled.)
curl -X POST http://127.0.0.1:23849/api/v1/projects \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <SYSTEM_API_KEY>" \
-d '{"id":"my_project"}'If save_api_key_to_config is false for project a plaintext API key may be returned once in data.new_api_key. Securely store this key as it will not be shown again.
- Always set
Content-Type: application/jsonwhen sending JSON; the server attempts JSON parse first and preserves JSON values. - Use the project API key for project-level calls and the system/global API key for administrative actions.
- Be prepared to handle two kinds of responses: the envelope and raw-value responses (for
GET .../store/<key>/value).
- 401 Unauthorised: check you are using the correct API key (system vs project) and header form.
- 403 Forbidden on
GET /api/v1/projects: checksystem.security.project_discoverable. - PUT /api/v1/projects/ rejects
idandauthenticationfields - update other fields only. - GET .../store//value returns raw content (no envelope); handle JSON/text accordingly.
- For
500errors consult server logs for internal messages.
Privacy and security are fundamental rights. Simple PyKV is designed with security best practices in mind, but it is your responsibility to ensure proper configuration and deployment.
Simple PyKV does not collect, store, or transmit any personal data by default, and all data is stored locally on your machine. However, you must ensure that your deployment is secure, especially if exposing the server to the internet.
You are responsible for managing API keys, securing network access, and ensuring data privacy. Always follow best practices for server deployment.
Please concider the following:
- Secure configurations - Enable authentication, use strong API keys, and limit the data saved to ur machine.
- Reverse proxies and HTTPS - Use reverse proxies with SSL/TLS to encrypt traffic.
- Tunneling and VPNs - Consider using VPNs or secure tunnels for remote access.
- Do not store sensitive data - Avoid storing highly sensitive data unless you have strong security measures in place.
- Regular audits - Regularly review server logs, configurations, updates for potential security issues.
Contributions are welcome! Suggested process:
- Open an issue describing the change or bug.
- Submit a PR against
mainwith tests (where applicable) and a short description of the change.
And or contact me directly at contact@majdij.com or on majdij.com/#contact.
This project is licensed under the Apache License 2.0. See the LICENSE file for details. Click here for the full license text.
Note: Trademarks and logos are not included in the license.