ESC
Type to search...
S
Soli Docs

Routing

Routes map HTTP requests to controller actions. Define your application's URL structure in config/routes.sl.

HTTP Methods

Use these helpers to define routes for different HTTP methods:

Method Description Example
get Retrieve a resource get("/users", "users#index")
post Create a new resource post("/users", "users#create")
put Update a resource completely put("/users/:id", "users#update")
patch Update a resource partially patch("/users/:id", "users#patch")
delete Delete a resource delete("/users/:id", "users#delete")

Basic Routes

# Root page
get("/", "home#index");

# Static pages
get("/about", "home#about");
get("/contact", "home#contact");

# Form submissions
post("/contact", "home#submit_contact");

Route Parameters

Use :param_name to capture dynamic segments:

# Capture user ID
get("/users/:id", "users#show");

# Multiple parameters
get("/posts/:post_id/comments/:comment_id", "comments#show");

# Optional parameters
get("/users/:id?", "users#show");

Access parameters in your controller:

def show(req: Any)    let user_id = req["params"]["id"];
    let post_id = req["params"]["post_id"];

    {
        "status": 200,
        "body": "User ID: " + user_id
    }
end

Splat Routes

Use *param_name to capture the remaining path segments greedously:

# Capture remaining path
get("/files/*filepath", "files#serve");
get("/downloads/*filename", "downloads#handle");

# Combine with parameters
get("/users/:id/*action", "users#action");
get("/api/*version/users/*id", "api#user");

# Root splat
get("/*path", "catchall#handle");

Captured splat values include a leading slash:

def serve(req: Any)    let filepath = req["params"]["filepath"];

    # /files/docs/report.pdf -> filepath = "/docs/report.pdf"
    # /files/images/photo.jpg -> filepath = "/images/photo.jpg"

    {
        "status": 200,
        "body": "Serving: " + filepath
    }
end
Pattern Request Path Captured
/files/*path /files/a/b/c {path: "/a/b/c"}
/api/*version/users /api/v1/users {version: "/v1"}
/users/:id/*action /users/123/edit {id: "123", action: "/edit"}
/*splat /any/path/here {splat: "/any/path/here"}

Wildcard Action Routes

Use controller#* to dynamically resolve actions from the URL path:

# Dynamic docs routes - /docs/routing → docs#routing
get("/docs/*", "docs#*");

# API routes with version - /api/v1/users → api#users
get("/api/*version/*action", "api#*");

# Files serving - /files/docs/readme → files#docs/readme
get("/files/*filepath", "files#serve");

The action name is resolved from captured splat parameters in this priority order:

  1. splat parameter
  2. path, filepath, filename, action, or resource
  3. First non-nested parameter
class DocsController extends Controller
    static
        this.layout = "docs";
    end

    def routing(req: Any)        let path = req["params"]["path"];  # "/routing"
        render("docs/routing", {"active_page": "routing"})
    end

    def installation(req: Any)        render("docs/installation", {"active_page": "installation"})
    end
end
Route Pattern Request Path Action Resolved
get("/docs/*", "docs#*") /docs/routing docs#routing
get("/docs/*", "docs#*") /docs/installation docs#installation
get("/api/*version/*action", "api#*") /api/v1/users api#users
get("/files/*filepath", "files#serve") /files/docs/readme files#docs/readme

Query Parameters

Query strings are automatically parsed and available in req["query"]:

# URL: /search?q=solilang&page=1

def search(req: Any)    let query = req["query"]["q"];      # "solilang"
    let page = req["query"]["page"];    # "1"

    {
        "status": 200,
        "body": "Searching for: " + query
    }
end

RESTful Resources

Generate standard CRUD routes automatically with resources():

resources("users")
Method Path Action
GET/usersindex
GET/users/newnew
POST/userscreate
GET/users/:idshow
GET/users/:id/editedit
PUT/users/:idupdate
DELETE/users/:iddelete

Namespaces

Group routes under a common prefix, like /admin:

namespace("admin", ->
    get("/dashboard", "admin#dashboard");
    get("/users", "admin#users");
    post("/users", "admin#create_user");
)

Scoped Middleware

Apply middleware only to specific route blocks:

# Only /admin/* routes require authentication
middleware("authenticate", ->
    get("/admin", "admin#index");
    get("/admin/users", "admin#users");
)

# Public routes - no auth needed
get("/", "home#index");
get("/about", "home#about");

Nested Resources

namespace("api", ->
    resources("users", ->
        resources("posts", ->
            resources("comments")
        )
    )
)
# Creates: /api/users/:user_id/posts/:post_id/comments/:id

Auto-Derived Routes

Soli can automatically map routes to controllers based on naming conventions. Defining a method named index in home_controller.sl automatically handles GET /home.

Next Steps