Play Framework 2.8, Scala, Slick, MySQL, and sbt (2022)

These are my notes from creating a Play Framework 2.8 project on January 2, 2022, using Scala 2.13, MySQL, and Slick. I share these notes here because I found it hard to find the correct versions of everything that needed to be used, and the correct syntax in the application.conf file. Unfortunately the Play 2.8 documentation is not up to date, so this was much harder than I expected it to be.

Create a Play Framework project

Create a Play Framework 2.8 project from their sbt/Giter8 template:

$ sbt new playframework/play-scala-seed.g8

MySQL database

  • Using MAMP on macOS
  • Username: root
  • Password: root
  • Port: 8889

Create the database and table at the MySQL command prompt:

# STEP 1:
CREATE USER 'play'@'localhost' IDENTIFIED BY 'play';

# STEP 2:
GRANT ALL PRIVILEGES
ON play101.*
TO 'play'@'localhost'
WITH GRANT OPTION;

# USE the db
use play101;

# CREATE the table(s)
create table stocks (
    id int auto_increment not null,
    symbol varchar(10) not null,
    company varchar(32) not null,
    primary key (id),
    constraint unique index idx_stock_unique (symbol)
);

# INSERT some data
INSERT INTO stocks (symbol, company) VALUES ('AAPL', 'Apple');
INSERT INTO stocks (symbol, company) VALUES ('GOOG', 'Google');

That’s not a very practical table, but it’s good enough for a simple Play/MySQL example.

build.sbt

My build.sbt file. Note that these versions seem to be correct with Play 2.8 on January 2, 2022:

name         := "play-gcp-101"
organization := "com.example"
version      := "0.1.0"
scalaVersion := "2.13.7"

lazy val root = (project in file(".")).enablePlugins(PlayScala)

libraryDependencies ++= Seq(
    guice,
    "org.scalatestplus.play" %% "scalatestplus-play" % "5.0.0" % Test,
    jdbc,
    "mysql" % "mysql-connector-java" % "8.0.27",
    "com.typesafe.play" %% "play-slick" % "5.0.0",
    "com.typesafe.play" %% "play-slick-evolutions" % "5.0.0" % Test
)

project/plugins.sbt

The project/plugins.sbt file:

addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.11")
addSbtPlugin("org.foundweekends.giter8" % "sbt-giter8-scaffold" % "0.13.1")

conf/application.conf

It took a ridiculously long time to find the correct Play 2.8 conf/application.conf syntax/incantation:

# play.evolutions.db.default.enabled = true
slick.dbs.default.profile = "slick.jdbc.MySQLProfile$"
slick.dbs.default.db.dataSourceClass = "slick.jdbc.DatabaseUrlDataSource"
slick.dbs.default.driver= "slick.driver.MySQLDriver$"
slick.dbs.default.db.properties.url="jdbc:mysql://127.0.0.1:8889/play101?serverTimezone=UTC"
slick.dbs.default.db.user="root"
slick.dbs.default.db.password="root"
slick.dbs.default.db.connectionTimeout=5s

Note that I use a short timeout value so things fail fast while developing. Play was hanging up for 30 seconds failing to connect to the database for the first 30-60 minutes of dev time, so I found that setting. One thing that might have helped is if I had added this setting to conf/logback.xml earlier:

<!-- this can help to understand with database, connections, mysql, and hikari errors -->
<logger name="com.zaxxer.hikari" level="DEBUG" />

conf/routes

Add this to the bottom of the Play conf/routes file:

# MY ROUTES
GET     /stocks    controllers.StocksController.list

models/Stock.scala

My models/Stock.scala file:

package models

case class Stock (
    val id: Long,
    val symbol: String,
    val company: String
)

dao/StockDao.scala

My dao/StockDao.scala file:

package dao

//TODO: clean up these imports
import scala.concurrent.{ ExecutionContext, Future }
import javax.inject.Inject

import models.Stock
import play.api.db.slick.DatabaseConfigProvider
import play.api.db.slick.HasDatabaseConfigProvider
import slick.jdbc.JdbcProfile

class StockDAO @Inject()(
    protected val dbConfigProvider: DatabaseConfigProvider
)(
    implicit executionContext: ExecutionContext
) extends HasDatabaseConfigProvider[JdbcProfile] {

    import profile.api._

    private val Stocks = TableQuery[StocksTable]

    def all(): Future[Seq[Stock]] = db.run(Stocks.result)

    // SLICK database stuff
    // this first "stocks" string refers to my database table name
    private class StocksTable(tag: Tag) extends Table[Stock](tag, "stocks") {
        def id      = column[Long](  "ID", O.PrimaryKey)
        def symbol  = column[String]("SYMBOL")
        def company = column[String]("COMPANY")
        def *       = (id, symbol, company) <> (Stock.tupled, Stock.unapply)
    }
}

controllers/StocksController.scala

My controllers/StocksController.scala class:

package controllers

import javax.inject.Inject

// TODO: clean these up.
// SEE:  https://github.com/playframework/play-slick/blob/master/samples/basic/app/controllers/Application.scala
import play.api._
import play.api.mvc._
import play.api.data.Form
import play.api.data.Forms.mapping
import play.api.data.Forms.text
import play.api.mvc.{ AbstractController, ControllerComponents }

import scala.concurrent.ExecutionContext

import models.Stock
import dao.StockDAO
import views._

class StocksController @Inject() (
    stockDao: StockDAO,
    controllerComponents: ControllerComponents
)(
    implicit executionContext: ExecutionContext
) extends AbstractController(controllerComponents) {

    // def list = Action {
    //     Ok(html.stock.list(StockDao.selectAll()))
    // }

    // TODO find a better way to do this
    def list = Action.async {
        stockDao.all().map { case (stocks) => Ok(views.html.list(stocks)) }
    }

}

views/list.scala.html

My views/list.scala.html file:

@(stocks: Seq[Stock])

@main("Stocks") {
    <h1>You have @stocks.size Stock(s)</h1>

    <div>
        <ul>
            @stocks.map { stock =>
                <li>
                    @stock.symbol
                </li>
            }
        </ul>
    </div>

}

Running and packaging

To run a Play 2.8 app:

$ sbt
sbt> run

# OR:

sbt> ~run

To package a Play 2.8 app:

sbt> show dist
[info] Your package is ready in target/universal/play-docker-gcp-101-0.1.0.zip
[info] target/universal/play-gcp-101-0.1.0.zip

Per the docs, “To run the application, unzip the file on the target server, and then run the script in the bin directory.”

Summary

If you ever need to run Play Framework 2.8 with Scala 2.13, MySQL, and Slick, I hope these notes are helpful.