The basics of building a Scala/Play Framework
Introduction
For demonstrate how to build an asynchronous & non-blocking Play framework. it will take all have being simplicity. create a very basic simple CRUD that allows to perform and presentation the very basic of CRUD operations for the data stored in a Mysql database.
Prerequisite
The post is accessible for beginners. However, Scala and Play framework fundamentals are required. Before going any further, please make sure that you have the following tools installed on your local machine.
JVM 14
Scala 2.13.3
Sbt 1.3.13
Docker (Optional)
Architecture of Play Framework
The typical architecture of a Play Framework web application. Here, some components are mandatory for all applications, such as the routes file, Controller, View Templates, and so on; some are optional:
Following steps in any Play Framework Web Application for Observer:
The users access a Play Web Application, using the application URL and a web browser.
User sends a HTTP request to a Play Web Application, such as /route2 for request reaches the Play Framework's Routing component (the default route filename is routes).
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 XAMPP on macOS
Username: root
Password: root
Port: 8080
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.red.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
To run the application, unzip the file on the target server, and then run the script in the bin directory.
References:
https://pedrorijo.com/blog/play-slick-updated/
https://medium.com/geekculture/rest-api-with-scala-play-framework-and-reactive-mongo-5016e57846a9
https://alvinalexander.com/scala/play-framework-2.8-scala-slick-mysql-sbt/
https://simpleepic.medium.com/play-framework-with-mysql-database-part-3-6136192812a6
https://sysgears.com/articles/how-to-create-restful-api-with-scala-play-silhouette-and-slick/
https://blog.knoldus.com/hands-on-crud-operation-with-scala-play/amp/
https://www.lxpert.com/showarticle/36/Build-your-first-Play-Framework-web-application
https://blog.genuine.com/2016/02/database-crud-in-scala-play/
https://dev.to/pazvanti/building-a-rest-api-in-play-framework-3agh
https://copyprogramming.com/howto/crud-operation-play-framework-example-using-angular-js