Web Framework¶
Incan includes a web framework that compiles to Axum, giving you Flask/FastAPI-like syntax with native Rust performance.
Quick Start¶
No-install fallback
If you did not run make install, you can still run the incan binary directly:
- from the repository root:
./target/release/incan ...
- or via an absolute path (from anywhere):
/absolute/path/to/incan run path/to/file.incn
from web import App, route, Response, Json
@derive(Serialize)
model Greeting:
message: str
@route("/")
async def index() -> Response:
return Response.html("<h1>Hello from Incan!</h1>")
@route("/api/greet/{name}")
async def greet(name: str) -> Json[Greeting]:
return Json(Greeting(message=f"Hello, {name}!"))
def main() -> None:
app = App()
app.run(port=8080)
Build and run:
incan build examples/web/hello_web.incn
./target/incan/hello_web/target/release/hello_web
Note: the first build may download Rust crates via Cargo (can take minutes) and requires internet access.
Routes¶
Define routes using the @route decorator:
@route("/path")
async def handler() -> Response:
...
@route("/api/resource", methods=[GET, POST])
async def resource_handler() -> Response:
...
Path Parameters¶
Use {name} syntax for path parameters:
@route("/users/{id}")
async def get_user(id: int) -> Json[User]:
user = find_user(id)?
return Json(user)
@route("/posts/{year}/{month}")
async def get_posts(year: int, month: int) -> Json[list[Post]]:
return Json(fetch_posts(year, month))
HTTP Methods¶
Specify allowed methods with the methods parameter:
from web import GET, POST, PUT, DELETE
@route("/items", methods=[GET])
async def list_items() -> Json[list[Item]]:
...
@route("/items", methods=[POST])
async def create_item(body: Json[CreateItem]) -> Json[Item]:
...
@route("/items/{id}", methods=[PUT, DELETE])
async def modify_item(id: int) -> Response:
...
Responses¶
JSON Responses¶
Use Json[T] for JSON responses. The inner type must have @derive(Serialize):
@derive(Serialize)
model User:
id: int
name: str
email: str
@route("/api/user/{id}")
async def get_user(id: int) -> Json[User]:
user = User(id=id, name="Alice", email="alice@example.com")
return Json(user)
HTML Responses¶
Return HTML with Response.html():
@route("/")
async def index() -> Response:
return Response.html("<h1>Welcome!</h1>")
Status Codes¶
Use Response methods for different status codes:
@route("/health")
async def health() -> Response:
return Response.ok() # 200
@route("/created")
async def created() -> Response:
return Response.created() # 201
@route("/empty")
async def empty() -> Response:
return Response.no_content() # 204
@route("/error")
async def error() -> Response:
return Response.bad_request("Invalid input") # 400
@route("/missing")
async def missing() -> Response:
return Response.not_found("Resource not found") # 404
@route("/server-error")
async def server_error() -> Response:
return Response.internal_error("Something went wrong") # 500
Request Data¶
Extracting Path Parameters¶
Path parameters are automatically extracted into function arguments:
@route("/users/{user_id}/posts/{post_id}")
async def get_post(user_id: int, post_id: int) -> Json[Post]:
...
Query Parameters¶
Use Query[T] for query string parameters:
@derive(Deserialize)
model SearchParams:
q: str
limit: int = 10
@route("/search")
async def search(params: Query[SearchParams]) -> Json[list[Result]]:
results = do_search(params.q, params.limit)
return Json(results)
JSON Body¶
Use Json[T] as a parameter for JSON request bodies:
@derive(Deserialize)
model CreateUser:
name: str
email: str
@route("/users", methods=[POST])
async def create_user(body: Json[CreateUser]) -> Json[User]:
user = User(id=1, name=body.name, email=body.email)
return Json(user)
Application¶
Starting the Server¶
Create an App and call run():
def main() -> None:
app = App()
app.run(host="0.0.0.0", port=3000)
Parameters:
host: Bind address (default:"127.0.0.1")port: Port number (default:8080)
How It Works¶
When you compile an Incan web application:
- Routes are collected from
@routedecorators - Handlers become async Rust functions with Axum extractors
- Models with
@derive(Serialize/Deserialize)get serde derives app.run()becomes Axum router setup + tokio server
The generated Rust code uses:
axum::Routerfor routingaxum::Jsonfor JSON request/responseaxum::extract::Pathfor path parametersaxum::extract::Queryfor query parameterstokiofor async runtime
Complete Example¶
"""
A simple REST API for managing items.
"""
from web import App, route, Response, Json, GET, POST, DELETE
@derive(Serialize, Deserialize)
model Item:
id: int
name: str
price: float
# In-memory storage (in a real app, use a database)
items: list[Item] = []
@route("/api/items", methods=[GET])
async def list_items() -> Json[list[Item]]:
"""List all items."""
return Json(items)
@route("/api/items", methods=[POST])
async def create_item(body: Json[Item]) -> Json[Item]:
"""Create a new item."""
items.append(body.value)
return Json(body.value)
@route("/api/items/{id}", methods=[GET])
async def get_item(id: int) -> Response:
"""Get an item by ID."""
for item in items:
if item.id == id:
return Json(item)
return Response.not_found("Item not found")
@route("/api/items/{id}", methods=[DELETE])
async def delete_item(id: int) -> Response:
"""Delete an item by ID."""
for i, item in enumerate(items):
if item.id == id:
items.pop(i)
return Response.no_content()
return Response.not_found("Item not found")
def main() -> None:
println("Starting API server at http://localhost:8080")
app = App()
app.run(port=8080)
Performance¶
Since Incan compiles to Rust/Axum, your web application runs with:
- Native performance — no interpreter overhead
- Zero-cost async — Tokio's efficient async runtime
- No garbage collector — predictable latency
- Low memory usage — Rust's ownership model
This makes Incan ideal for high-performance APIs and microservices.
See Also¶
- Error Handling - Working with
Resulttypes - Derives & Traits - Drop trait for custom cleanup
- File I/O - Reading, writing, and path handling
- Async Programming - Async/await with Tokio
- Imports & Modules - Module system, imports, and built-in functions
- Rust Interop - Using Rust crates directly from Incan