WARNING: This page is a guide for 1.x series. See here instead to learn about the latest

Controller & Routes

Skinny’s routing mechanism and controller layer on MVC architecture can be thought of as a rich Scalatra.

Skinny’s extensions provide you with much simpler and richer syntax. Of course, if you need to use Scalatra’s API directly, Skinny never bothers you.


SkinnyController is a trait which extends ScalatraFilter and includes various useful components out-of-the-box.

// src/main/scala/controller/MembersController.scala
class MembersController extends SkinnyController {
  protectFromForgery() // CSRF protection enabled

  def index = {
    // set 'members' in the request scope, then you can use it in views
    set("members" -> Member.findAll())

  def newOne = render("/members/new")

  def createForm = validation(params,
    paramKey("name") is required & minLength(2),
    paramKey("countryId") is numeric

  def createFormParams = params.permit(
    "name" -> ParamType.String, 
    "countryId" -> ParamType.Long)

  def create = if (createForm.validate()) {
  } else {

  // searches both query params and path params
  def show = params.getAs[Long]("id").map { id =>
    Member.findById(id).map { member =>
      set("member" -> member)
    }.getOrElse haltWithBody(404)
  }.getOrElse haltWithBody(404)


// src/main/scala/controller/Controllers.scala
object Controllers {
  object members extends MembersController with Routes {
    val indexUrl = get("/members/?")(index).as('index)
    val newUrl = get("/members/new")(newOne).as('new)
    val createUrl = post("/members/?")(create).as('create)
    val showUrl = get("/members/:id")(show).as('show)

// src/main/scala/ScalatraBootstrap.scala
class ScalatraBootstrap extends SkinnyLifeCycle {
  override def initSkinnyApp(ctx: ServletContext) {
    // register routes

#render expects src/main/webapp/WEB-INF/views/members/index.html.ssp by default.

<!-- src/main/webapp/WEB-INF/views/members/index.html.ssp -->
<%@val members: Seq[Member]%>
#for (member <- members)

Reverse Routes

You can use Scalatra’s reverse routes.


In controllers:

class MembersController extends SkinnyController {
  def oldPage = redirect(url(Controllers.members.indexUrl))

object Controllers {
  object members extends MembersController with Routes {
    val indexUrl = get("/members/?")(index).as('index)
    val showUrl = get("/members/:id")(show).as('show)

In views:

// Jade example
a(href={s.url(Controllers.members.showUrl, "id" -> member.id)}) Show detail

FYI: You can see more examples for SkinnyResource by generating scaffold views.

SkinnyController & SkinnyServlet

There are the two controller base traits. SkinnyController is a ScalatraFilter. SkinnyServlet is a ScalatraServlet.

In general SkinnyController is more suitable for Skinny framework based applications.

However, if you really need to use a ScalatraServlet instead of a filter, use SkinnyServlet.


SkinnyResource is a useful base trait for RESTful web services. SkinnyResource is very similar to Rails ActiveResource.

Resource Routing: the Rails Default (Rails Routing from the Outside In — Ruby on Rails Guides)


SkinnyResource is also useful as a controller sample. If you’re a Skinny beginner, take a look at its code.


SkinnyResourceActions has action methods for the resource and SkinnyResourceRoutes defines routings for the resource. If you’d like to customize routings (e.g. use only creation and deletion), just mixin only SkinnyResourceActions and define routings by yourself.

You can see a SkinnyResource example using the scaffolding generator.

./skinny g scaffold members member name:String birthday:LocalDate

Then you get the following code. If you need to customize, override some parts of SkinnyResource:


package controller

import skinny._
import skinny.validator._
import model.Member

object MembersController extends SkinnyResource {
  protectFromForgery() // CSRF protection

  override def model = Member // SkinnyModel for this controller
  override def resourcesName = "members"
  override def resourceName = "member"

  // parameters & validations for creation
  override def createParams = Params(params).withDate("birthday")
  override def createForm = validation(createParams,
    paramKey("name") is required & maxLength(512),
    paramKey("birthday") is required & dateFormat
  override def createFormStrongParameters = Seq(
    "name" -> ParamType.String,
    "birthday" -> ParamType.LocalDate

  // parameters & validations for modification
  override def updateParams = Params(params).withDate("birthday")
  override def updateForm = validation(updateParams,
    paramKey("name") is required & maxLength(512),
    paramKey("birthday") is required & dateFormat
  override def updateFormStrongParameters = Seq(
    "name" -> ParamType.String,
    "birthday" -> ParamType.LocalDate


Required view templates:

├── _form.html.ssp
├── edit.html.ssp
├── index.html.ssp
├── new.html.ssp
└── show.html.ssp

In this case, SkinnyResource provides the following URLs from SkinnyResourceRoutes by default. If you don’t need all the routes below, just mixin SkinnyResourceActions instead and specify only routes you need by hand.


skinny.Skinny provides getters for basic elements in view templates.

<%@val s: skinny.Skinny %>


beforeAction/afterAction Filters

Scalatra has before/after filters by default, but we recommend Skinny users to use Skinny’s beforeAction/afterAction filters.

The reason is that Scalatra’s filters might affect other controllers that are defined further down in ScalatraBootstrap.scala and it’s not easy to figure out where each controller’s before/after affects completely.

So if you need filters that are similar to Rails filters, just use Skinny filters.

Scalatra’s before/after filters


Skinny’s beforeAction/afterAction filters


class MembersController extends SkinnyController with Routes {

  // Scalatra filters
  before() { ... } // might affect other controllers
  after() { ... }

  // Skinny filters
  beforeAction(only = Seq('index, 'new)) {
  afterAction(except = Seq('new)) {

  def index = ...
  def newInput = ...
  def edit = ...

  // routes
  get("/members/?")(index).as('index)      // before, after
  get("/members/new")(newInput).as('new)   // before
  get("/members/:id/edit")(edit).as('edit) // after



SkinnyFilter is our original filter to enable easily handling before/after/error for each controller. You can apply the same beforeAction/afterAction/error filters to several controllers by just mixing in SkinnyFilter-based traits.

Contrary to your expectations, Scalatra doesn’t run all the handlers for after and error. Only the first one would be applied and the others are just ignored. We think it’s difficult to be aware of this specification (it’s not a bug but by design). So SkinnyFilter’s before/after/error handlers are always applied.

For instance, in skinny-blank-app, ErrorPageFilter is applied to ApplicationController.

trait ApplicationController extends SkinnyController
  with ErrorPageFilter {


ErrorPageFilter is as follows. It’s pretty easy to customize such a filter (e.g. adding error notification) as follows.

trait ErrorPageFilter extends SkinnyRenderingFilter {
  // appends error filter in order
  addRenderingErrorFilter {
    case e: Throwable =>
      logger.error(e.getMessage, e)
      try {
        status = 500
      } catch {
        case e: Exception => throw e

It just outputs error logging, set status as 500 and renders “/error/500.html.ssp” or similar templates. SkinnyRenderingFilter is a sub-trait of SkinnyFilter. SkinnyFilter’s response value is always ignored. But SkinnyRenderingFilter can return a response body like above.

The second example is TxPerRequestFilter which enables you to apply the open-session-in-view pattern to your apps.

trait TxPerRequestFilter extends SkinnyFilter with Logging {
  def cp: ConnectionPool = ConnectionPool.get()

  def begin = {
    val db = ThreadLocalDB.create(cp.borrow())

  // will handle exceptions occurred in controllers
  addErrorFilter { case e: Throwable =>
    Option(ThreadLocalDB.load()).foreach { db =>
      if (db != null && !db.isTxNotActive) using(db)(_.rollbackIfActive())

  def commit = {
    val db = ThreadLocalDB.load()
    if (db != null && !db.isTxNotActive) {


How to Do It? Examples

Basically, you will use Scalatra’s DSL.



(Query/Form/Path) Parameters


val name: Option[String] = params.get("id")
val id: Option[Int] = params.getAs[Int]("id")
val id: Long = params.getAsOrElse[Long]("id", -1L)

val ids: Option[Seq[Int]] = multiParams.getAs[Int]("ids")

// additional APIs are provided from Skinny 
queryParams: Params
queryMultiParams: MultiParams
formParams: Params
formMultiParams: MultiParams







get("/say/*/to/*") {
  // Matches "GET /say/hello/to/world"
  multiParams("splat") // == Seq("hello", "world")

get("/download/*.*") {
  // Matches "GET /download/path/to/file.xml"
  multiParams("splat") // == Seq("path/to/file", "xml")




get("""^\/f(.*)/b(.*)""".r) {
  // Matches "GET /foo/bar"
  multiParams("captures") // == Seq("oo", "ar")




def hello = {
  cookies += "name" -> "value"
  cookies -= "name"

def helloWithOptions = {
  implicit val options = CookieOptions(secure = true)
  cookies.set("name" -> "value")
  cookies.set("name" -> "value")(options)



Request/Response Headers

val v: Option[String] = request.header(name)


response.headers += "name" -> "value"
response.headers -= "name"



val v: Any = session("name") // or session('name)
session += "name" -> "value"
session -= "name"


Even if you don’t use sessions in your application code, Scalatra’s Flash and CSRF protection features are using servlet sessions. So your apps are not naturally stateless when using vanilla Scalatra.

We recommend you use SkinnySession to keep your Skinny apps stateless. When you enable SkinnySession, these features also start using SkinnySession instead of Servlet session attributes.


import scalikejdbc._
import skinny.session.SkinnySessionInitializer
class ScalatraBootstrap extends SkinnyLifeCycle {
  override def initSkinnyApp(ctx: ServletContext) {
    // Database queries will be increased
    GlobalSettings.loggingSQLAndTime = LoggingSQLAndTimeSettings(
      singleLineMode = true
    ctx.mount(classOf[SkinnySessionInitializer], "/*")



class RootController extends ApplicationController with SkinnySessionFilter {

  def index = {
    val v: Option[Any] = skinnySession.getAttribute("name")
    skinnySession.setAttribute("name", "value")

DB migration file:

-- H2 Database compatible
create table skinny_sessions (
  id bigserial not null primary key,
  created_at timestamp not null,
  expire_at timestamp not null
create table servlet_sessions (
  jsession_id varchar(32) not null primary key,
  skinny_session_id bigint not null,
  created_at timestamp not null,
  foreign key(skinny_session_id) references skinny_sessions(id)
create table skinny_session_attributes (
  skinny_session_id bigint not null,
  attribute_name varchar(128) not null,
  attribute_value bytea,
  foreign key(skinny_session_id) references skinny_sessions(id)
alter table skinny_session_attributes add constraint
  unique(skinny_session_id, attribute_name);


Notice: Flash uses servlet sessions by default. Be aware of sticky session mode.

flash(name) = value
flash += (name -> value)
flash.now += (name -> value)



Request Body

val body: String = request.body
val stream: InputStream = request.inputStream // raw HTTP POST data


File Upload

WARNING: Extend not SkinnyController but SkinnyServlet. You cannot use FileUploadFeature with SkinnyController.
import skinny.SkinnyServlet
import skinny.controller.feature.FileUploadFeature

// SkinnyServlet!!!
class FilesController extends SkinnyServlet with FileUploadFeature {

  def upload = fileParams.get("file") match {
    case Some(file) =>
      Ok(file.get(), Map(
        "Content-Type"        -> (file.contentType.getOrElse("application/octet-stream")),
        "Content-Disposition" -> ("attachment; filename=\"" + file.name + "\"")
    case None =>
          Hey! You forgot to select a file.



Response handling

halt(status = 400, headers = Map("foo" -> "bar"), reason = "why")

redirect("/top") // 302
redirect301("/new_url") // 301
redirect302("/somewhere") // 302
redirect303("/complete") // 303





CSRF Protection

Notice: Scalatra CSRF protection implementation uses servlet sessions by default. Be aware of sticky session mode.

Define protectFromForgery in controller/RootController.scala:

class RootController extends ApplicationController {


Use Skinny.csrfMetaTags in /WEB-INF/layouts/default.jade:

-@val body: String
-@val s: skinny.Skinny
!!! 5
    != s.csrfMetaTags
    %script(type="text/javascript" src={uri("/assets/js/skinny.js")})

Check execution time

val result = warnElapsedTime(1) {
// will output "[SLOW EXECUTION DETECTED] Elapsed time: 10 millis"

In controllers, you can add request info:

val result = warnElapsedTimeWithRequest(1) {

Read your own configuration values

development {
  timeout {
  memcached=["server1:11211", "server2:11211"]

Read the server names like this:

val label: Option[String] = SkinnyConfig.stringConfigValue("defaultLabel")
val connectTimeout: Option[Int] = SkinnyConfig.intConfigValue("timeout.connect")
val memcachedServers: Option[Seq[String]] = SkinnyConfig.stringSeqConfigValue("memcached")

Awaiting several Futures within action method

The following is a simple dashboard application which collects several futures within the index method.

Since Skinny 1.2.0, you can easily access request and requestScope from Futures by using futureWithRequest block.

package controller

import _root_.service._
import javax.servlet.http.HttpServletRequest
import org.joda.time._
import scala.concurrent._
import scala.concurrent.duration._

// using your own ExecutionContext will be preferred in most cases
import scala.concurrent.ExecutionContext.Implicits.global

case class DashboardOps(controller: DashboardController) {
  def setCurrentUser(implicit req: HttpServletRequest) = {
    // implicit request is not ambiguous here
    val userId = controller.currentUserId.getOrElse(controller.halt(401))
    controller.set("currentUser" -> controller.adminUserService.getCurrentUser(userId))

class DashboardController extends ApplicationController {

  val adminUserService = new AdminUserService
  val accessService = new AccessLogService
  val alertService = new AlertService
  val ops = DashboardOps(this)

  def index = {
    val scope: scala.collection.concurrent.Map[String, Any] = requestScope()

      // simply define operation inside of this controller
      futureWithRequest { implicit req =>
        //set("hourlyStats", accessService.getHourlyStatsForGraph(new LocalDate))
        set("hourlyStats", accessService.getHourlyStatsForGraph(new LocalDate))(req)

// [error] example/src/main/scala/controller/DashboardController.scala:43: ambiguous implicit values:
// [error]  both value req of type javax.servlet.http.HttpServletRequest
// [error]  and method request in class DashboardController of type => javax.servlet.http.HttpServletRequest
// [error]  match expected type javax.servlet.http.HttpServletRequest
// [error]         set("hourlyStats", accessService.getHourlyStatsForGraph(new LocalDate))
// [error]            ^
// [error] one error found

      // separate operation to outside of this controller
      futureWithRequest(req => ops.setCurrentUser(req)),

      // just use Future directly
      Future {
        // When using Future directly, you must be aware of Scalatra's DynamicScope's thread local request.
        // In this case, you cannot use request here. requestScope, session and so on 
        // should be captured outside of this Future block.
        scope.update("alerts", alertService.findAll())

  private[controller] def currentUserId(implicit req: HttpServletRequest): Option[Long] = session(req).getAs[Long]("userId")
If you find a typo or mistake in this page, please report or fix it. How?