Slick codegen and Scala.js
Since Scala.js came out, I've wanted to use the Slick codegen module to generate my data model and share classes between the client and server. It took me a while to reach a setup I liked, below are some of my lessons.
TL;DR. Haven't found a perfect solution yet, but working example is on Github.
This post assumes you have knowledge of both sbt and Slick, no knowledge of Scala.js is required.
Let's begin, I have some SQL code from which I want to generate boilerplate Slick mapping. My sbt project looks like this
build.sbt <-- second step project/ plugins.sbt <-- first step Codegen.scala <-- third step server/ src/models/Tables.scala <-- autogenerated conf/db/evolutions/1.sql <-- database stuff shared/ src/models/Tables.scala <-- autogenerated src/models/Time.scala <-- inherits java.util.Date client/ slick-driver/ src/slick/Driver.scala <-- custom driver server-codegen/ <-- empty project to run server codegen shared-codegen/ <-- empty project to run shared codegen flyway/ <-- empty project to run migrations
This seems like a lot of projects. Don't fear, creating projects in sbt is easy.
The second step is to define the projects in
build.sbt. You don't
necessarily need all the bells and whistles from that build definition, but the
project definitions are required.
We're done with the boring part.
The third step is to write code that generates even more code! We write this in
project/Codegen.scala. This section has much room for improvements,
but I think my findings below are not too bad. I struggled most with dates,
custom type mapping and using a custom driver.
I want to map
timestamp columns to something that runs both on the JVM and
The benefit to this approach is that
Timestamp is a case class. Case classes
play well with Slick's
* method and also make it possible to use libraries
The downside to this approach is that I created Yet Another date class for my own project. Ideally, these should be part of some reusable library.
Reuse custom type maps
I want to reuse my custom type mappings between the server and shared code
SourceCodeGenerator API makes this trickier than I expected
(or maybe my trait foo isn't good enough). Eventually, I settled on using trait
I haven't tried to map more custom types than
imagine that the
colMap will grow alongside your application.
Use custom slick-pg driver
Skip this part if you don't want to use the latest and greatest features in PostgreSQL.
slick-pg exposes many of PostgresSQL's best features with Slick.
Unfortunately, there isn't a good way to use these features on H2 during
For me, the trade-off is worth it. I put
lazy val profile = is.launaskil.slick.Driver into the server
Tables.scala instead of
val profile: JdbcProfile. I won't look
back (but let me know if you find a solution ;)).
Note. You must publish the Slick driver locally before running the app, Play's custom class loaders will otherwise not find the driver, see #266.
Run the app
Excellent, everything is almost complete. The fourth step is to run the app.
First, you need to start the database. I use docker (on OSX) and run the following command
$ docker run -p 5432:5432 --name launaskil-dev -e POSTGRES_PASSWORD=postgres -d postgres
Next, I source the database connection parameters and fire up sbt like this
$ source source_me && sbt
[server] $ flyway/flywayMigrations
p to publish the slick driver and then
cg (short for "codegen") to
run the code generation. These aliases are defined in build.sbt.
[server] $ p [server] $ cg
To verify the project compiles, execute
run in the
sbt console and visit
localhost:9000. The browser should display a message like
List(AppUserRow(1,Some(Sun Nov 14:14:09 GMT+0100 2015),None,List(a)))
That's a wrap. We just managed to use Slick codegen to generate our data model from SQL such that we can share classes between the client and server. The solution supports dates, custom type maps and custom drivers.
It would be neat to put some of this stuff into an sbt plugin.
PS. You'll see
is.launaskil sprinkled around the app.
Launaskil.is is a website I wrote last summer and the examples are
borrowed from the app's closed source.