ESC

Type to search...

Quick Start

Build a working EVOID API in 5 minutes.

Prerequisites

  • Python 3.12 or higher
  • uv (recommended) or pip

Step 1: Install & Create

uv add evoid
evo init my-api
cd my-api

This creates:

my-api/
  pyproject.toml
  shared/
  services/
    gateway/        # ← your entry point (port 8000)
      evoid.toml
      main.py

Step 2: Create a Service

Each service handles one domain. Services subscribe to Intents via the message bus.

evo service new users

Edit services/users/main.py:

from evoid import subscribe
from evoid.core import Context

async def handle_get_user(ctx: Context) -> dict:
    user_id = ctx.intent.metadata.get("user_id", 0)
    return {"id": user_id, "name": f"User {user_id}"}

async def handle_create_user(ctx: Context) -> dict:
    name = ctx.intent.metadata.get("name", "unknown")
    return {"status": "created", "name": name}

subscribe("get_user", handle_get_user)
subscribe("create_user", handle_create_user)

The service doesn’t know about HTTP, URLs, or the gateway. It only knows Intents.

Step 3: Connect Gateway to Service

The gateway receives HTTP and converts it to Intents. Edit services/gateway/main.py:

from evoid.web.route import Service, get, post, run
from evoid import Intent, Level, publish

app = Service("gateway")

@get("/health")
async def health() -> dict:
    return {"status": "healthy"}

@get("/users/{user_id}")
async def get_user(user_id: int) -> dict:
    result = await publish(Intent(
        name="get_user",
        level=Level.STANDARD,
        metadata={"user_id": user_id},
    ))
    return result[0] if result else {"error": "no handler"}

@post("/users")
async def create_user(name: str) -> dict:
    result = await publish(Intent(
        name="create_user",
        level=Level.STANDARD,
        metadata={"name": name},
    ))
    return result[0] if result else {"error": "no handler"}

The gateway converts HTTP to Intent. The service handles the Intent. Neither knows about the other.

Step 4: Run Everything

evo run

This starts both services. You should see:

Starting gateway on http://0.0.0.0:8000
Starting users on http://0.0.0.0:8001

Step 5: Test It

# Get user — gateway routes to users service via message bus
curl http://localhost:8000/users/123
# {"id": 123, "name": "User 123"}

# Create user
curl -X POST http://localhost:8000/users?name=Ali
# {"status": "created", "name": "Ali"}
1. **Gateway received HTTP** — `GET /users/123` hit the gateway on port 8000
2. **Gateway created Intent** — Converted HTTP to `Intent(name="get_user", metadata={"user_id": 123})`
3. **Message bus routed** — Intent went to the users service (subscribed to "get_user")
4. **Service handled Intent** — `handle_get_user()` ran, returned `{"id": 123, "name": "User 123"}`
5. **Gateway returned response** — Result sent back as HTTP JSON

The gateway doesn't know what "get_user" does. The users service doesn't know about HTTP. They only share Intent names. That's IOP — data declares intent, the system routes it.

Adding Protection Levels

Change the protection level per route:

@get("/public/data", level="ephemeral")
async def public_data() -> dict:
    return {"data": "cache me"}

@get("/users/{id}", level="standard")
async def get_user(id: int) -> dict:
    return {"id": id}

@post("/payments", level="critical")
async def process_payment(amount: float) -> dict:
    return {"status": "paid"}

Each level maps to a different pipeline — ephemeral gets fast validation only, critical gets full audit and protection.

Next

Build a real project step by step — Tutorial: Your First Intent.