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

class Gateway(ApiGatewayService):
    name = "api"
    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},
        }]
    }

async def main():
    broker = Broker("gateway-1", settings=Settings(transporter="nats://localhost:4222"))
    broker.create_service(Gateway)
    await broker.start()    # starts gateway + uvicorn automatically
    # Gateway is now listening on http://0.0.0.0:3000/api/v1/...

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                        # 348 tests, 93% 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.1.tar.gz (30.1 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.1-py3-none-any.whl (31.5 kB view details)

Uploaded Python 3

File details

Details for the file moleculerpy_web-0.1.1.tar.gz.

File metadata

  • Download URL: moleculerpy_web-0.1.1.tar.gz
  • Upload date:
  • Size: 30.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for moleculerpy_web-0.1.1.tar.gz
Algorithm Hash digest
SHA256 8319bf4edc00e1bd36013d7fa41b27d7a424c5c49e57bcf834faa643a5d0e0da
MD5 d94ad1559854a19df860aa95ac27f07e
BLAKE2b-256 bbd7b772d379dd5f3144818ca18070cb74c3f62f12badd65263bca0db9feb6bf

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for moleculerpy_web-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 4b896a493160582df249ce857a7dde2b92257c77d9ee219df79026e9d8b651cd
MD5 322c0459e8f786ee268ac58023ce8f94
BLAKE2b-256 dff3944857eccf4dc0e31fba2ef62c7d43d211c4163074d3a90a0b146f41fcaf

See more details on using hashes here.

Provenance

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