What’s Skinny?
Skinny is a full-stack web app framework built on Skinny Micro.
To put it simply, Skinny framework’s concept is Scala on Rails. Skinny is highly inspired by Ruby on Rails and it is optimized for sustainable productivity for Servlet-based web app development.
What’s more, Skinny’s components are basically independent from Skinny app environment. If you prefer using only Skinny ORM, Validator module or else, it’s also possible. We hope Skinny components help developers that use other frameworks too.
Why Skinny named as Skinny? What does the name Skinny
actually mean? We have three reasons as follows.
Application should be skinny
All the parts of a web application - controllers, models, views, routings and other settings - should be skinny. If you use Skinny framework, you can do without a lot of non-essential boilerplate code. For instance, when you create a simple registration form, all you need to do is define the parameters and validation rules and create view templates in an efficient way (ssp, scaml, jade, FreeMarker or something else) in most cases.
Framework itself should be skinny
Even if you need to investigate Skinny’s internals, don’t worry. Skinny keeps itself skinny, too. We believe that if the framework is well-designed, the resulting implementation will be skinny.
‘su-ki-ni’ in Japanese means ‘as you like it’
A sound-alike word “好きに (su-ki-ni)” in Japanese means “as you like it”. This is only half kidding but it also represents Skinny’s concept. Skinny framework should provide flexible APIs to empower developers as much as possible and shouldn’t get in the way.
Try It Right Now
Download skinny-blank-app.zip
and unzip it, then just run ./skinny (or skinny.bat) command on your terminal. That’s all!
After unzipping blank-app.zip, Let’s create our first Skinny app by using the scaffold generator.
# If you're a zsh user, try "noglob ./skinny g scaffold ..."
./skinny g scaffold members member name:String activated:Boolean luckyNumber:Option[Long] birthday:Option[LocalDate]
./skinny db:migrate
# When you use IDE's debugger, use "./skinny debug" instead. (default JDWP port is 5005)
./skinny run
If you prefer Scalate templates precompilation, specify -precompile
option too.
./skinny run -precompile
And then, access http://localhost:8080/members
.
You can also run the generated tests.
./skinny db:migrate test
./skinny test
Now let’s create a war file for deployment to a Servlet container.
./skinny package
It’s also possible to build a standalone runnable jar file (with embedded Jetty server).
./skinny package:standalone
Skinny’s Components Overview
Skinny Micro
In the aspect of Web application framework, Skinny Framework’s core part is an independent and useful micro framework named Skinny Micro. The following code shows you how to run Skinny Micro application right now by using sbt Script runnner.
#!/usr/bin/env scalas
/***
scalaVersion := "2.13.8"
libraryDependencies += "org.skinny-framework" %% "skinny-micro-server" % "2.3.0"
*/
import skinny.micro._
object HelloApp extends WebApp {
get("/say-hello") {
s"Hello, ${params.getOrElse("name", "Anonymous")}!\n"
}
}
WebServer.mount(HelloApp).port(4567).start()
println
println("Try: curl -v 'localhost:4567/say-hello?name=Martin'")
println
Routing & Controller & Validator
Previously Skinny 1.x’s routing mechanism and controller layer on MVC architecture was built upon Scalatra. Skinny project decided to move its own rich Servlet layer, Skinny Micro described above.
SkinnyController
is a trait which extends SkinnyMicroFilter (WebApp)
and includes various useful components out-of-the-box.
// src/main/scala/controller/MembersController.scala
class MembersController extends SkinnyController {
def index = {
set("members" -> Member.findAll()) // can call this in views
render("/members/index")
}
}
// src/main/scala/controller/Controllers.scala
object Controllers {
object members extends MembersController with Routes {
val indexUrl = get("/members/?")(index).as("index")
}
}
// src/main/scala/ScalatraBootstrap.scala
class ScalatraBootstrap extends SkinnyLifeCycle {
override def initSkinnyApp(ctx: ServletContext) {
Controllers.members.mount(ctx)
}
}
SkinnyResource
, which is similar to Rails ActiveResource, is also available. It’s a pretty DRY way to define RESTful resources.
object CompaniesController extends SkinnyResource {
protectFromForgery()
override def model = Company
override def resourcesName = "companies"
override def resourceName = "company"
...
}
Company
object should implement skinny.SkinnyModel
APIs and you should prepare some view templates under src/main/webapp/WEB-INF/views/members/
.
See in detail: Controller & Routes
Skinny ORM
Skinny provides you Skinny-ORM as the default O/R mapper, which is built with ScalikeJDBC.
Skinny-ORM does a lot of work under the hood, so you don’t need to write much code. Your first model class and companion are here.
case class Member(id: Long, name: String, createdAt: DateTime)
object Member extends SkinnyCRUDMapper[Member] {
override def defaultAlias = createAlias("m")
override def extract(rs: WrappedResultSet, n: ResultName[Member]) = new Member(
id = rs.long(n.id),
name = rs.string(n.name),
createdAt = rs.dateTime(n.createdAt)
)
}
That’s all! Now you can use the following APIs.
// find by primary key
val member: Option[Member] = Member.findById(123)
val member: Option[Member] = Member.where("id" -> 123).apply().headOption
val members: List[Member] = Member.where("id" -> Seq(123, 234, 345)).apply()
// find many
val members: List[Member] = Member.findAll()
val groupMembers = Member.where("groupName" -> "Scala Users", 'deleted -> false).apply()
// create with unsafe parameters
Member.createWithAttributes(
"id" -> 123,
"name" -> "Chris",
"createdAt" -> DateTime.now
)
// update with unsafe parameters
Member.updateById(123).withAttributes("name" -> "Alice")
// delete
Member.deleteById(234)
DB Migration with Flyway
DB migration is provided by Flyway. Usage is pretty simple.
./skinny db:migrate [env]
This command expects src/main/resources/db/migration/V***__***.sql
files.
See in detail: DB Migration
View Templates
Skinny framework basically follows Scalatra’s Scalate Support, but Skinny has an additional convention.
Template paths should be of the form {path}.{format}.{extension}
. Expected {format} are html
, json
, js
and xml
.
For instance, assuming your controller code looks like this:
class MembersController extends SkinnyController {
def index = {
set("members", Member.findAll())
render("/members/index")
}
}
The render method expects that src/main/webapp/WEB-INF/views/members/index.html.ssp
exists.
<%=@val members: Seq[model.Member] %>
<hr/>
#for (member <- members)
${member.name}
#end
Scalate supports many template engines. For example, if you want to write your template using Jade, save it as src/main/webapp/WEB-INF/views/members/index.html.jade
instead.
See in detail: View Templates
Skinny Mailer
SkinnyMailer makes sending emails pretty easy.
val config = SkinnyMailerConfigdefault.copy(
debug = true
)
val mailer = SkinnyMailer(config)
mailer
.from("info@skinny-framework.org")
.to("you@example.com")
.cc("support@skinny-framework.org", "xxx@example.com")
.subject("Skinny Framework 1.0.0 is out!")
.body {
"""Hi all,
|
|We're very proud to announce that Skinny Framework version 1.0.0 is released.
|
|.....
|
|Best,
|Skinny Framework Team
|""".stripMargin
}.deliver()
See in detail: Mail
Assets Support (CoffeeScript, LESS, Sass, ReactJS, Scala.js)
First, add skinny-assets
to libraryDependencies.
libraryDependencies ++= Seq(
"org.skinny-framework" %% "skinny-framework" % "4.0.0",
"org.skinny-framework" %% "skinny-assets" % "4.0.0",
"org.skinny-framework" %% "skinny-test" % "4.0.0" % "test"
)
And then, add AssetsController
to routes. Now you can easily use CoffeeScript, LESS and Sass.
// src/main/scala/ScalatraBootstrap.scala
class ScalatraBootstrap extends SkinnyLifeCycle {
override def initSkinnyApp(ctx: ServletContext) {
AssetsController.mount(ctx)
}
}
AssetsController supports Last-Modified header and returns status 304 correctly if the requested file isn’t changed.
However, precompiling the assets is highly recommended in production (./skinny package
does that).
See in detail: Assets Support
Testing Support
You can use Scalatra’s great test support. Some extra optional features are provided by the skinny-test library.
class ControllerSpec extends ScalatraFlatSpec with SkinnyTestSupport {
addFilter(MembersController, "/*")
it should "show index page" in {
withSession("userId" -> "Alice") {
get("/members") { status should equal(200) }
}
}
}
See in detail: Testing
FactoryGirl
Though Skinny’s FactoryGirl is not a complete port of thoughtbot/factory_girl, this module will be quite useful when testing your apps.
case class Company(id: Long, name: String)
object Company extends SkinnyCRUDMapper[Company] {
def extract ...
}
val company1 = FactoryGirl(Company).create()
Configuration is not in yaml files but a typesafe-config conf file. In this example, src/test/resources/factories.conf
looks like this:
company {
name="Typesafe"
}
See in detail: FactoryGirl
Examples
Basic Example
How to write controllers, models and routes. Test suites (87% coverage). Coveralls settings.
https://github.com/skinny-framework/skinny-framework-example
Scaldi Example
How to integrate Scaldi with Skinny Framework. Stubbing AWS SDK.
https://github.com/skinny-framework/skinny-scaldi-example
Skinny ORM in Play apps
How to use Skinny ORM in Play applications. Typesafe Activator Template. Reverse model generator.
https://github.com/skinny-framework/skinny-orm-in-play
Skinny Task Runner Example
How to use only skinny-task without Web application project settings.
https://github.com/skinny-framework/skinny-task-example
Skinny Framework Team and You
Skinny Framework team is passionately working on this project.
https://github.com/orgs/skinny-framework/people
If you’re interested in Skinny development, pull requests are always welcome.
Under The MIT License
(The MIT License)
Copyright © skinny-framework.org