Skip to main content

HTTP API Gateway for MoleculerPy — maps HTTP requests to Moleculer service actions

Project description

MoleculerPy Web

CI PyPI version Python versions License

HTTP API Gateway for MoleculerPy — maps HTTP requests to Moleculer service actions via Starlette (ASGI).

Python port of moleculer-web.


Features

  • HTTP Gateway — Starlette (ASGI) + uvicorn, deploy via any ASGI server
  • Route Aliases"GET /users/{id}" → "users.get" with :id and {id} syntax
  • REST Shorthand"REST /users" → 6 CRUD routes with only/except filters
  • Middleware Pipeline — Onion model matching Node.js moleculer-web execution order
  • HooksonBeforeCall, onAfterCall, onError (route-level)
  • Authentication — Sets ctx.meta.user from custom auth function
  • Authorization — Raises ForbiddenError on deny
  • CORS — Route-level with origin validation (string/list/wildcard/callable)
  • Rate Limiting — MemoryStore with async reset, X-Rate-Limit-* headers
  • Whitelist/Blacklist — Action access control with fnmatch wildcards + regex
  • Error Mapping — MoleculerError hierarchy → HTTP status codes (400-504)
  • ctx.meta Passthrough$statusCode, $responseHeaders, $responseType, $location
  • Security — Path traversal, SSRF, open redirect protection, body size limit
  • Type-Safe — Full type hints with mypy strict mode

Performance

Benchmarked with Apache Bench (ab), Python 3.12:

Scenario Throughput Latency
Simple GET (mock broker) 11,425 req/sec 4.4ms
Real NATS + 3 services 10,900 req/sec 4.5ms
404 error path 14,302 req/sec 3.5ms

Quick Start

Installation

pip install moleculerpy-web

Basic Gateway

import asyncio
from moleculerpy import Broker
from moleculerpy.settings import Settings
from moleculerpy_web import ApiGatewayService

async def main():
    broker = Broker("gateway-1", settings=Settings(transporter="nats://localhost:4222"))
    # ... register services ...
    await broker.start()

    gateway = ApiGatewayService(broker=broker, settings={
        "port": 3000,
        "path": "/api",
        "routes": [{
            "path": "/v1",
            "aliases": {
                "REST /users": "users",        # 6 CRUD routes
                "GET /health": "health.check",
            },
            "cors": {"origin": "*"},
            "rateLimit": {"window": 60, "limit": 100, "headers": True},
        }]
    })
    gateway._build_routes()
    gateway._app = gateway._create_app()

    import uvicorn
    config = uvicorn.Config(gateway.app, host="0.0.0.0", port=3000)
    server = uvicorn.Server(config)
    await server.serve()

asyncio.run(main())

With Authentication

from moleculerpy_web import ApiGatewayService
from moleculerpy_web.errors import UnauthorizedError, ForbiddenError

async def authenticate(ctx, route, request):
    token = request.headers.get("authorization", "")[7:]  # Bearer <token>
    if not token:
        return None  # Anonymous
    user = await verify_token(token)
    if not user:
        raise UnauthorizedError("Invalid token")
    return user  # Sets ctx.meta.user

async def authorize_admin(ctx, route, request):
    if not ctx.user or ctx.user.get("role") != "admin":
        raise ForbiddenError("Admin required")

gateway = ApiGatewayService(broker=broker, settings={
    "port": 3000,
    "path": "/api",
    "routes": [{
        "path": "/admin",
        "aliases": {"GET /stats": "admin.stats"},
        "authentication": authenticate,
        "authorization": authorize_admin,
    }]
})

Route Configuration

{
    "path": "/v1",                    # Route prefix
    "mappingPolicy": "restrict",      # "restrict" (default) or "all"
    "aliases": {
        "GET /users": "users.list",
        "REST /products": "products", # REST shorthand → 6 CRUD routes
        "REST /orders": {"action": "orders", "only": ["list", "get"]},
    },

    # Middleware pipeline (Node.js execution order)
    "onBeforeCall": async_function,   # Before broker.call()
    "onAfterCall": async_function,    # After broker.call(), can modify data
    "onError": async_function,        # Custom error handler

    # Access control
    "whitelist": ["users.*", "products.*"],  # fnmatch patterns
    "blacklist": ["admin.danger"],

    # Authentication
    "authentication": async_function, # Returns user object or None
    "authorization": async_function,  # Raises on deny

    # CORS
    "cors": {
        "origin": "*",               # String, list, callable, or wildcard
        "methods": ["GET", "POST", "PUT", "DELETE"],
        "credentials": False,
        "maxAge": 3600,
    },

    # Rate limiting
    "rateLimit": {
        "window": 60,                # Seconds
        "limit": 100,                # Max requests per window
        "headers": True,             # X-Rate-Limit-* headers
    },
}

Architecture

HTTP Request (uvicorn)
    ↓
Starlette (ASGI)
    ↓
CORS preflight check
    ↓
AliasResolver → match route
    ↓
Middleware Pipeline (onion model):
    onBeforeCall → Whitelist → Blacklist
    → Auth → Authz → Rate Limit
    → broker.call(action, params, meta)
    → onAfterCall
    ↓
Response: ctx.meta.$statusCode, $responseHeaders, $responseType
    ↓
HTTP Response (JSON / bytes / redirect / 204)

Modules

Module Purpose LOC
service.py ApiGatewayService lifecycle 89
handler.py Request pipeline + response 81
middleware.py RequestContext + compose 97
alias.py Path matching + REST shorthand 70
errors.py HTTP error classes + mapping 67
cors.py CORS headers + origin check 142
ratelimit.py MemoryStore + rate limit 138
access.py Whitelist/blacklist 132
route.py RouteConfig dataclass 77
parsers.py JSON + form body parsing 22
utils.py Path normalization 22

Testing

pip install -e ".[dev]"
pytest                        # 277 tests, 95% coverage
mypy moleculerpy_web/        # 0 errors (strict mode)
ruff check moleculerpy_web/  # 0 errors

Real NATS demo

# Start NATS
docker run -p 4222:4222 nats:2-alpine

# Run demo (3 microservices + gateway)
python examples/demo_real_v2.py

# Run smoke tests (31 tests on real NATS)
python examples/smoke_test_v2.py

Node.js Compatibility

Feature moleculer-web moleculerpy-web
Route aliases
REST shorthand
Path params (:id / {id}) ✅ (both)
Hooks (before/after/error)
Authentication
Authorization
CORS
Rate limiting
Whitelist/blacklist
ctx.meta passthrough
Param merge order body < query < path ✅ Same
Error format ✅ Same
Mapping policy default: all default: restrict (secure)

Contributing

See CONTRIBUTING.md.

Security

See SECURITY.md.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

moleculerpy_web-0.1.0a1.tar.gz (25.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

moleculerpy_web-0.1.0a1-py3-none-any.whl (26.6 kB view details)

Uploaded Python 3

File details

Details for the file moleculerpy_web-0.1.0a1.tar.gz.

File metadata

  • Download URL: moleculerpy_web-0.1.0a1.tar.gz
  • Upload date:
  • Size: 25.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for moleculerpy_web-0.1.0a1.tar.gz
Algorithm Hash digest
SHA256 dafccbbfe65b26f4c95150b42ab8c8dfb0983bf71d66667d4650e2b495cc1942
MD5 7a9fd93b79385b92d835ccb8d9c61b2f
BLAKE2b-256 b07dea4b4e7eaee26b3d7b5148f0652005cf53b10a9b5775e66a2e7c925cf914

See more details on using hashes here.

Provenance

The following attestation bundles were made for moleculerpy_web-0.1.0a1.tar.gz:

Publisher: publish.yml on MoleculerPy/moleculer-web

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file moleculerpy_web-0.1.0a1-py3-none-any.whl.

File metadata

File hashes

Hashes for moleculerpy_web-0.1.0a1-py3-none-any.whl
Algorithm Hash digest
SHA256 98fac36168674576955df20ae8f12c98bb3c996ec29802ddd47ccde4108a7e46
MD5 49c6e46e1b0f11437341a9896cef3e60
BLAKE2b-256 2821aaaef5d750bf224fbd5473f357f8c3473719c05122aee7cbd0d447f9fbeb

See more details on using hashes here.

Provenance

The following attestation bundles were made for moleculerpy_web-0.1.0a1-py3-none-any.whl:

Publisher: publish.yml on MoleculerPy/moleculer-web

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page