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
  • Service InheritanceApiGatewayService(Service) with full broker integration
  • Internal Actionsapi.listAliases, api.addRoute, api.removeRoute
  • Auto-aliases$services.changed event + action rest annotations
  • Streaming — Async/sync generators → StreamingResponse
  • File Upload — Multipart/form-data with filename sanitization
  • Static Files — Starlette StaticFiles mount via settings.assets
  • ETag + 304 — Conditional GET with If-None-Match support
  • Security — Path traversal, SSRF, open redirect, CRLF injection, body size limits
  • 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
Service inheritance
Internal actions (listAliases)
Auto-aliases ($services.changed)
Streaming responses
File upload (multipart)
Static file serving
ETag / conditional GET
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.0b1.tar.gz (29.8 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.0b1-py3-none-any.whl (31.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: moleculerpy_web-0.1.0b1.tar.gz
  • Upload date:
  • Size: 29.8 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.0b1.tar.gz
Algorithm Hash digest
SHA256 9c4bcf19277dd1d5f946e4611e04cec18d5fe9fab9a9ff5647ec066490ed290d
MD5 89730c7c26fd9bfbe5e313f66071652e
BLAKE2b-256 5f38387434e9188fed6aa0fdac93ae8a438d8eb8f389661125f53b4d73c3c6ec

See more details on using hashes here.

Provenance

The following attestation bundles were made for moleculerpy_web-0.1.0b1.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.0b1-py3-none-any.whl.

File metadata

File hashes

Hashes for moleculerpy_web-0.1.0b1-py3-none-any.whl
Algorithm Hash digest
SHA256 cb25ea55b2dee65008483bc08c8fa039d9ec106af60ff30e58ea9ed5a591690d
MD5 47cc99ae37749df2c9484efbb462de13
BLAKE2b-256 52815f46f124216b1107e9815e5fc3369a90024946992f0f75a901382dd5c70a

See more details on using hashes here.

Provenance

The following attestation bundles were made for moleculerpy_web-0.1.0b1-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