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.”
this post is sponsored by my books: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |
Summary
If you ever need to run Play Framework 2.8 with Scala 2.13, MySQL, and Slick, I hope these notes are helpful.