Web endpoints

Modal gives you a few ways to expose functions as web endpoints. You can either turn any Modal function into a webhook with a single line of code, or you can serve an entire ASGI-compatible app (including existing apps written with frameworks such as FastAPI or Flask).

@webhook

The easiest way to create a webhook out of an existing function is to use the @modal.webhook decorator.

import modal

stub = modal.Stub()

@stub.webhook()
def f():
    return "Hello world!"


if __name__ == "__main__":
    stub.serve()

You can run this code as an ephemeral app, which means that it stays up as long as your script runs (until you hit Ctrl-C to stop it). It creates a temporary URL that you can use like any other REST endpoint. You can also deploy your app and create a persistent webhook in the cloud:

More configuration

In addition to the keyword arguments supported by a regular stub.function, webhooks take an optional method argument to set the HTTP method of the REST endpoint (see reference for a full list of supported arguments).

How do webhooks run in the cloud?

Note that webhooks, like anything else in Modal, only runs when it needs to. When you hit the webhook the first time, it will boot up the container, which might take a few seconds. Modal keeps the container alive for a short period (a minute, at most) in case there are subsequent requests. If there are a lot of requests, Modal might create more containers running in parallel.

Under the hood, Modal wraps your function in a FastAPI application, and so functions you write need to follow the same request and response semantics as FastAPI. This also means you can use all of FastAPI’s powerful features, such as Pydantic models for automatic validation, typed query and path parameters, and response types.

More complex example

Here’s everything together, combining Modal’s abilities to run functions in user-defined containers with the expressivity of FastAPI:

from pydantic import BaseModel
from fastapi.responses import HTMLResponse

class Item(BaseModel):
    name: str
    qty: int = 42

image = modal.Image.debian_slim().pip_install(["boto3"])

@stub.webhook(method="POST", image=image)
def f(item: Item):
    import boto3
    # do things with boto3
    return f"<html>Hello, {item.name}!</html>"


if __name__ == "__main__":
    stub.serve()

Serving ASGI and WSGI apps

You can also serve any app written in an ASGI or WSGI compatible web application framework on Modal. For ASGI apps, you can create a function decorated with @modal.asgi that returns a reference to your web app:

from fastapi import FastAPI, Request

import modal

web_app = FastAPI()
stub = modal.Stub()

image = modal.Image.debian_slim().pip_install(["boto3"])

@web_app.post("/foo")
async def foo(request: Request):
    body = await request.json()
    return body


@web_app.get("/bar")
async def bar():
    return "Hello Fast World!"


@stub.asgi(image=image)
def fastapi_app():
    return web_app


if __name__ == "__main__":
    stub.serve()

Now, as before, when you deploy this script as a modal app, you get a URL for your app that you can use:

WSGI

You can serve WSGI apps using the wsgi decorator:

import modal

stub = modal.Stub()
image = modal.Image.debian_slim().pip_install(["flask"])


@stub.wsgi(image=image)
def flask_app():
    from flask import Flask, request

    web_app = Flask(__name__)

    @web_app.get("/")
    def home():
        return "Hello Flask World!"

    @web_app.post("/foo")
    def foo():
        return request.json

    return web_app


if __name__ == "__main__":
    stub.serve()

See Flask’s docs for more information.

Developing with stub.serve()

Modal Stubs come with the handy serve function, that live updates an app when any of the supporting files change. This is particularly useful when working with apps containing webhooks, as any changes made to webhook handlers will show up almost immediately, without requiring a manual restart of the app.