Unixy micro-framework: file routing, SSE/WS, jobs, bus, flat files.
Project description
dbbasic-web
A Unix-philosophy micro-framework that restores the simplicity of CGI while delivering modern performance (~4000 req/s vs 100 req/s for traditional CGI).
Philosophy
Modern web frameworks moved away from Unix principles and lost key capabilities:
- ❌ Message passing → bolted-on queue systems
- ❌ Cron jobs → separate background job libraries
- ❌ Filesystem routing → giant route tables
- ❌ Flat files → everything forced into SQL
- ❌ Streams → poor SSE/WebSocket support
dbbasic-web restores these capabilities using:
- ✅ Filesystem routing (like CGI, but async)
- ✅ TSV-based storage, queues, and streams (no Redis/SQL required)
- ✅ First-class WebSockets and Server-Sent Events
- ✅ Background jobs with dbbasic-queue
- ✅ Message bus with dbbasic-pipe
- ✅ Flat files alongside databases
Features
1. Filesystem Routing
No route tables. No decorators. Just files:
api/hello.py → handles /hello
api/tasks.py → handles /tasks (collection)
api/tasks/[id].py → handles /tasks/123 (item with pattern matching)
api/users/[username].py → handles /users/alice
templates/about.html → renders /about
public/css/app.css → serves /css/app.css
Use method-specific functions (GET, POST, PUT, DELETE) or generic handle() function.
2. Hierarchical API Handlers
Each handler can manage its own sub-routes:
# api/user.py
def handle(request):
path_parts = request['path_parts'] # ['user', '123', 'edit']
if len(path_parts) == 1:
return list_users()
elif len(path_parts) == 2:
return get_user(path_parts[1])
elif len(path_parts) == 3:
return edit_user(path_parts[1], path_parts[2])
3. TSV-Based Storage (No SQL Required)
from dbbasic_web.storage import write_text, read_text
# Flat files with automatic directory creation
write_text("notes/2024-01-15.txt", "Meeting notes...")
content = read_text("notes/2024-01-15.txt")
4. Background Jobs (No Celery/Redis)
from dbbasic_web.jobs import enqueue
# Jobs stored in TSV files
enqueue("write_flatfile", relpath="exports/data.csv", content=csv_data)
Run worker:
python manage.py worker
5. Message Bus (No Kafka/Redis)
from dbbasic_web.bus import EventBus
bus = EventBus()
await bus.publish("notifications", {"user_id": 123, "event": "login"})
async for message in bus.consume("notifications", group="processors", consumer="worker-1"):
print(message['data'])
6. WebSockets & Server-Sent Events
Built-in, no configuration needed:
WebSocket:
const ws = new WebSocket('ws://localhost:8000/ws/room-name');
ws.send(JSON.stringify({message: 'Hello!'}));
SSE:
const events = new EventSource('/sse/counter');
events.addEventListener('tick', (e) => console.log(e.data));
Quick Start
Installation
pip install dbbasic-web
Or install from source:
git clone https://github.com/askrobots/dbbasic-web.git
cd dbbasic-web
pip install -e .
Run the Server
python manage.py serve
Visit: http://localhost:8000
Project Structure
dbbasic-web/
├── dbbasic_web/
│ ├── api/ # API handlers (filesystem routing)
│ │ ├── hello.py # /hello
│ │ └── user.py # /user, /user/*, /user/*/posts
│ ├── templates/ # Jinja2 templates
│ │ ├── base.html
│ │ └── index.html
│ ├── public/ # Static files
│ │ ├── css/app.css
│ │ └── js/app.js
│ ├── asgi.py # ASGI application
│ ├── router.py # Filesystem router
│ ├── jobs.py # Background jobs
│ ├── bus.py # Message bus
│ ├── storage.py # Flat-file storage
│ ├── websocket.py # WebSocket hub
│ └── sse.py # Server-Sent Events
├── _data/ # Auto-created data directory
│ ├── jobs.tsv # Job queue
│ └── streams/ # Message bus streams
├── manage.py # CLI
└── pyproject.toml
Creating API Endpoints
Simple Endpoint
# api/status.py
import json
from dbbasic_web.responses import json as json_response
def handle(request):
return json_response(json.dumps({"status": "ok"}))
Access: GET /status
REST Resource with Sub-Routes (Classic Pattern)
# api/posts.py
def handle(request):
parts = request['path_parts']
method = request['method']
# /posts
if len(parts) == 1:
if method == 'GET':
return list_posts()
elif method == 'POST':
return create_post(request)
# /posts/123
elif len(parts) == 2:
post_id = parts[1]
if method == 'GET':
return get_post(post_id)
elif method == 'PUT':
return update_post(post_id, request)
# /posts/123/comments
elif len(parts) == 3 and parts[2] == 'comments':
return get_comments(parts[1])
Pattern Routing with Method Functions (New in 0.1.7)
Split routes by file structure and HTTP method for cleaner code:
Collection endpoints:
# api/tasks.py
def GET(request):
"""List all tasks"""
return json(json.dumps({"tasks": [...]}))
def POST(request):
"""Create new task"""
data = json.loads(request.body)
return json(json.dumps({"task": data}), status=201)
Item endpoints with pattern matching:
# api/tasks/[id].py - matches /tasks/123
def GET(request, id):
"""Get single task - id is automatically extracted"""
task = tasks.query_one(id=id)
return json(json.dumps({"task": task}))
def PUT(request, id):
"""Update task"""
data = json.loads(request.body)
tasks.update({'id': id}, data)
return json(json.dumps({"success": True}))
def DELETE(request, id):
"""Delete task"""
tasks.delete(id=id)
return json(json.dumps({"success": True}))
Pattern routing works with any parameter name:
api/users/[username].py → /users/alice → GET(request, username)
api/posts/[slug].py → /posts/hello → GET(request, slug)
api/@[handle].py → /@alice → GET(request, handle)
Benefits:
- File structure mirrors URL structure (
ls api/tasks/shows available routes) - HTTP method = function name (no nested IFs)
- Parameters extracted from URL and passed as function arguments
- Backward compatible (old
handle()pattern still works)
Performance
| Implementation | Requests/sec |
|---|---|
| Traditional CGI | ~100 |
| dbbasic-web | ~4000 |
Achieved by combining:
- Async I/O (ASGI/uvicorn)
- Filesystem routing (no regex matching)
- Direct module imports (no middleware chains)
- TSV files instead of database round-trips
Dependencies
Minimal and purposeful:
uvicorn- ASGI serverjinja2- Templatesdbbasic-tsv- TSV databasedbbasic-queue- Job queuedbbasic-pipe- Message streamsdbbasic-sessions- Authenticationwebsockets- WebSocket support
No Redis. No Celery. No PostgreSQL required.
Commands
# Run development server
python manage.py serve
# Run background job worker
python manage.py worker
# Interactive shell
python manage.py shell
Examples
See complete working examples at dbbasic-examples:
- blog - Simple blog with posts
- microblog - Twitter-like microblog with follows
- api - REST API with Bearer token auth, demonstrating pattern routing
Philosophy in Action
This framework proves that:
- Simplicity scales - Filesystem routing is faster than route tables
- Flat files work - TSV beats Redis for many use cases
- Unix was right - Pipes, files, and processes are enough
- Less is more - 8000 lines total vs 200k+ for Django
License
MIT
Contributing
Built for clarity and hackability. Every module is under 500 lines. Read the source.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dbbasic_web-0.1.11.tar.gz.
File metadata
- Download URL: dbbasic_web-0.1.11.tar.gz
- Upload date:
- Size: 28.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e361a2a52fa5c958a42da6d12a64fbd462c02e1cd2f2a0537c93e01fb0b0cb90
|
|
| MD5 |
9bf73f39b95ac5a6dd9e70e79bf01954
|
|
| BLAKE2b-256 |
17b304dc857befc2ce175741b3e2f05b6013efd26e31099cb3e252f686c939a2
|
File details
Details for the file dbbasic_web-0.1.11-py3-none-any.whl.
File metadata
- Download URL: dbbasic_web-0.1.11-py3-none-any.whl
- Upload date:
- Size: 23.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bb567b7e4d6d2fc2c3c684a4b5a1b839a87a42cf53e80d46fc64f85438124112
|
|
| MD5 |
f2ad89629c76024d73799f726320e2d7
|
|
| BLAKE2b-256 |
971141609b9fbad432f04b6be4a22fd2b18331472fa62527c61838d296eaf219
|