ESC
Type to search...
S
Soli Docs

Controllers

Controllers are the heart of your application. They handle HTTP requests, process data, and determine the response. Soli supports both modern class-based controllers and simple function-based handlers.

Class-Based Controllers

Recommended for most applications. They provide structure, inheritance, and powerful hooks like before_action.

class PostsController extends ApplicationController
    static
        this.layout = "layouts/posts";
    end

    def index(req)
        let posts = Post.all();
        render("posts/index", {
            "title": "All Posts",
            "posts": posts
        })
    end

    def show(req)
        let id = req.params["id"];
        let post = Post.find(id);

        if post == null
            return error(404, "Post not found");
        end

        render("posts/show", {
            "title": post["title"],
            "post": post
        })
    end
end

Lifecycle Hooks

Execute code before or after actions. Perfect for authentication or data loading.

this.layout

Set the layout template for all actions

this.before_action

Run code before action executes

this.after_action

Run code after action executes

class PostsController extends ApplicationController
    static
        # Set layout for all actions
        this.layout = "layouts/posts";

        # Run before ALL actions
        this.before_action = def(req)
            print("Processing: " + req.path);
            req
        end

        # Run before SPECIFIC actions only
        this.before_action(:show, :edit) = def(req)
            let post = Post.find(req.params["id"]);
            if post == null return error(404, "Not Found"); end
            req["post"] = post; # Pass to action
            req
        end

        # Run after SPECIFIC actions
        this.after_action(:create, :update) = def(req, response)
            # Log activity after create/update
            print("Action completed");
            response
        end
    end
    
    # ... actions ...
end

Function-Based Controllers

Great for simple APIs or microservices where you don't need the full power of classes.

# GET /health
def health(req)
    {
        "status": 200,
        "headers": {"Content-Type": "application/json"},
        "body": "{\"status\":\"ok\"}"
    }
end

The Request Object

Access all HTTP request details through the req parameter.

req.params

Route parameters & query strings

req.body

Raw request body content

req.headers

HTTP headers map

req.session

User session data storage

Response Types

render("home/index", { "title": "Welcome" })
redirect("/login")
{
    "status": 200,
    "headers": { "Content-Type": "application/json" },
    "body": json_encode({ "data": posts })
}
error(404, "Page Not Found")

Pro Tip: Keep Controllers Thin

Delegate complex business logic to Models or Service objects. Controllers should primarily focus on handling the HTTP request and selecting the correct response.

Best Practices

  • Use class-based controllers for better organization and reuse.
  • Create a base ApplicationController to share logic like auth across all controllers.
  • Use before_action to load resources and DRY up your code.
  • Prefix private helper methods with _ to prevent them from becoming routes.

Next Steps