Skip to content

Roadmap — What's needed for a large-scale app

Current state of Nori and the pieces needed to support production-grade applications at scale.


What already exists

Area Implemented
HTTP Security headers, CORS, sessions, CSRF, rate limiting (pluggable backends), flash messages, Request ID tracing
Auth Login/logout/register, password hashing PBKDF2, role decorators (login_required, require_role, require_any_role), JWT with @token_required, granular ACL (@require_permission, load_permissions)
Validation Declarative rules: required, min, max, email, numeric, matches, in, file, file_max, file_types. Custom messages
Database Tortoise ORM (MySQL, PostgreSQL, SQLite), soft deletes, tree mixin with recursive CTEs, to_dict() with protected_fields, Aerich migrations
Storage Multi-driver save_upload() with extension, MIME, magic byte content verification, size validation, UUID filenames. Built-in: local. Extensible via register_storage_driver(). Example: services/storage_s3.py
Email Multi-driver send_mail() with Jinja2 templates, MIME multipart. Built-in: smtp, log. Extensible via register_mail_driver(). Example: services/mail_resend.py
Search Multi-driver dispatcher (search(), index_document(), remove_document()). No built-in driver (opt-in). Extensible via register_search_driver(). Example: services/search_meilisearch.py
JWT / API Tokens Manual HMAC-SHA256 (create_token, verify_token), @token_required decorator for APIs, minimum secret length enforcement (32 chars)
Rate Limiting Pluggable backends: MemoryBackend (default) and RedisBackend (sorted sets). Config via THROTTLE_BACKEND
Caching Pluggable backends: MemoryCacheBackend (default) and RedisCacheBackend. cache_get, cache_set, cache_delete, cache_flush, @cache_response decorator
Background Tasks background() (volatile) and push() (persistent) with multi-driver support (Memory, Database). Error logging and retries included
WebSockets WebSocketHandler and JsonWebSocketHandler base classes, /ws/echo demo route
Utilities NoriCollection (17 methods), async pagination, flash messages
Templates Jinja2 with globals (csrf_field, get_flashed_messages), email base template
Config .env with python-dotenv, centralized settings, startup validation
Logging Production-grade nori.* logger with JSON/text formatters, rotating file handler, env-based config (LOG_LEVEL, LOG_FORMAT, LOG_FILE)
Audit Logging AuditLog model, fire-and-forget audit(), get_client_ip(), structured logging with request ID tracing
Deployment Multi-stage Dockerfile, docker-compose.yml (app + MySQL), Gunicorn with UvicornWorker
Error Handling Custom handlers for 404 (JSON + HTML) and 500
Health Check GET /health endpoint with DB connectivity check (200/503)
DB Seeding Convention-based seeder system with db:seed and make:seeder CLI commands
Test Factories make_article(), make_post(), make_category() factory functions with auto-incrementing defaults
Tests pytest + pytest-asyncio, 358 tests (unit + E2E with httpx), asyncio_mode = auto

Recently completed

Feature Description
Job Queue (Persistent) push() dispatcher with Database/Memory drivers. Features: Atomic locking, Exponential backoff, Dead letters (failed_at), Graceful shutdown, and queue:work CLI
Granular permissions (ACL) require_permission('articles.edit') decorator, Permission + Role models with M2M, load_permissions() for session caching
Audit logging AuditLog model + fire-and-forget audit() with structured logging, IP tracking, and request ID tracing
Multi-driver Email send_mail() refactored with driver registry. Built-in: smtp (production), log (development). Custom drivers via register_mail_driver()
Multi-driver Storage save_upload() refactored with driver registry. Built-in: local (disk). Custom drivers via register_storage_driver(). S3 example in services/
Search dispatcher core/search.py with search(), index_document(), remove_document(). No built-in driver — external engines only. Meilisearch example in services/
Security hardening protected_fields on models to prevent data leaks in to_dict(), magic byte verification on uploads (pure Python, no libmagic), JWT secret minimum length (32 chars) enforced at startup
OAuth2 Social Login Google (OpenID Connect + PKCE) and GitHub drivers in services/. Core helpers for state CSRF and PKCE in core/auth/oauth.py. 3-function interface: get_auth_url, handle_callback, get_user_profile
Framework Decoupling Introduced core.registry and core.conf to make the core application-agnostic. Models are registered at startup and settings are accessed via a provider.
Automatic Updates nori.py framework:update command to pull the latest framework core from GitHub, with automatic backups.
Core Hardening Standardized from __future__ import annotations across core. Robust @inject with whitelisted type coercion.

Production hardening — Before going live

These are not new features — they are gaps in existing subsystems that must be addressed before serving real traffic. Ordered by severity.

P0 — Must fix

Gap Problem Fix
~~Memory cache unbounded growth~~ ~~MemoryCacheBackend has no max key limit.~~ Done — LRU eviction with max_keys (default 10,000). Configurable via CACHE_MAX_KEYS. Least-recently-used entries evicted on insert.
Memory backends unsuitable for production Both MemoryCacheBackend and MemoryThrottleBackend lose all state on restart or deploy. Rate limit counters reset, cached data vanishes. With multiple workers (Gunicorn), each process has its own isolated store — rate limits become ineffective. Add a startup warning when DEBUG=False and memory backends are active. Document Redis as the production requirement for cache and rate limiting.

P1 — Fix before real users have accounts

Gap Problem Fix
~~No brute-force protection on login~~ ~~An attacker can attempt passwords against a known email indefinitely.~~ Donecheck_login_allowed(), record_failed_login(), clear_failed_logins() in core/auth/login_guard.py. Per-account lockout with escalating backoff (1m → 5m → 15m → 30m → 1h). Uses cache backend.
Session permissions never invalidate load_permissions() caches the user's roles and permissions in the session. If an admin revokes a role, the user keeps their old permissions until their session expires or they log out. Add a TTL or version check: store a permissions_loaded_at timestamp in the session and re-fetch after a configurable interval (e.g. 5 minutes).
JWT has no revocation mechanism Once issued, a JWT is valid until it expires. A compromised token cannot be invalidated. Implement a lightweight token blacklist (DB or cache-backed) checked on @token_required. Add an optional jti (JWT ID) claim for selective revocation.

What's next — Priority order

~~1. Social Auth (OAuth2)~~ — Done

Implemented in services/oauth_google.py and services/oauth_github.py. Core helpers in core/auth/oauth.py. See Authentication — OAuth2.

~~2.~~ 1. OpenAPI / Swagger

Why it's #2: The most valuable feature Nori lacks for public APIs. Auto-generated docs from route definitions.

Concerns: Pipe-separated validation ('required|email|max:255') doesn't map cleanly to OpenAPI schemas. May require adding optional type metadata to routes.

...